前言
这周突然想学习一下状态管理的写法。看看业界是怎么实现的,之前使用过 redux,那就先从 redux 下手吧,但是,一上来就看最新版本的代码,不太适合新手学习,一方面最新版本已经发展n多年了,功能已经非常完善(代码多难懂),另一方面直接看最新的不了解这个工具是怎么设计出来的。于是就打算学习最早的发布版本 v0.2.1
先来说下我认识的一般的状态管理的基本路子:
全局只存在 唯一state,而前端不直接改变 state,而是通过 action 去改变 state
HelloWorld
一个计数器的栗子,目录结构如下:
counter
├── App.js
├── Counter.js
├── actions
│ ├── CounterActions.js
│ └── index.js
├── constants
│ └── ActionTypes.js
├── dispatcher.js
└── stores
├── CounterStore.js
└── index.js
actions
函数,返回一个带 type 的对象,或者返回一个函数
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。
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 里面还经常出现,装饰器。
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 解构赋值即可。
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) 进行绑定。
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 进行封装,
// 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 函数调用。
// 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 更新。
// 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
// 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 进行更新。
// 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,调用通知函数。
// 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,
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 函数,作为回调使用。最后返回一个函数,移除监听,这个也太妙了吧。
// 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 拿到。完美~~~
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;
}
}
performs 组件
action 绑定到组件,可以通过 this.props ,通过 this.context.getActions() 拿到 actions
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
var object = { 'user': 'fred', 'age': 40 }; _.pick(object, 'user'); // => { 'user': 'fred' } _.pick(object, _.isString); // => { 'user': 'fred' }
- identity
function identity(value) { return value; }
- mapValues
_.mapValues({ 'a': 1, 'b': 2 }, function(n) { return n * 3; }); // => { 'a': 3, 'b': 6 }
最后
麻雀虽小,却能看透精髓~