Redux
Redux 是為了處理應(yīng)用復(fù)雜狀態(tài)流而設(shè)計的狀態(tài)管理庫囚聚,它吸收了 Flux 架構(gòu)和函數(shù)式編程的優(yōu)秀思想,提出了應(yīng)用分層設(shè)計的解決方法蓖谢;
Redux 基本架構(gòu)
Redux 的將應(yīng)用分為 Actions捂蕴、State 和 View 三層譬涡;
Actions
Actions 描述用戶操作的基本信息,包括操作類型和所需傳遞的數(shù)據(jù)啥辨;
在代碼層面看涡匀,一個 action 就是一個對象,實際編碼過程中會將 action 設(shè)計為 action creator溉知,里面直接封裝 action.type陨瘩,只需要傳遞數(shù)據(jù)。
// addTodoAction
export var addToDo = payload => ({
type: 'ADD_TODO',
payload,
});
Reducers
Reducer 是根據(jù) action 類型生成新的 state 的函數(shù)着倾。這里要求 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)]
數(shù)據(jù)流向
這里以 React 這一 UI 框架為例脸侥,講解一下 React + Redux 的基本數(shù)據(jù)流向;
- 首先盈厘,UI 從 Store 里面獲取 State睁枕,這里通過 react-redux 的 Provider 組件實現(xiàn) store 的注入;
- UI 發(fā)生交互后沸手,會調(diào)用 dispatch(action(payload)) 方法外遇,dispatch 方法默認掛載在 store 上;
- dispatch 觸發(fā)后契吉,會調(diào)用 rootReducer()跳仿,rootReducer 會根據(jù)之前的 state 和 action 計算新的 state;
- 新的 state 會重新從根組件傳遞下去捐晶,如果 state 發(fā)生變化菲语,則 re-rerender 對應(yīng)的組件,從而實現(xiàn)視圖的更新惑灵;
源碼解析
源碼以 redux 和 react-redux 為內(nèi)容山上,為了避免干擾,將會在源碼基礎(chǔ)上去除本身邊界條件英支、狀態(tài)鎖以及干擾分析部分的代碼胶哲,并進行簡化;
combineReducer
combineReducer 是一個高階函數(shù)潭辈, 作用就是將所有的子 reducer 合并為一個根 reducer鸯屿,當(dāng)調(diào)用 rootReducer 時,內(nèi)部會遍歷所有子 reducer把敢,然后根據(jù)每個子 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 等方法的倉庫,提供 UI 組件數(shù)據(jù)和發(fā)射特定類型 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ù)傳遞機制,通過 context 對象將 store 和 state 綁定到各個組件上割择;
這里在源碼的 Provider 組件在實現(xià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 組件是一個高階組件,內(nèi)部通過 useContext 去消費 Provider 提供的 context荔泳,將 context.store 和 context.store.getState() 以 props 的方式傳遞給 connect 的組件蕉饼,并監(jiān)聽 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;
}
}