1. Redux 簡(jiǎn)單介紹
Redux 是 JavaScript 應(yīng)用狀態(tài)容器,提供可預(yù)測(cè)化的狀態(tài)管理。
可以讓你構(gòu)建一致化的應(yīng)用,運(yùn)行于不同的環(huán)境(客戶(hù)端、服務(wù)器、原生應(yīng)用),并且易于測(cè)試。不僅于此,它還提供 超爽的開(kāi)發(fā)體驗(yàn),比如有一個(gè)時(shí)間旅行調(diào)試器可以編輯后實(shí)時(shí)預(yù)覽。
2. 為什么要用 redux,什么情況下需要用到 redux
曾經(jīng)有人說(shuō)過(guò)這樣一句話(huà)。
"如果你不知道是否需要 Redux,那就是不需要它。"
Redux 的創(chuàng)造者 Dan Abramov 又補(bǔ)充了一句。
"只有遇到 React 實(shí)在解決不了的問(wèn)題,你才需要 Redux 。"
如果一個(gè)應(yīng)用不是很復(fù)雜,可以通過(guò) react 的內(nèi)部 state 就滿(mǎn)足對(duì)數(shù)據(jù)的管理,那就沒(méi)必要使用 redux。
因?yàn)槭褂?redux 每次寫(xiě)代碼會(huì)額外增加代碼的編寫(xiě)以及可能需要多創(chuàng)建多幾個(gè)文件。
那什么時(shí)候適合用 redux?
- 組件樹(shù)龐大,不同的組件節(jié)點(diǎn)需要共享狀態(tài)
- 某個(gè)組件需要去修改全局的狀態(tài)或修改其他組件的狀態(tài)
- 與后端服務(wù)器有比較多的數(shù)據(jù)交互,并且組件視圖依賴(lài)后端返回的數(shù)據(jù)
使用 redux 帶來(lái)什么好處?
- redux 讓 state 的變化變得可預(yù)測(cè),因?yàn)樾薷?state 只能通過(guò) dispatch 一個(gè) action,這個(gè)過(guò)程是可監(jiān)控的,在開(kāi)發(fā)環(huán)境中,結(jié)合 redux-devtools 還可以實(shí)現(xiàn)時(shí)間旅行、錄制、重放等
- redux 將 state 統(tǒng)一管理,會(huì)使代碼更加有規(guī)律,易于維護(hù)管理。
3. redux 基礎(chǔ)知識(shí)
3.1 redux API
- 頂級(jí)暴露的 API
- Store API
3.1.1 createStore
createStore 用于創(chuàng)建一個(gè) store 實(shí)例,創(chuàng)建時(shí),第一個(gè)參數(shù)會(huì)傳入一個(gè) reducer 函數(shù),store 保存了 state 數(shù)據(jù),傳入的 reducer 函數(shù)定義了修改 state 的規(guī)則。
3.1.2 store.getState
store.getSate() 返回 store 內(nèi)部的 state 數(shù)據(jù),state 數(shù)據(jù)在外部無(wú)法直接訪(fǎng)問(wèn),必須通過(guò) getState 方法獲取
3.1.3 store.dispatch
store.dispatch() 派發(fā)一個(gè) action,action 是一個(gè)對(duì)象,一般至少包含一個(gè) type 字段,例如:{ type: 'add' },除了 type 字段外,也可以包含其他數(shù)據(jù),type 用于匹配 reducer 中的修改規(guī)則,其他數(shù)據(jù)用于更新 state。
想要修改 state 中的數(shù)據(jù),必須通過(guò) dispatch
3.1.4 store.subscribe
store.subscribe 用于訂閱 store 的數(shù)據(jù)變化,監(jiān)聽(tīng)函數(shù)會(huì)在 dispatch 調(diào)用時(shí)執(zhí)行
3.2 redux flow

