Redux
Redux 是为了处理应用复杂状态流而设计的状态管理库,它吸收了 Flux 架构和函数式编程的优秀思想,提出了应用分层设计的解决方法;
Redux 基本架构
Redux 的将应用分为 Actions、State 和 View 三层;
Actions
Actions 描述用户操作的基本信息,包括操作类型和所需传递的数据;
在代码层面看,一个 action 就是一个对象,实际编码过程中会将 action 设计为 action creator,里面直接封装 action.type,只需要传递数据。
// addTodoAction
export var addToDo = payload => ({
type: 'ADD_TODO',
payload,
});
Reducers
Reducer 是根据 action 类型生成新的 state 的函数。这里要求 state 是个 Immutable 对象,因为为了降低性能开销,新旧 state 将采用浅比较,使用 Immutable 对象可以很好匹配这一适用场景。
// todosReducer
var todos = (state = [], action) => {
switch(action.type) {
case 'ADD_TODO':
return [...state, {id: action.id, payload: action.payload}];
default:
return state;
}
}
export default todos;
Store
Store 是存储整个 state 树的仓库,实际上就是一个对象,里面部署了 dispatch() 和 getState() 等主要方法。
import { createStore, combineReducers } from 'redux';
import todosReducer from 'reducers/todosReducer';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
var rootReducer = combineReducers({
todos: todosReducer,
});
var store = createStore(rootReducer);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
View
View 就是视图层,可以对用户交互给予反馈;
import React from 'react';
import {connect} from 'react-redux';
import * as TodoAction from 'actions/todoAction';
var AddToDo = props => {
const {
todos,
dispatch,
} = props;
return <div>
<section className='todo-list'>
{
todos.map(todo => <p key={todo.id}>{todo.name}</p>)
}
</section>
<button onClick={() => dispatch(TodoAction({name: 'hello world'})}>add to do</button>
</div>;
}
var mapStateToProps = state => ({
todos: state.todos,
});
export default connect(mapStateToProps)(AddToDo);
[图片上传失败...(image-1b3958-1616590677926)]
数据流向
这里以 React 这一 UI 框架为例,讲解一下 React + Redux 的基本数据流向;
- 首先,UI 从 Store 里面获取 State,这里通过 react-redux 的 Provider 组件实现 store 的注入;
- UI 发生交互后,会调用 dispatch(action(payload)) 方法,dispatch 方法默认挂载在 store 上;
- dispatch 触发后,会调用 rootReducer(),rootReducer 会根据之前的 state 和 action 计算新的 state;
- 新的 state 会重新从根组件传递下去,如果 state 发生变化,则 re-rerender 对应的组件,从而实现视图的更新;
源码解析
源码以 redux 和 react-redux 为内容,为了避免干扰,将会在源码基础上去除本身边界条件、状态锁以及干扰分析部分的代码,并进行简化;
combineReducer
combineReducer 是一个高阶函数, 作用就是将所有的子 reducer 合并为一个根 reducer,当调用 rootReducer 时,内部会遍历所有子 reducer,然后根据每个子 state 是否发生改变,返回新旧的 根 state;
function combineReducer(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers);
return combine(state, action) {
// 遍历所有的 reducer,根据前后 state 是否发生变化返回新旧 state
var hasChanged = false;
var nextState = {};
for (var j = 0; j < finalReducerKeys.length; j++) {
var key = finalReducerKeys[j];
var reducer = finalReducers[key];
var prevStateForKey = state[key];
var nextStateForKey = reducer(prevStateForKey, action);
nextState[key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== prevStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? newState : state;
}
}
createStore
createStore 主要是封装 state、dispatch 和 subscribe 等方法的仓库,提供 UI 组件数据和发射特定类型 action;
function createStore(reducer, prelaodedState, enhancer) {
var isDispatching = false;
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
function getState() {
return currentState;
}
function dispatch(action) {
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = (currentListeners = nextListeners);
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) return;
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
}
}
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
// 先调用 dispatch,初始化 state
dispatch({ type: `@@redux/INIT${/* #__PURE__ */ randomString()}` });
const store = ({
dispatch,
subscribe,
getState,
});
return store;
}
Provider
react-redux 的 Provider 组件采用 React 的 Context 数据传递机制,通过 context 对象将 store 和 state 绑定到各个组件上;
这里在源码的 Provider 组件在实现上进行一定的简化,分离出核心代码:
function Provider({ store, context, children }) {
var Context = Context || React.createContext(null);
var contextValue = useMemo(() => {
return {
store,
};
}, [store]);
return <Context.Provider value={contextValue}>
{children}
</Context.Provider>
}
connect
react-redux 的 connect 组件是一个高阶组件,内部通过 useContext 去消费 Provider 提供的 context,将 context.store 和 context.store.getState() 以 props 的方式传递给 connect 的组件,并监听 context.store 的变化;
function createConnect({
connectHOC = connectAdvanced,
selectorFactory,
}) {
return function connect({
mapStateToProps,
mapDispatchToProps,
mergeProps,
}) {
return connectHOC(selectorFactory, {
mapStateToProps,
mapDispatchToProps,
});
}
}
function connectAdvanced(selectorFactory, {
context = React.createContext(null),
...connectOptions,
}) {
// 这里 context 是从 parent Provider 给的
var Context = context;
return function wrapWithConnect(WrappedComponent) {
function Connect(props) {
var contextValue = useContext(Context);
var store = props.store ? props.store : contextValue.store;
// 实际的框架,通过 mapStateToProps 将根 state 的特定子 state 合并到 props
var state = store.getState();
return <WrappedComponent {...store, ...state} />
}
return Connect;
}
}