在强调组件化的React中,我们需要以高内聚、低耦合的原则设计高可复用性的组件。因此渲染组件的数据由两部分组成,一个是由父组件传入的Props参数、一个是组件的内部状态State。Props参数可以是任何的Javascript对象,作为组件本身可以通过使用propTypes限制必须输入的参数和输入参数的类型以保证组件的可用性。State负责维护组件内部的状态,组件内部必要时可以通过触发父组件传递的回调函数传递信息给父组件或者将State以Props的形式传递给子组件。
1、数据重复以及数据不一致的问题
不同的组件之间在数据上如果存在依赖关系,则在不同的组件中可能都各自维护着相同的数据或者一个组件的数据可以根据另一个组件的数据计算得到,这就存在了数据重复的问题。数据出现了重复的情况就有了一致性的问题,特别在一个组件的数据由其他多个组件的多个数据决定的情况下一致性的问题就需要越来越复杂的函数来保证。
2、数据传递问题
在一个应用中如果包含三级或者三级以上的组件结构,顶层的祖父级组件想要传递一个数据给最底层的子组件,用prop的方式就只能通过父组件的中转。这样,即使父组件不需要该prop也被迫成为一个搬运工的角色这样与我们创建高可用性的组件原则相违背。(虽然可以使用React的上下文Context解决这个问题,但是Context的使用有可能使组件间的关系变得复杂且代码维护性差,在官方文档中并不推荐使用)
以上描述的React原生数据流存在的问题会使我们使用React开发应用时将视图、数据和业务逻辑混在一起,当应用足够庞大的时候代码的可阅读性和可维护性就变得很低。因此,Facebook在发布React的时候也同时推出了Flux框架;Flux的核心思想是“单向数据流”,在理解Flux的基础上我们可以更容易地理解Redux。
Flux框架的出现源于Facebook对现有的传统MVC框架不满,在MVC框架中当Model数据层和View视图层可以直接相互调用的时候而不是通过控制器Controller通讯时就会出现多个Model对应多个View的多对多混乱的情况,例如下图:
一个Flux应用包含以下的四个部分:
使用Flux的流程:
1、创建Dispatcher
import {Dispatcher} from 'flux';
export default new Dispatcher();
安装好Flux后导入Dispatcher模块,new一个Dispatche对象就可以了。它的的主要作用就是用来派发Action到Store注册的回调函数里的。
2、创建Action
创建Action分为以下两个步骤
步骤一:在ActionType.js中定义动作的类型
export const ActionTypeName= '字符串';
动作的定义是一个常量字符串类型,仅用于表示当前动作的类型。
步骤二:在Action.js中定义动作的构造函数
export const actionConstructor= (参数) => {
AppDispatcher.dispatch({
type: ActionTypes.动作类型,
参数名: 参数
});
在Action.js中定义可以产生并派发action对象的函数
3、创建Store
Store存储应用的状态,同时还要接受Dispatcher派发的动作并根据动作来决定如何更新应用的状态
const Store = Object.assign({}, EventEmitter.prototype, {
getValue: function() {
//这是获取store存储的状态的方法
}
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
在Store中,Store扩展了EventEmitter.prototype成为一个EventEmitter对象。可以将Store绑定在特定的View上并在状态改变的时候通过this.emit触发绑定了该Store的View上的回调函数。
Store.dispatchToken = Dispatcher.register((action) => {
if (action.type === ActionTypes.ActionTypesA) {
Store.emitChange();
} else if (action.type === ActionTypes.ActionTypesB) {
Store.emitChange();
}
});
将Store注册到Dispatcher上,当Dispatcher接收到一个动作的时候会将该动作派发到所有注册到Dispatcher上的回调函数,回调函数去判断对应的动作类型做对应的操作。我们无法预测Dispatcher派发到不同Store的不同回调函数的顺序,所以不同的回调函数之间如果存在依赖关系可以使用Dispatcher.waitFor(某个回调函数的返回值dispatchToken
)处理回调函数的先后处理顺序。
4、修改View
constructor(props) {
super(props);
this.state = {
stateName: Store.getValue( )
}
}
在组件创建时的构造函数中,将组件的State设置为Store中存储的状态
componentDidMount() {
Store.addChangeListener(this.onChange);
}
componentWillUnmount() {
Store.removeChangeListener(this.onChange);
}
onChange() {
//当Store中状态改变的时候触发组件改变
}
在组件被挂载时(生命周期的componentDidMount函数中)为组件添加监听器和在组件被销毁之前(生命周期的componentWillunmount函数中)移除监听器。当Store中的状态改变的时候,将会触发添加在监听器上的回调函数this.onChange(),一般我们在该回调函数中调用this.state方法修改组件的内部状态触发组件的重新渲染。对于视图View来说,要想修改Store的状态则需要调用Action.js中的动作构造函数,动作函数根据参数创建Action对象并将其派发。
通过创建Action、Store、Dispatcher以及View我们就实现这种Flux“单向数据流”的状态数据管理方式,杜绝了像MVC框架中View和Model直接通讯的情况。在Flux框架下,用户的操作等行为调用由Action.js维护的动作构造函数,构造函数根据ActionType.js描叙的动作类型创建对应的Action并使用全局唯一的Dispatcher将其派发给所有已经在Dispatcher上注册的Store的回调函数,Store根据对应的动作类型修改状态值。而由于组件在初始化的时候已经添加了Store的监听函数,组件的State已经成为了Store中某个状态的映射;当Store改变的时候将出发组件State的修改进而触发组件的重新渲染。新渲染的组件又能响应各种动作触发不同的动作构造函数完成新一轮的交互,这样我们就使用了“单向数据流”的形式将视图与数据的业务逻辑隔离开了。
1、Flux中的Store之间存在依赖关系
Flux中允许多Store,多个Store各自维护着渲染一部分组件的状态数据。但无法避免的多个Store之间可能会存在或多或少的依赖关系,某一个Store的状态数据需要根据另一个Store先更新后再计算得到。虽然Flux中提供了waitFor函数可以等待另一个Store注册在Dispatcher上的回调函数执行完成,但当依赖关系复杂的时候就很容易出错了。
2、Flux中的Store混杂了逻辑和状态
Store的定义类似于面向对象思想中对象的定义,包含了状态数据和状态数据改变的业务逻辑。在上面数据单向流动的过程中,我们仅仅只是修改了Store中的状态数据;如果修改了Store的业务逻辑,则需要销毁当前的Store对象并重新创建新逻辑的Store,这种方法将无法保存当前Store最新的状态数据,无法实现像热加载这类型的功能。
如果把Flux看作是Web应用中状态数据管理的一个框架理念的话,则Redux是Flux的一个具体的实现。其中,Redux名字的由来就是Reducer+Flux的组合。
在Redux中,Redux用一个单独的Store对象保存这一整个应用的状态,这个对象不能直接被改变。当一些数据变化了,通过Action和Reducer一个新的对象就会被创建。
1、唯一数据源
在Redux应用中只维护了唯一一个数据源Store,所有组件的数据源都是这个Store上的状态。避免了在Flux应用中多个Store之间互相依赖的问题并消除了数据冗余的问题。
2、保持状态只读
在Redux中,如果想要修改组件状态达到驱动用户界面重新渲染的目的不是通过this.setState去修改组件的State状态而是创建一个新的状态对象返回给Redux,由Redux来完成新状态的渲染。
3、数据改变只能通过纯函数来完成
这里所说的纯函数值得就是Reducer,规约函数Reducer接收两个参数分别是当前的状态和接收到的动作action对象。Reducer根据当前的State和动作类型Action产生一个新的State对象返回。需要注意的是在这里,Reducer是一个纯函数;也就是说Reducer的输出完全是由当前State和接收到的动作action决定,并且只是返回一个新的对象而没有产生类似修改State的副作用。
在Redux中仅仅维护了一个状态管理Store,不需要像Flux中一样单独有一个Dispatcher对象来派发动作action给所有Store绑定的回调函数;在Redux中Dispatcher对象简化成Store的一个dispatch方法,用于将动作action派发给唯一的Store。将Flux中Store的状态存储和计算状态功能分离开,Store专门做数据存储而Reducer专门做状态计算。
1、创建Action
export const Action= (参数) => {
return {
type: ActionTypes.动作类型,
参数名: 参数
};
};
ActionType的定义和Flux没有区别,都是用字符串表示一个特定动作的类型。而在Action.js中,定义的不再是构造一个Action动作并将其派发出去了而是简单地构造一个动作对象并返回。
2、创建Store
import {createStore} from 'redux';
import reducer from './Reducer.js';
const init = {
初始参数名:参数值
};
const store = createStore(reducer, init);
export default store;
使用Redux的createStore方法创建全局唯一的Store对象,可以带三个参数按顺序分别用于规约的Reducer、初始值和Store enhancer增强器。
3、创建Reducer
import * as ActionTypes from './ActionTypes.js';
export default (state, action) => {
const {参数名} = action;
switch (action.type) {
case ActionTypes.ActionTypesA:
return {...state, [参数名]: OPR(state[参数名]) };
case ActionTypes.ActionTypesB:
return {...state, [参数名]: OPR(state[参数名])};
default:
return state
}
}
Redux中的Reducer类似于Flux中的回调函数,不同的是在Reducer中多了一个传入参数State表示当前状态,Reducer返回一个更新后的State状态对象。在Reducer中严格杜绝直接修改传入参数State的行为,Reducer应该是一个纯函数不产生任何副作用。
4、修改View
class component extends Component {
constructor(props){
super(props);
....
this.state = this.getState( );
}
getState( ){
return {
value: store.getState()[参数名]
}
}
响应用户交互的函数( ){
store.dispatch(Actions.[Action.js中动作的构造函数](参数));
}
componentDidMount() {
store.subscribe(回调函数);
}
componentWillUnmount() {
store.unsubscribe(回调函数);
}
}
每次Store更新时都会触发View获取最新的状态值,因此我们将获取Store中最新的状态信息抽出一个单独的函数getState处理。使用Store的subscribe和unsubscribe方法在组件挂载和取消挂载时绑定和解绑回调函数,回调函数将会重新获取Store中最新的状态值并且使用this.setState修改组件内部的状态值触发组件渲染。当View需要改变Store值的时候,使用Store的dispatch方法派发一个特定ActionType的动作Action。
使用Redux对应用中的状态进行管理,首先使用Redux中Store提供的subscribe和unsubscribe方法在组件的生命周期内监听Store的更新并及时将Store中的最新状态通过this.setState方法渲染在组件中。View要修改Store中的状态,则使用Store的Dispatch方法派发一个Action.js中定义的动作构造函数。Store由Redux来维护,Redux负责存储数据最新的状态并将当前状态和动作传递给Reducer进行状态计算,计算后返回更新后的状态又交由Store来存储。Store的更新将触发View的回调函数重新渲染组件。这样就实现了使用“单向数据流”并将存储状态数据和状态计算分离达到提供可预测化状态管理的目的。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。