redux 與 react 結(jié)合使用的數(shù)據(jù)流:
- 在 react 組件中調(diào)用
dispatch(action),派發(fā)一個(gè)action,action是一個(gè)描述 “發(fā)生了什么” 的普通對(duì)象,例如:{ type: 'add', value: 1 },這個(gè)action 對(duì)象可以理解為 “增加數(shù)值 1” -
store取到action后,調(diào)用構(gòu)建store傳入到reducer函數(shù),并且傳入當(dāng)前的state和action -
reducer取到state和action后,會(huì)通過(guò)action.type匹配到修改state的規(guī)則,然后修改并返回新的state - store 保存新的 state,然后所有訂閱了
store.subscribe()的監(jiān)聽(tīng)器都被調(diào)用,并通過(guò)store.getState()獲取新的 state,重新渲染組件。

4. redux 使用
4.1 reducer 介紹
創(chuàng)建 store 的時(shí)候,需要傳入一個(gè) reducer 函數(shù)。
reducer = (state, action) => {}
Reducer (也稱(chēng)為 reducing function) 函數(shù)接受兩個(gè)參數(shù):
state: 之前累積運(yùn)算的結(jié)果和當(dāng)前被累積的值
action: 描述了發(fā)生什么事情的一個(gè)對(duì)象,一般帶有 type 字段。例如:
{ type: 'ADD' }
返回的是一個(gè)新的累積結(jié)果(state)。
Reducers 指定了應(yīng)用狀態(tài)的變化如何響應(yīng) actions 并發(fā)送到 store 的,記住 actions 只是描述了有事情發(fā)生了這一事實(shí),并沒(méi)有描述應(yīng)用如何更新 state。
reducer 是一個(gè)純函數(shù),即接收的參數(shù)不變的情況下,返回值也不會(huì)發(fā)生變化。
永遠(yuǎn)不要在 reducer 里做這些操作:
- 修改傳入?yún)?shù);
- 執(zhí)行有副作用的操作,如 API 請(qǐng)求和路由跳轉(zhuǎn);
- 調(diào)用非純函數(shù),如
Date.now()或Math.random()。
reducer 一定要保持純凈。只要傳入?yún)?shù)相同,返回計(jì)算得到的下一個(gè) state 就一定相同。沒(méi)有特殊情況、沒(méi)有副作用,沒(méi)有 API 請(qǐng)求、沒(méi)有變量修改,單純執(zhí)行計(jì)算。
4.2 創(chuàng)建一個(gè) store
安裝 redux :npm install redux 或 yarn add redux
src/store/index.js
import { createStore } from 'redux';
// 定義 reducer 函數(shù)
const reducer = (state = 0, action) => {
switch (action.type) {
case 'add':
return state + 1;
case 'minus':
return state - 1;
default:
return state;
}
};
// 使用 createStore ,傳入 reducer 函數(shù),生成一個(gè) store
const store = createStore(reducer);
export default store;
4.3 使用 store
src/pages/ReduxPage.js
import React, { Component } from 'react';
import store from '../store';
export default class ReduxPage extends Component {
componentDidMount() {
// 到目前為止,通過(guò) dispath 一個(gè) action 修改 state 后,頁(yè)面并不會(huì)自動(dòng)更新
// 在沒(méi)有使用其他庫(kù)配合前,暫時(shí)使用這種方式更新界面,訂閱 store 變化,然后強(qiáng)制更新頁(yè)面
this.unsubscribe = store.subscribe(() => {
this.forceUpdate();
});
}
add = () => {
store.dispatch({ type: 'ADD' });
};
minus = () => {
store.dispatch({ type: 'MINUS' });
};
render() {
return (
<div>
ReduxPage
<div>count: {store.getState()}</div>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
<button onClick={() => this.unsubscribe()}>unsubscribe</button>
</div>
);
}
}
src/App.js
import React from 'react';
import ReduxPage from './page/ReduxPage';
function App() {
return (
<div className="App">
<ReduxPage />
</div>
);
}
export default App;
到此就已經(jīng)可以簡(jiǎn)單的使用 redux 進(jìn)行狀態(tài)管理
5. redux 實(shí)現(xiàn)
5.1 createStore 實(shí)現(xiàn)
創(chuàng)建 redux 并暴露 createStore 方法
src/redux/index.js
import createStore from './createStore'
export {
createStore
}
5.1.1 實(shí)現(xiàn) createStore
createStore 創(chuàng)建了一個(gè) store 實(shí)例,這個(gè)實(shí)例包含了 getState、subscribe、dispatch 等方法
export default function createStore(reducer) {
function getSate() {}
function subscribe() {}
function dispatch() {}
return {
getSate,
subscribe,
dispatch,
}
}
5.1.2 實(shí)現(xiàn) getState 方法
getState 只需要返回當(dāng)前等 state
export default function createStore(reducer) {
// 定義 currentState,保存當(dāng)前的 state
let currentState;
function getSate() {
// 調(diào)用 getState 時(shí),返回當(dāng)前的 state
return currentState;
}
function subscribe() {}
function dispatch() {}
return {
getSate,
subscribe,
dispatch,
}
}
5.1.3 實(shí)現(xiàn) subscribe 方法
subscribe,將訂閱的回調(diào)函數(shù)保存到回調(diào)函數(shù)數(shù)組中
export default function createStore(reducer) {
let currentState;
// 定義 currentListeners,保存訂閱的回調(diào)函數(shù)
let lisenters = [];
function getSate() {
return currentState;
}
function subscribe(listener) {
// 將回調(diào)函數(shù)保存到回調(diào)函數(shù)數(shù)組中
lisenters.push(listener)
// 返回一個(gè)取消訂閱的函數(shù)
return function unsubscribe() {
const index = lisenters.indexOf(lisenter);
lisenters.splice(index, 1);
}
}
function dispatch() {}
return {
getSate,
subscribe,
dispatch,
}
}
5.1.4 實(shí)現(xiàn) dispatch 方法
dispatch 一個(gè) action (dispatch(action))是修改 state 的唯一方法。
dispatch 方法會(huì)調(diào)用創(chuàng)建 store 時(shí)傳入的 reducer 函數(shù),并把當(dāng)前的 state 和 action 傳入。
最后會(huì)循環(huán)調(diào)用 subscribe 方法收集的回調(diào)函數(shù)。
export default function createStore(reducer) {
let currentState;
let lisenters = [];
function getSate() {
return currentState;
}
function subscribe(listener) {
lisenters.push(listener);
return function unsubscribe() {
const index = lisenters.indexOf(lisenter);
lisenters.splice(index, 1);
};
}
function dispatch(action) {
// 調(diào)用 reducer 函數(shù),修改當(dāng)前的 state
currentState = reducer(currentState, action)
// 循環(huán)調(diào)用回調(diào)函數(shù)
for (let i = 0; i < lisenters.length; i++) {
const lisenter = lisenters[i];
lisenter()
}
}
return {
getSate,
subscribe,
dispatch,
}
}
5.1.5 初始化 state 操作
export default function createStore(reducer) {
let currentState;
let lisenters = [];
function getState() {/*略*/}
function subscribe(listener) {/*略*/}
function dispatch(action) {/*略*/}
// 執(zhí)行一次初始化的 dispatch,這樣可以保證整個(gè) state 樹(shù)都擁有初始狀態(tài)值,這樣在定義 reducer 函數(shù)時(shí)定義都初始 state 才會(huì)生效。
dispatch({ type: '@@redux/INIT' });
return {
getState,
subscribe,
dispatch,
};
}
到這里,已經(jīng)實(shí)現(xiàn)了 redux 的基礎(chǔ)功能,除此之外,上述的幾個(gè)方法中,還需要做一些參數(shù)兼容性處理,邊緣情況的處理等。
此時(shí)可以修改上面的例子中的代碼,把 redux 改為自己實(shí)現(xiàn)的 redux(src/redux/index.js),修改后功能正常使用。
目前這里 action 還只能支持 js 的普通對(duì)象(plain object),action 不支持傳入一個(gè)函數(shù)(異步)。如果要實(shí)現(xiàn)異步操作,要傳入一個(gè)值為函數(shù)的 action 還需要借助中間件。
如何實(shí)現(xiàn)異步操作?
到目前為止,使用 redux 還只能使用普通對(duì)象作為 action,如果需要異步的更新 state,例如實(shí)現(xiàn)動(dòng)態(tài)從后端獲取數(shù)據(jù),然后修改 state,還無(wú)法實(shí)現(xiàn)。
import React, { Component } from 'react';
import store from '../store';
export default class ReduxPage extends Component {
componentDidMount() {
store.subscribe(() => {
this.forceUpdate();
});
}
add = () => {
store.dispatch({ type: 'ADD' });
};
minus = () => {
store.dispatch({ type: 'MINUS' });
};
// 傳入 action 為函數(shù),進(jìn)行異步操作
asyncAdd = () => {
store.dispatch(() => {
setTimeout(() => {
store.dispatch({ type: 'ADD' });
});
});
};
render() {
return (
<div>
<div>count: {store.getState()}</div>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
<button onClick={this.asyncAdd}>asyncAdd</button>
</div>
);
}
}
要實(shí)現(xiàn)異步操作,需要接入 redux 中間件
6. redux 中間件的使用
Redux 只是純粹的狀態(tài)管理器,默認(rèn)只支持同步,實(shí)現(xiàn)異步任務(wù)比如延遲、網(wǎng)絡(luò)請(qǐng)求等,需要中間件的支持
store 是 redux 的核心內(nèi)容,除了 store 相關(guān)都內(nèi)容,redux 還提供了其他一些 api,用于擴(kuò)展、接入其他庫(kù),實(shí)現(xiàn)更強(qiáng)大的功能。其中 applyMiddlewares 用于中間件的接入應(yīng)用。
中間件的執(zhí)行在調(diào)用 dispatch 到最終 reducer 函數(shù)修改 state 的這個(gè)過(guò)程之間。

