Flux 使用整理
注意:代码中用到了 Flow | Immutable ,可能会影响阅读,请忽略相关代码
说明
Flux 是 Facebook 开发,用来帮助 React 之类的应用做状态管理的模式,它利用单向数据流,来梳理复杂的状态
一个 Flux 应用由三个部分组成
- dispatcher 负责分发事件
- store 负责保存数据,并且响应事件更新数据
- view 负责订阅 store 中的数据,并且渲染页面
基本流程
- 用户触发视图事件
- 事件中通过调用(直接或者间接) dispatcher 实例的 dispatch 方法触发一个 action 事件
- action 的触发会导致 store 中进行数据变更
- 监听 store 中数据的 view 获得更新
原则
- 数据在 Flux 应用中的流向是单一方向的 Action -> Dispatcher -> Store -> View
- Dispatcher 是一个中央集线器,管理所有Flux 应用中的数据流,它只是回调到 store 中的注册表,用来分配 action 到 store,当一个 action 被 dispatch 的时候,所有注册过这个 Dispatcher 的 store 都会收到通知,判断 action.type 之后触发相应的更新
- Store 中包含应用的状态和逻辑,类似于传统 MVC 架构中的 Model ,Store 将自己注册到 Dispatcher 并提供回调,在 Store 的注册回调中,一个基于action.type 的 switch 语句用于解释分析,并为 Store 内部的方法提供适当的钩子,钩子中对当前状态更改,更改后会触发广播事件,这个时候视图查询到状态更改,更新自己
flux/utils
flux/utils 是一个基础的,帮助实现 Flux 的基本实用程序类,提供一下几个主要的类
- Store
- ReduceStore
- Container
结合 flux/utils 的 react 项目最佳实践(代码结构划分)
- Stores
- 可以借助 flux/utils 的 ReduceStore 实现用于缓存数据
- 对外提供可访问数据的方法
- 响应 dispatcher 分发的 action
- 只有在 dispatch 可以触发 data 的更新
- 按照页面分离,也就是每个页面创建一个 Store
- Actions
- 有 action creater 创建,描述了触发 action 的内容
- Containers
- 可以借助 flux/utils 提供的 Container.create() 方法辅助生成一个 react 容器组件
- 主要的功能是从 store 中获取 data 并保存为 这个容器组件的 state
- 容器组件不进行 UI 的定义
- Views
- 普通的 react 组件,用来展示 UI
- 所有需要用到的 store 中数据都通过容器组件传入 props
基本实现流程
1. 定义全局唯一的 Dispatcher 实例 /src/store/Dispatcher.js
Dispatcher 也可以定义多个,这样要触发哪个 Store 的更新,就找到注册 Store 时传入的那个 Dispatcher,来触发 dispatch(action)
设置多个 Store 共同绑定同一个 Dispatcher 时,要注意 Store 中定义的 action.type 不要重复
// @flow
/*
* 这里将 Dispatcher 设置为全局只有一个,要确保不同的 Store 中响应的 action.type 不能重复
* */
// 创建全局唯一 Dispatcher
import { Dispatcher } from 'flux';
const instance: Dispatcher = new Dispatcher();
// 这样方便直接引用 dispatch
export const dispatch = instance.dispatch.bind(instance);
export default instance;
2. 创建某个页面的 Store 数据 /src/store/{pageName}/store.js
- 创建一个继承 ReduceStore 的 pageStore 类, 最后输出他的实例, ReduceStore 是 ‘flux/utils’ 提供的
- pageStore 中有两个需要重写的方法 getInitialState | reduce
- 定义 getInitialState 方法返回一个初始化的 state
- 定义 reduce 方法接受旧的 state 和 action ,根据 action.type 触发不同的更改,返回新的 state
- 与 Redux 不同, Flux 官方建议在 store 中将 state 的处理逻辑提取出一个内部方法
// @flow
/*
* login 页面 store
* */
import {ReduceStore} from 'flux/utils';
import {Map, fromJS} from 'immutable';
import Dispatcher from '../Dispatcher';
import {CHANGE_INPUT_DATA, INPUT_FEEDBACK} from './constants';
type State = Map<string, any>
const initialData = fromJS({
username: {
value: '',
},
password: {
value: '',
},
loginSuccess: false
});
class loginStore extends ReduceStore<State> {
state: State;
// 初始化 state
getInitialState(): State {
return initialData.merge({})
}
/*
action 的处理部分,每次 dispatch 都会他通知到这个函数,然后进行 action.type 的对比
*/
reduce(state: State, action: Object): State {
switch (action.type) {
case CHANGE_INPUT_DATA:
return state.mergeDeep(action.payload);
case INPUT_FEEDBACK:
return this._inputFeedBack(state, action);
default: return state;
}
}
// 将 state 的更新逻辑提取出一个方法
_inputFeedBack(state: State, action: Object) {
const success = action.payload.success;
if (success) {
const newState = state.merge(initialData);
return newState.set('loginSuccess', success);
}
return state.set('loginSuccess', success);
}
}
// 输出的是一个 Store 的实例,这个实例中绑定了指定的 Dispatcher
export default new loginStore(Dispatcher);
3. 创建 对应这个页面的 actions-creator 文件 /src/store/{pageName}/actions.js
- 在这个文件中引入 dispatch, dispatch 是 Dispatcher 的方法,用来分发 action
- 定义一个 action creator 函数,这个函数中可以通过 dispatch 一个 含有 type 属性的对象来分发 action
- 页面用到哪个 Store 就使用 Store 对应的 Dispatcher
import {dispatch} from '../Dispatcher';
export function changeInputData(newInputData: InputData) {
return dispatch({
type: CHANGE_INPUT_DATA,
payload: newInputData
})
}
4. 提取 action.type 为常量
由于 action.type 在 action 中和 store 中都会用到,一次需要将其提取出为常量,防止拼写错误引起 BUG 提取到 /src/store/{pageName}/constants.js
5. 组件中使用
- 定义一个容器组件 Page 在 /src/container/{pageName}.js
- 这个组件被 Container.create(Page) 高阶组件包裹后,返回新的组件 Container 是 ‘flux/utils’ 提供的高阶组件,用于将 flux 的 store 与组建立联系
- 在我们自定义的组件 Page 中要定义两个固定的静态方法 getStore | calculateState,(高阶组件中会用到
- getStore 用来将我们创建的 store 与组件建立联系,他需要返回一个包含多个 store 的数组
- calculateState 会返回一个对象,对象中的属性值对应了 从 store 中获取的 状态,这属性值会作为 组件的 this.state 的属性值使用
- 我们之前定义的 actions 可以在这里引入
最后 引入的 action 与 state 又可以传给子组件作为 props 使用
// @flow /* + Login 页面 使用 antd Form + */ import * as React from 'react'; import {Container} from 'flux/utils' import LoginCom from '../components/Login/index'; import {changeInputData, postDataToLogin} from '../store/login/actions'; import loginStore from '../store/login/store'; type State = { loginState: Map<string, any> } class Login extends React.Component<any, State> { static getStores() { return [loginStore] } static calculateState(prevState: State): State { return { loginState: loginStore.getState() } } render(): React.Element<any> { return ( <LoginCom loginState={this.state.loginState} changeInputData={changeInputData} postDataToLogin={postDataToLogin} /> ) } } export default Container.create(Login);