###React+Flux
React 不使用 HTML,而使用 JSX 。它打算抛弃 DOM,要求开发者不要使用任何 DOM 方法,因为Dom操作会引起浏览器的重绘,非常影响性能,所以react做了一件事,发明出了虚拟DOM,这里推荐一篇react原理。它甚至还抛弃了 SQL ,自己发明了一套查询语言 GraphQL 。当然,这些你都可以不用,React 照样运行,但是就发挥不出它的最大威力。
在了解Flux前我们先需要了解下React的生命周期
一.理论
- 组件本质上是状态机,输入确定,输出一定确定
- 生命周期的三个阶段,三者时间是不固定的,只是在逻辑上的分类
二. 这里是列表文本. 初始化阶段:
- getDefaultProps:获取实例的默认属性(即使没有生成实例,组件的第一个实例被初始化CreateClass的时候调用,只调用一次,)
- getInitialState:获取每个实例的初始化状态(每个实例自己维护)
- componentWillMount:组件即将被装载、渲染到页面上(render之前最好一次修改状态的机会)
- render:组件在这里生成虚拟的DOM节点(只能访问this.props和this.state;只有一个顶层组件,也就是说render返回值值职能是一个组件;不允许修改状态和DOM输出)
- componentDidMount:组件真正在被装载之后,可以修改DOM
三、运行中状态:
- componentWillReceiveProps:组件将要接收到属性的时候调用(赶在父组件修改真正发生之前,可以修改属性和状态)
- shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)
- componentWillUpdate:不能修改属性和状态
- render:只能访问this.props和this.state;只有一个顶层组件,也就是说render返回值只能是一个组件;不允许修改状态和DOM输出
- componentDidUpdate:可以修改DOM
四、销毁阶段:
- componentWillUnmount:开发者需要来销毁(组件真正删除之前调用,比如计时器和事件监听器)
五、demo查看:
我这里有一个关于react生命周期的例子,大家打开后,打开控制台会看到打印出的生命周期的详细信息。
https://github.com/jdbi336534/React-lifeCycle.git
六,Flux
同时我这里提供一个Demo,大家可以下载安装下,那么我就用这个来讲解下。建议大家下载运行下,才能真正清晰明了的了解Flux的工作流程。
这个Demo主要功能,当我们输入文字后,点击“New item”按钮后,就会逐条添加到下面,当我们点击每条后面的删除按钮就会删除当前的文字。
$ git clone https://github.com/jdbi336534/React-TodoList.git
$ cd React-TodoList && npm install
$ npm run start
然后,访问 http://localhost
那么,我们先来看看flux个整个流程图。
Flux将一个应用分成了四个部分。
- View: 视图层
- Action(动作):视图层发出的消息(比如mouseClick)
- Dispatcher(派发器):用来接收Actions、执行回调函数
- Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面
Flux的最大特点,就是数据的“单向流动”。
- 用户访问 View
- View 发出用户的 Action
- Dispatcher 收到 Action,要求 Store 进行相应的更新
- Store 更新后,发出一个"change"事件
- View 收到"change"事件后,更新页面
上面过程中,数据总是"单向流动",任何相邻的部分都不会发生数据的"双向流动"。这保证了流程的清晰。
我整个项目的结构目录如下:
1. view部分
打开Demo首页app.js,只加载了一个组件
//app.js
'use strict';
import '../styles/usage/page/app.scss';
import React from 'react';
import ReactDOM from 'react-dom';
import MyButtonController from './flux/components/MyButtonController.jsx';
let app = document.getElementById('app');
ReactDOM.render(
<MyButtonController />
, app);
if (module.hot) {
module.hot.accept();
}
上面的代码中,我们的组件采用的是 React 的 controller view 模式。"controller view"组件只用来保存状态,然后将其转发给子组件。MyButtonController的源码很简单。
// components/MyButtonController.jsx
import React from 'react';
import MyButton from './MyButton.jsx';
import ButtonActions from '../actions/ButtonActions.js';
import ListStore from '../stores/ListStore.js';
export default React.createClass({
getInitialState(){
return{
items:ListStore.getAll()
}
},
createNewItem(val){
ButtonActions.addNewItem(val);
// console.log(val);
},
deleteli(index){
ButtonActions.delLi(index);
},
componentDidMount(){
ListStore.addChangeListener(this._onChange);
},
componentWillUnmount(){
ListStore.removeChangeListener(this._onChange);
},
_onChange(){
this.setState({
items:ListStore.getAll()
})
},
render() {
return (
<MyButton onClick={this.createNewItem} items={this.state.items} onDel={this.deleteli}/>
)
}
})
上面代码中,MyButtonController将参数传给子组件MyButton。
// components/MyButton.jsx
import React from 'react';
export default React.createClass({
handleVal(event){
let val=this.refs.listtodo.value;
this.refs.listtodo.value="";
// console.log(this.refs);
this.props.onClick(val);
},
delwli(index){
this.props.onDel(index);
},
render() {
let items=this.props.items;
let itemHTML=items.map((listItem,i)=>{
return <li key={i}>{listItem}<button onClick={this.delwli.bind(this,i)}>X</button></li>
});
return (
<div>
<input type="text" ref="listtodo" placeholder="请输入文字。"/>
<button onClick={this.handleVal}>New item</button>
<ul>
{itemHTML}
</ul>
</div>
)
}
})
这里有点小知识,就是map方法,大家要了解, 需要注意的是这里需要通过bind来传递参数,即
this.delwli.bind(this,i)
,不能这样this.delwli(i)
,这样会直接执行这个函数的。 你可以看到MyButton是不一个纯组件(即不含有任何状态),这里因为我要子组件传值给父组件。那么有一个更好的方法就是,在父组件中通过event.target方法来取到子组件里的值。 如下图:
MyButton只有一个逻辑,就是一旦用户点击,就调用this.createNewItem 方法,向Dispatcher发出一个Action。
createNewItem(val){
ButtonActions.addNewItem(val);
// console.log(val);
}
上面代码中,调用createNewItem方法,会触发名为addNewItem的Action。
##2.Action
每个Action都是一个对象,包含一个actionType属性(说明动作的类型)和一些其他属性(用来传递数据)。 在这个Demo里面,ButtonActions 对象用于存放所有的Action。
// actions/ButtonActions.js
import AppDispatcher from '../dispatchers/AppDispatcher.js';
export default {
addNewItem(text){
AppDispatcher.dispatch({
actionType:'ADD_NEW_ITEM',
text:text
})
},
delLi(index){
AppDispatcher.dispatch({
actionType:'DELETE_LI',
index:index
})
}
}
上面代码中,ButtonActions.addNewItem方法使用AppDispatcher,把动作ADD_NEW_ITEM派发到Store。
3.Dispatcher
Dispatcher 的作用是将 Action 派发到 Store、。你可以把它看作一个路由器,负责在 View 和 Store 之间,建立 Action 的正确传递路线。注意,Dispatcher 只能有一个,而且是全局的。 Facebook官方的 Dispatcher 实现输出一个类,你要写一个AppDispatcher.js,生成 Dispatcher 实例。
// dispatchers/AppDispatcher.js
import {Dispatcher} from 'flux';
import ListStore from '../stores/ListStore.js';
let AppDispatcher=new Dispatcher();
AppDispatcher.register(action => {
switch(action.actionType){
case 'ADD_NEW_ITEM':
ListStore.addNewItemHandle(action.text);
ListStore.emitChange();
break;
}
});
AppDispatcher.register(action => {
switch(action.actionType){
case 'DELETE_LI':
ListStore.delItemHandle(action.index);
ListStore.emitChange();
break;
}
});
export default AppDispatcher;
AppDispatcher.register()方法用来登记各种Action的回调函数。 上面代码中,Dispatcher收到ADD_NEW_ITEM动作,就会执行回调函数,对ListStore进行操作。 记住,Dispatcher 只用来派发 Action,不应该有其他逻辑。
4.store
Store 保存整个应用的状态。它的角色有点像 MVC 架构之中的Model 。 在我们的 Demo 中,有一个ListStore,所有数据都存放在那里。
import { EventEmitter } from 'events';
export default Object.assign({}, EventEmitter.prototype ,{
items:[],
getAll(){
return this.items;
},
addNewItemHandle(text){
// this.items.push(text);
this.items.unshift(text);
},
delItemHandle(index){
this.items.splice(index,1);
},
emitChange(){
this.emit('change');
},
addChangeListener(callback){
this.on('change',callback);
},
removeChangeListener(callback){
this.removeListener('change',callback);
}
})
ListStore.items用来保存条目,ListStore.getAll()用来读取所有条目,ListStore.emitChange()用来发出一个"change"事件。 由于 Store 需要在变动后向 View 发送"change"事件,因此它必须实现事件接口。 ListStore继承了EventEmitter.prototype,因此就能使用ListStore.on()和ListStore.emit(),来监听和触发事件了。 Store 更新后(this.addNewItemHandler())发出事件(this.emitChange()),表明状态已经改变。 View 监听到这个事件,就可以查询新的状态,更新页面了。
##5.View 现在,我们再回过头来看 View ,让它监听 Store 的 change 事件。
// components/MyButtonController.jsx
import React from 'react';
import MyButton from './MyButton.jsx';
import ButtonActions from '../actions/ButtonActions.js';
import ListStore from '../stores/ListStore.js';
export default React.createClass({
getInitialState(){
return{
items:ListStore.getAll()
}
},
createNewItem(val){
ButtonActions.addNewItem(val);
// console.log(val);
},
deleteli(index){
ButtonActions.delLi(index);
},
componentDidMount(){
ListStore.addChangeListener(this._onChange);
},
componentWillUnmount(){
ListStore.removeChangeListener(this._onChange);
},
_onChange(){
this.setState({
items:ListStore.getAll()
})
},
render() {
return (
<MyButton onClick={this.createNewItem} items={this.state.items} onDel={this.deleteli}/>
)
}
})
上面代码中,你可以看到当MyButtonController 发现 Store 发出 change 事件,就会调用 this._onChange 更新组件状态,从而触发重新渲染。