Redux
Redux 是為了處理應(yīng)用復(fù)雜狀態(tài)流而設(shè)計(jì)的狀態(tài)管理庫(kù),它吸收了 Flux 架構(gòu)和函數(shù)式編程的優(yōu)秀思想,提出了應(yīng)用分層設(shè)計(jì)的解決方法;
Redux 基本架構(gòu)

Redux 的將應(yīng)用分為 Actions、State 和 View 三層;
Actions
Actions 描述用戶(hù)操作的基本信息,包括操作類(lèi)型和所需傳遞的數(shù)據(jù);
在代碼層面看,一個(gè) action 就是一個(gè)對(duì)象,實(shí)際編碼過(guò)程中會(huì)將 action 設(shè)計(jì)為 action creator,里面直接封裝 action.type,只需要傳遞數(shù)據(jù)。
// addTodoAction
export var addToDo = payload => ({
type: 'ADD_TODO',
payload,
});
Reducers
Reducer 是根據(jù) action 類(lèi)型生成新的 state 的函數(shù)。這里要求 state 是個(gè) Immutable 對(duì)象,因?yàn)闉榱私档托阅荛_(kāi)銷(xiāo),新舊 state 將采用淺比較,使用 Immutable 對(duì)象可以很好匹配這一適用場(chǎng)景。
// 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 是存儲(chǔ)整個(gè) state 樹(shù)的倉(cāng)庫(kù),實(shí)際上就是一個(gè)對(duì)象,里面部署了 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 就是視圖層,可以對(duì)用戶(hù)交互給予反饋;
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)]
數(shù)據(jù)流向
這里以 React 這一 UI 框架為例,講解一下 React + Redux 的基本數(shù)據(jù)流向;
- 首先,UI 從 Store 里面獲取 State,這里通過(guò) react-redux 的 Provider 組件實(shí)現(xiàn) store 的注入;
- UI 發(fā)生交互后,會(huì)調(diào)用 dispatch(action(payload)) 方法,dispatch 方法默認(rèn)掛載在 store 上;
- dispatch 觸發(fā)后,會(huì)調(diào)用 rootReducer(),rootReducer 會(huì)根據(jù)之前的 state 和 action 計(jì)算新的 state;
- 新的 state 會(huì)重新從根組件傳遞下去,如果 state 發(fā)生變化,則 re-rerender 對(duì)應(yīng)的組件,從而實(shí)現(xiàn)視圖的更新;
源碼解析
源碼以 redux 和 react-redux 為內(nèi)容,為了避免干擾,將會(huì)在源碼基礎(chǔ)上去除本身邊界條件、狀態(tài)鎖以及干擾分析部分的代碼,并進(jìn)行簡(jiǎn)化;
combineReducer
combineReducer 是一個(gè)高階函數(shù), 作用就是將所有的子 reducer 合并為一個(gè)根 reducer,當(dāng)調(diào)用 rootReducer 時(shí),內(nèi)部會(huì)遍歷所有子 reducer,然后根據(jù)每個(gè)子 state 是否發(fā)生改變,返回新舊的 根 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,根據(jù)前后 state 是否發(fā)生變化返回新舊 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 等方法的倉(cāng)庫(kù),提供 UI 組件數(shù)據(jù)和發(fā)射特定類(lèi)型 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();
}
}
// 先調(diào)用 dispatch,初始化 state
dispatch({ type: `@@redux/INIT${/* #__PURE__ */ randomString()}` });
const store = ({
dispatch,
subscribe,
getState,
});
return store;
}
Provider
react-redux 的 Provider 組件采用 React 的 Context 數(shù)據(jù)傳遞機(jī)制,通過(guò) context 對(duì)象將 store 和 state 綁定到各個(gè)組件上;
這里在源碼的 Provider 組件在實(shí)現(xiàn)上進(jìn)行一定的簡(jiǎn)化,分離出核心代碼:
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 組件是一個(gè)高階組件,內(nèi)部通過(guò) useContext 去消費(fèi) Provider 提供的 context,將 context.store 和 context.store.getState() 以 props 的方式傳遞給 connect 的組件,并監(jiān)聽(tīng) 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;
// 實(shí)際的框架,通過(guò) mapStateToProps 將根 state 的特定子 state 合并到 props
var state = store.getState();
return <WrappedComponent {...store, ...state} />
}
return Connect;
}
}