接下來(lái)試用一下最簡(jiǎn)單的 thunk 和 logger 中間件
src/store/index.js
import { createStore, applyMiddleware } from 'redux';
// import { createStore } from '../redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
const reducer = function (state = 0, action) {
switch (action.type) {
case 'ADD':
return state + 1;
case 'MINUS':
return state - 1;
default:
return state;
}
};
// 使用 applyMiddleware 接入 logger,thunk 中間件
const store = createStore(reducer, applyMiddleware(logger, thunk));
export default store;
中間件是按照一定的順序執(zhí)行的,調(diào)整中間件的位置,可能會(huì)得到不同的執(zhí)行結(jié)果。
例如上面例子,logger 放在 thunk 前面,可以打印到異步的action 的日子,如果 logger 放到 thunk 后面,就無(wú)法打印出異步的action 的信息,因?yàn)?thunk 的邏輯代碼中,如果 action 是函數(shù),就會(huì)執(zhí)行該函數(shù),直接跳過(guò)后續(xù)中間件。這個(gè)在實(shí)際開(kāi)發(fā)中可能要了解并注意一下。
7. redux 中間件實(shí)現(xiàn)
實(shí)際上中間件是對(duì)原始的 dispatch 做了增強(qiáng)、擴(kuò)展,在 dispatch 外面又包了一層函數(shù),執(zhí)行中間件本身的功能,執(zhí)行完中間件本身的功能后,再去調(diào)用原始的 dispatch。
中間件可能是一個(gè),也可能是無(wú)數(shù)個(gè),要保證這些中間件都能有效并且有序的執(zhí)行,就需要一個(gè)好的方法將中間件串聯(lián)起來(lái)。
7.1 實(shí)現(xiàn) compose 函數(shù)
compose 函數(shù)實(shí)現(xiàn)將中間件串起來(lái),一個(gè)接一個(gè)執(zhí)行,并將上一個(gè)中間件的執(zhí)行結(jié)果,傳過(guò)下個(gè)中間件。
實(shí)現(xiàn)這樣的效果 compose(f1, f2, f3)(...args) => f1(f2(f3(...args)))
compose 的實(shí)現(xiàn)使用到 Array.prototype.reduce() 方法。
src/redux/compose.js
export default function compose(...funcs) {
// funcs 長(zhǎng)度為 0 的話(huà),返回一個(gè)默認(rèn)的函數(shù)
if(funcs.length === 0) {
return arg => arg
}
// funcs 長(zhǎng)度為 1 的話(huà),直接返回該函數(shù)
if(funcs.length === 1) {
return funcs[0]
}
// 使用 reduce 將函數(shù)串聯(lián)起來(lái)
return funcs.reduce((a,b) => (...args) => a(b(...args)))
// 下面是將上面這行代碼的箭頭函數(shù)拆開(kāi),方便理解,要想理解這段代碼,必須先理解 reduce 的使用。
// 例如:funcs 是 [logger,thunk],則最終會(huì)得到的是 logger(thunk(..args))
// return funcs.reduce(function(a, b) {
// return function(...args) {
// return a(b(...args))
// }
// })
}
7.2 實(shí)現(xiàn) applyMiddleware 函數(shù)
applyMiddleware 函數(shù)傳入中間件,并返回一個(gè)增強(qiáng)函數(shù),該函數(shù)會(huì)對(duì) createStore 函數(shù)進(jìn)行增強(qiáng)。
src/redux/applyMiddleware.js
import compose from './compose';
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer) => {
let store = createStore(reducer);
let dispatch = () => {
throw new Error(
'不允許在構(gòu)建中間件時(shí)調(diào)用 dispatch,其他中間件不會(huì)應(yīng)用此 dispatch。'
);
};
// 提供給中間件到 API
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args),
};
// 調(diào)用中間件函數(shù)(構(gòu)建),并返回一個(gè)調(diào)用后到數(shù)據(jù)
const chain = middlewares.map((middleware) => middleware(middlewareAPI));
// 使用 compose 將中間件數(shù)組串起來(lái)
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
}
這里 let dispatch 定義了一個(gè)函數(shù),最后才給 dispatch 賦值為“增強(qiáng)” 后的 dispatch, 是為了防止在構(gòu)建中間件的時(shí)候就調(diào)用 dispatch。
src/redux/createStore.js
修改 createStore 函數(shù)增加一個(gè) enhancer 參數(shù),用于增強(qiáng) createStore
export default function createStore(reducer, enhancer) {
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('enhancer 必須是一個(gè)函數(shù)');
}
return enhancer(createStore)(reducer);
}
/*略*/
}
當(dāng)使用 applyMiddleware 函數(shù)接入中間件的時(shí)候增加 store 功能的時(shí)候,enhancer 就是 applyMiddleware
7.3 實(shí)現(xiàn)中間件
中間件一共3層函數(shù),
第一層函數(shù),是中間件的構(gòu)建函數(shù),構(gòu)建時(shí)會(huì)傳入 getState 和 dispatch 方法,中間件的初始化也會(huì)在這里執(zhí)行,使中間件在執(zhí)行時(shí)可以用到這兩個(gè)方法。
第二層函數(shù),主要用于將中間件串連起來(lái)
第三層函數(shù),是該中間件要擴(kuò)展的功能的主要邏輯代碼實(shí)現(xiàn)
7.3.1 實(shí)現(xiàn) redux-thunk
// 構(gòu)建中間件的時(shí)候,傳入 getState, dispatch,使中間件可以用這兩個(gè)方法
const thunk = ({ getState, dispatch }) => {
// 第二層是 compose 的時(shí)候生成的一層包一層的函數(shù),其中 next 就是下一層中間件,最后一個(gè) next 就是原始的 dispatch
return (next) => {
// 中間件主要邏輯代碼
return (action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
};
};
8. react-redux
單純依靠 redux 的話(huà),只能通過(guò) subscribe 訂閱數(shù)據(jù)的變化,手動(dòng)更新界面,并且每次都需要通過(guò) getState 獲取最新的數(shù)據(jù)。
這樣操作起來(lái)比較麻煩。
要更好的將 react 跟 redux 結(jié)合起來(lái),就需要借助像 react-redux 這樣的庫(kù)。
8.1 react-redux api
react-redux 提供了兩個(gè) api:Provider、connect
<Provider store>connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
-
Provider使組件層級(jí)中的connect()方法都能夠獲得 Redux store,正常情況下,你的根組件應(yīng)該嵌套在<Provider>中才能使用connect()方法。 -
connect顧名思義,就是連接 Redux store 與組件。它是一個(gè)高階組件(HOC),傳入一個(gè)組件,并且返回一個(gè)新的組件,擴(kuò)展原來(lái)的組件使原來(lái)組件可以獲取到 sotre 中的數(shù)據(jù)與變更數(shù)據(jù)的方法。連接操作不會(huì)改變?cè)瓉?lái)的組件類(lèi)。而是返回一個(gè)新的已與 Redux store 連接的組件類(lèi)。
8.2 react-redux 使用
將上面例子改成使用 react-redux
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from './react-redux';
import store from './store'
// 使用 Provider 為后代組件提供 store,使組件層級(jí)中的 connect() 方法都能夠獲得 Redux store
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
src/page/ReactReduxPage.js
import React, { Component } from 'react';
import { connect } from '../react-redux';
class ReactReduxPage extends Component {
add = () => {
this.props.dispatch({ type: 'ADD' });
};
minus = () => {
this.props.dispatch({ type: 'MINUS' });
};
asyncAdd = () => {
this.props.dispatch((dispatch) => {
setTimeout(() => {
dispatch({ type: 'ADD' });
}, 1000);
});
};
render() {
return (
<div>
<div>count: {this.props.count}</div>
{/* <button onClick={this.add}>add</button> */}
<button onClick={this.props.add}>add</button>
<button onClick={this.minus}>minus</button>
<button onClick={this.asyncAdd}>asyncAdd</button>
</div>
);
}
}
// mapStateToProps 用于將 redux 的 state 傳給 組件
// mapDispatchToProps 用于將 dispatch 與 action 封裝成方法,再傳給組件,方便組件里修改 state,而不用寫(xiě)大量 dispatch(xxxx)
const mapStateToProps = (state) => ({ count: state });
// mapDispatchToProps 傳一個(gè)對(duì)象的使用方式
const mapDispatchToProps = {
add: () => ({ type: 'ADD' }),
};
// mapDispatchToProps 傳一個(gè)函數(shù)的使用方式,下面代碼與上面?zhèn)鲗?duì)象的方式實(shí)現(xiàn)同樣的功能
// const mapDispatchToProps = (dispatch) => {
// return {
// dispatch,
// add: () => dispatch({ type: 'ADD' }),
// };
// };
// 使用 connect 方法連接組件與 store
// 傳入的 mapStateToProps 和 mapDispatchToProps 定義了需要傳遞給組件的 state 與 dispatch
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage);
8.3 react-redux 中用到的 react 的api:Context
這里 Context 用于將 redux 中的 state 傳遞到各個(gè)組件,使組件可以方便的使用 redux 的 state 中的數(shù)據(jù)。
在一個(gè)典型的 React 應(yīng)用中,數(shù)據(jù)是通過(guò) props 屬性自上而下(由父及子)進(jìn)行傳遞的,但這種做法對(duì)于某些類(lèi)型的屬性而言是極其繁瑣的(例如:地區(qū)偏好,UI 主題),這些屬性是應(yīng)用程序中許多組件都需要的。Context 提供了一種在組件之間共享此類(lèi)值的方式,而不必顯式地通過(guò)組件樹(shù)的逐層傳遞 props。
Context 在日常開(kāi)發(fā)中用的比較少,在第三方庫(kù)中用到比較多。react-redux 中就用到這個(gè)功能。
Context 的使用
// Context 可以讓我們無(wú)須明確地傳遍每一個(gè)組件,就能將值深入傳遞進(jìn)組件樹(shù)。
// 為當(dāng)前的 theme 創(chuàng)建一個(gè) context(“l(fā)ight”為默認(rèn)值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一個(gè) Provider 來(lái)將當(dāng)前的 theme 傳遞給以下的組件樹(shù)。
// 無(wú)論多深,任何組件都能讀取這個(gè)值。
// 在這個(gè)例子中,我們將 “dark” 作為當(dāng)前的值傳遞下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中間的組件再也不必指明往下傳遞 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 讀取當(dāng)前的 theme context。
// React 會(huì)往上找到最近的 theme Provider,然后使用它的值。
// 在這個(gè)例子中,當(dāng)前的 theme 值為 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
function Button({ theme }) {
return <button style={{ backgroundColor: theme }}>Test Button</button>;
}
// 也可以使用 Consumer 獲取 context
// function ThemedButton () {
// return (
// <ThemeContext.Consumer>
// {value => <Button theme={value} />}
// </ThemeContext.Consumer>
// )
// }
// 或者使用 hooks,useContext()
8.4 react-redux 中用到的 redux 的 api:bindActionCreators
bindActionCreators 在 connect 時(shí)傳 mapDispatchToProps 為對(duì)象時(shí)會(huì)用到,用于將對(duì)象中每個(gè) key 對(duì)應(yīng)的 value(action creator) 綁定一層 dispatch 調(diào)用。然后通過(guò) props 傳給組件,使組件中可以更方便的修改 redux 的 state,減少組件中寫(xiě)很多 dispatch(xxx)。
action creator 后面會(huì)講。
用法:bindActionCreators(actionCreators, dispatch)
bindActionCreators 把一個(gè) value 為不同 action creator 的對(duì)象,轉(zhuǎn)成擁有同名 key 的對(duì)象。同時(shí)使用 dispatch對(duì)每個(gè) action creator 進(jìn)行包裝,以便可以直接調(diào)用它們。
作用大概是下面這樣的效果:
{
add: () => ({ type: 'ADD' }),
};
// 變成
{
add: () => dispatch({ type: 'ADD' })
}
參數(shù):
-
actionCreators(Function or Object): 一個(gè) action creator,或者一個(gè) value 是 action creator 的對(duì)象。 -
dispatch(Function): 一個(gè)由Store實(shí)例提供的dispatch函數(shù)。
8.4.1 Action Creator
Action Creator 是指用來(lái)生成 action 的函數(shù)
例如:
function ADD_TODO(text) {
return { type: 'ADD', text}
}
dispatch(ADD_TODO('待辦事項(xiàng)1')) // dispatch({ type: 'ADD', text: '待辦事項(xiàng)1'})
上例中
ADD_TODO用來(lái)生成 Action{ type: 'ADD', text},ADD_TODO就是一個(gè) action creator 函數(shù)Action 是一個(gè)信息的負(fù)載,而 action creator 是一個(gè)創(chuàng)建 action 的工廠(chǎng)。
8.4.1 bindActionCreators 實(shí)現(xiàn)
作用:在 mapDispatchToProps 為對(duì)象時(shí),使對(duì)象 { add: () => ({type: 'ADD'})} 相當(dāng)于 add = () => dispatch({type: 'ADD'})
// bindActionCreator 會(huì)對(duì) action creator 進(jìn)行包裝,加上 dispatch 調(diào)用
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args));
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
const boundActionCreators = {};
for (let key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
8.4 react-redux 實(shí)現(xiàn)
src/react-redux/index.js
import Provider from './Provider';
import connect from './connect';
// 暴露 Provider,connect API
export { Provider, connect };
創(chuàng)建一個(gè) Context,用于數(shù)據(jù)通信
src/react-redux/Context.js
import React from 'react';
export const ReactReduxContext = React.createContext(null);
export default ReactReduxContext;
8.5 Provider 的實(shí)現(xiàn)
import React, { Component } from 'react';
import { ReactReduxContext } from './Context';
export default class Provider extends Component {
render() {
// 使用 Context.Provider 為組件提供 store
return (
<ReactReduxContext.Provider value={this.props.store}>
{this.props.children}
</ReactReduxContext.Provider>
);
}
}
8.6 connect 的實(shí)現(xiàn)
connect 接受 mapStateToProps 和 mapDispatchToProps 參數(shù),返回一個(gè)高階組件。
“連接” 需要使用 store 的組件,把 store 中的值傳遞給組件
import React, { useLayoutEffect, useReducer, useContext } from 'react';
// import { bindActionCreators } from 'redux';
import { bindActionCreators } from '../redux';
import { ReactReduxContext } from './Context';
const connect = (mapStateToProps = (state) => state, mapDispatchToProps) => (
WrappedComponent
) => (props) => {
const store = useContext(ReactReduxContext);
const { getState, dispatch, subscribe } = store;
// mapStateToProps 是在使用 connect 時(shí)傳入的第一個(gè)參數(shù),
// getState 獲取到 state 到值,然后傳遞給 mapStateToProps 使用
// mapStateToProps 執(zhí)行完成后返回需要傳遞給組件的 stateProps
const stateProps = mapStateToProps(getState());
// connect 的第二個(gè)參數(shù) mapDispatchToProps 可以是對(duì)象或者函數(shù)
let dispatchProps;
if (typeof mapDispatchToProps === 'object') {
// 如果 mapDispatchToProps 是一個(gè)對(duì)象,則使用 bindActionCreators 將該對(duì)象包裝成可以直接調(diào)用的函數(shù)對(duì)象
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
} else if (typeof mapDispatchToProps === 'function') {
// 如果 mapDispatchToProps 是一個(gè)函數(shù),則調(diào)用函數(shù)并傳入 dispatch
dispatchProps = mapDispatchToProps(dispatch);
} else {
// 默認(rèn)傳遞 dispatch
dispatchProps = { dispatch };
}
function storeStateUpdatesReducer(state, action) {
return state + 1;
}
// 使用 useReducer 實(shí)現(xiàn)強(qiáng)制更新頁(yè)面
// useReducer 返回的數(shù)組包含兩個(gè)項(xiàng) [state, dispatch],調(diào)用 dispatch 返回新的 state 時(shí),組件會(huì)重新渲染
const [, forceComponentUpdateDispatch] = useReducer(
storeStateUpdatesReducer,
0
);
useLayoutEffect(() => {
// 訂閱 store 中數(shù)據(jù)更新,強(qiáng)制刷新頁(yè)面
const ubsubscribe = subscribe(() => {
forceComponentUpdateDispatch({ type: 'STORE_UPDATED' });
});
return () => {
// 卸載組件取消訂閱
ubsubscribe && ubsubscribe();
};
}, [store]);
// 將需要“連接”的組件返回,并傳遞給組件需要的數(shù)據(jù)
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />;
};
export default connect;
9. redux devtools
9.1 接入 redux
import { createStore, applyMiddleware } from 'redux';
// 引入 composeWithDevTools
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
const reducer = function (state = 0, action) {
switch (action.type) {
case 'ADD':
return state + 1;
case 'MINUS':
return state - 1;
default:
return state;
}
};
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(logger, thunk))
);
export default store;
9.2 chrome 插件:redux devtools

