前言
这周突然想学习一下状态管理的写法。看看业界是怎么实现的,之前使用过 redux,那就先从 redux 下手吧,但是,一上来就看最新版本的代码,不太适合新手学习,一方面最新版本已经发展n多年了,功能已经非常完善(代码多难懂),另一方面直接看最新的不了解这个工具是怎么设计出来的。于是就打算学习最早的发布版本 v0.2.1
先来说下我认识的一般的状态管理的基本路子:
全局只存在 唯一state ,而前端不直接改变 state,而是通过 action 去改变 state
HelloWorld
一个计数器的栗子,目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 counter ├── App.js ├── Counter.js ├── actions │ ├── CounterActions.js │ └── index.js ├── constants │ └── ActionTypes.js ├── dispatcher.js └── stores ├── CounterStore.js └── index.js
actions
函数,返回一个带 type 的对象,或者返回一个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; export function increment() { return { type: INCREMENT_COUNTER }; } export function incrementAsync() { return dispatch => { setTimeout(() => { dispatch(increment()); }, 1000); }; } export function decrement() { return { type: DECREMENT_COUNTER }; }
store
返回一个函数,参数 state 和 action,当 state 为空时返回初始值,表示初始化。根据 action 的 type 值,进行相应的做法,返回一个新的 state。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; const initialState = { counter: 0 }; function incremenent({ counter }) { return { counter: counter + 1 }; } function decremenent({ counter }) { return { counter: counter - 1 }; } export default function CounterStore(state, action) { if (!state) { return initialState; } switch (action.type) { case INCREMENT_COUNTER: return incremenent(state, action); case DECREMENT_COUNTER: return decremenent(state, action); default: return state; } }
入口 App.js 你会发现 @provides(dispatcher) 这个奇怪的东西,在 React 里面还经常出现,装饰器。
1 2 3 4 5 6 7 8 9 10 11 12 13 import React, { Component } from 'react'; import Counter from './Counter'; import { provides } from 'redux'; import dispatcher from './dispatcher'; @provides(dispatcher) export default class App extends Component { render() { return ( <Counter /> ); } }
Couter.js,同样,也出现 performs(方法),observes(观察者)等关键字。使用 state 直接使用 this.props 解构赋值即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import React from 'react'; import { performs, observes } from 'redux'; @performs('increment', 'decrement') @observes('CounterStore') export default class Counter { render() { const { increment, decrement } = this.props; return ( <p> Clicked: {this.props.counter} times {' '} <button onClick={() => increment()}>+</button> {' '} <button onClick={() => decrement()}>-</button> </p> ); } }
这些关键字是早起 Redux 状态管理的关键,现在的版本应该已经不使用这种方式了。
解析
dispatcher
通过 provides 将 dispatcher 注入到 App 中,其中,dispatcher 是通过 createDispatcher 创建,并调用了 dispatcher.receive(stores, actions) 进行绑定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import * as stores from './stores/index'; import * as actions from './actions/index'; import { createDispatcher } from 'redux'; const dispatcher = module.hot && module.hot.data && module.hot.data.dispatcher || createDispatcher(); dispatcher.receive(stores, actions); module.hot.dispose(data => { data.dispatcher = dispatcher; }); export default dispatcher;
receive 方法,actionCreator 将 action 进行封装,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Provide a way to receive new stores and actions function receive(nextStores, nextActionCreators) { stores = nextStores; actionCreators = mapValues(nextActionCreators, wrapActionCreator); // Merge the observers observers = mapValues(stores, (store, key) => observers[key] || [] ); // Dispatch to initialize stores if (currentTransaction) { updateState(committedState); currentTransaction.forEach(dispatch); } else { dispatch(BOOTSTRAP_STORE); } }
action 进行转化返回一个 dispatchAction 函数,如果 action 为函数,则先执行函数,把 dispatchInTransaction 作为参数传入,这样可以在 action 内部使用该函数了,否则使用 dispatchInTransaction 函数调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 // Bind action creator to the dispatcher function wrapActionCreator(actionCreator) { return function dispatchAction(...args) { const action = actionCreator(...args); if (typeof action === 'function') { // Async action creator action(dispatchInTransaction); } else { // Sync action creator dispatchInTransaction(action); } }; }
dispatchInTransaction ,执行 dispatch ,计算 nextState,执行 updateState 更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // Dispatch in the context of current transaction function dispatchInTransaction(action) { if (currentTransaction) { currentTransaction.push(action); } dispatch(action); } // Reassign the current state on each dispatch function dispatch(action) { if (typeof action.type !== 'string') { throw new Error('Action type must be a string.'); } const nextState = computeNextState(currentState, action); updateState(nextState); }
获取 store,也就是 CounterStore,把参数传入,获取新的 state
1 2 3 4 5 6 // To compute the next state, combine the next states of every store function computeNextState(state, action) { return mapValues(stores, (store, key) => store(state[key], action) ); }
updateState 实现,计算变化的 changedKeys,执行 emitChange 进行更新。
1 2 3 4 5 6 7 8 9 10 11 12 // Update state and emit change if needed function updateState(nextState) { // Swap the state const previousState = currentState; currentState = nextState; // Notify the observers const changedKeys = Object.keys(currentState).filter(key => currentState[key] !== previousState[key] ); emitChange(changedKeys); }
emitChange,获取需要通知的 observers,调用通知函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Notify observers about the changed stores function emitChange(changedKeys) { if (!changedKeys.length) { return; } // Gather the affected observers const notifyObservers = []; changedKeys.forEach(key => { observers[key].forEach(o => { if (notifyObservers.indexOf(o) === -1) { notifyObservers.push(o); } }); }); // Emit change notifyObservers.forEach(o => o()); }
这里可能有点疑问,obersevers 是什么,从哪来?往下看~
observes.js
将 组件进行装饰,构造函数中有一个
this.unobserve = this.context.observeStores(storeKeys, this.handleChange);
context 就是 dispatcher,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import React, { Component, PropTypes } from 'react'; import pick from 'lodash/object/pick'; import identity from 'lodash/utility/identity'; const contextTypes = { observeStores: PropTypes.func.isRequired }; export default function connect(...storeKeys) { let mapState = identity; // Last argument may be a custom mapState function const lastIndex = storeKeys.length - 1; if (typeof storeKeys[lastIndex] === 'function') { [mapState] = storeKeys.splice(lastIndex, 1); } return function (DecoratedComponent) { const wrappedDisplayName = DecoratedComponent.displayName || DecoratedComponent.name || 'Component'; return class extends Component { static displayName = `ReduxObserves(${wrappedDisplayName})`; static contextTypes = contextTypes; constructor(props, context) { super(props, context); this.handleChange = this.handleChange.bind(this); this.unobserve = this.context.observeStores(storeKeys, this.handleChange); } .... componentWillUnmount() { this.unobserve(); } render() { return ( <DecoratedComponent {...this.props} {...this.state} /> ); } }; }; }
dispatcher observeStores 方法,将需要监听的组件传入,以及 onChange 函数,作为回调使用。最后返回一个函数,移除监听,这个也太妙了吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // Provide subscription and unsubscription function observeStores(observedKeys, onChange) { // Emit the state update function handleChange() { onChange(currentState); } // Synchronously emit the initial value handleChange(); // Register the observer for each relevant key observedKeys.forEach(key => observers[key].push(handleChange) ); // Let it unregister when the time comes return () => { observedKeys.forEach(key => { const index = observers[key].indexOf(handleChange); observers[key].splice(index, 1); }); }; }
当计算好 nextState 后,就会调用 observe 的 onChange 方法, onChange 方法也就是 装饰器里面的方法。最后调用自身的 updateState,使用 setState 进行组件更新。而这些 state 作为 props 传入了我们自己的组件,也就可以通过 this.props 拿到。完美~~~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 handleChange(stateFromStores) { this.currentStateFromStores = pick(stateFromStores, storeKeys); this.updateState(stateFromStores, this.props); } componentWillReceiveProps(nextProps) { this.updateState(this.currentStateFromStores, nextProps); } updateState(stateFromStores, props) { if (storeKeys.length === 1) { // Just give it the particular store state for convenience stateFromStores = stateFromStores[storeKeys[0]]; } const state = mapState(stateFromStores, props); if (this.state) { this.setState(state); } else { this.state = state; } }
action 绑定到组件,可以通过 this.props ,通过 this.context.getActions() 拿到 actions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import React, { Component, PropTypes } from 'react'; import pick from 'lodash/object/pick'; import identity from 'lodash/utility/identity'; const contextTypes = { getActions: PropTypes.func.isRequired }; export default function performs(...actionKeys) { let mapActions = identity; // Last argument may be a custom mapState function const lastIndex = actionKeys.length - 1; if (typeof actionKeys[lastIndex] === 'function') { [mapActions] = actionKeys.splice(lastIndex, 1); } return function (DecoratedComponent) { const wrappedDisplayName = DecoratedComponent.displayName || DecoratedComponent.name || 'Component'; return class extends Component { static displayName = `ReduxPerforms(${wrappedDisplayName})`; static contextTypes = contextTypes; constructor(props, context) { super(props, context); this.updateActions(props); } componentWillReceiveProps(nextProps) { this.updateActions(nextProps); } updateActions(props) { this.actions = mapActions( pick(this.context.getActions(), actionKeys), props ); } render() { return ( <DecoratedComponent {...this.props} {...this.actions} /> ); } }; }; }
到这里就差不多了~
额外收获
Lodash
pick
1 2 3 4 5 var object = { 'user': 'fred', 'age': 40 }; _.pick(object, 'user'); // => { 'user': 'fred' } _.pick(object, _.isString); // => { 'user': 'fred' }
identity
1 2 3 function identity(value) { return value; }
mapValues
1 2 3 4 _.mapValues({ 'a': 1, 'b': 2 }, function(n) { return n * 3; }); // => { 'a': 3, 'b': 6 }
最后
麻雀虽小,却能看透精髓~