打開(kāi) Chrome 調(diào)試工具,選擇 redux 進(jìn)入 redux devtools 調(diào)試界面。
選擇使用的功能:
Log monitor、Inspector、Chart,默認(rèn)是Inspector。錄制的redux 執(zhí)行過(guò)程中執(zhí)行的 action 的列表,點(diǎn)擊每個(gè) action 可以進(jìn)入到該 action 下的詳細(xì)狀態(tài)
-
Action可以查看當(dāng)前 action 的詳細(xì)內(nèi)容redux-devtools-action.png
-
State可以查看當(dāng)前 state 數(shù)據(jù)的詳細(xì)內(nèi)容redux-devtools-state.png
-
Diff可以查看當(dāng)前 action 執(zhí)行后,state 發(fā)生了哪些變化redux-devtools-diff.png
-
Trace跟蹤當(dāng)前 action 執(zhí)行的代碼位置redux-devtools-trace.png
-
Test測(cè)試模版redux-devtools-test.png
redux-devtools-toolbar.png
Start recording/Pause recording開(kāi)始/停止錄制,可以指定從什么時(shí)候開(kāi)始錄制到什么停止。Lock changes鎖定當(dāng)前的錄制狀態(tài),再有 action 執(zhí)行也不會(huì)改動(dòng)當(dāng)前錄制的狀態(tài)。Dispatcher用于派發(fā) action,Dispatcher框內(nèi)可以編輯 action 的內(nèi)容。Slider用于自動(dòng)播放整個(gè)錄制的過(guò)程。Import/Export導(dǎo)入/導(dǎo)出,導(dǎo)出當(dāng)前錄制的狀態(tài)的 JSON 文件,之后用于導(dǎo)入,導(dǎo)入后會(huì)還原到該文件保存到狀態(tài)。Settings是 redux devtools 的配置菜單。-
切換功能面板到
Log monitor可以查看整個(gè)過(guò)程的日志redux-devtools-log-monitor.png -
切換功能面板到
Chart可以查看整個(gè) store 的狀態(tài)redux-devtools-chart.png
擴(kuò)展閱讀:
官網(wǎng):https://redux.js.org/
github: https://github.com/reduxjs/redux
中文文檔:https://www.redux.org.cn/
React Context:https://zh-hans.reactjs.org/docs/context.html
你可能不需要 redux:You Might Not Need Redux







