每一種新語言志鹃,新框架或者工具的出現(xiàn)都是為了解決項(xiàng)目中存在的問題夭问。
動(dòng)機(jī)
隨著 Javascript 單頁應(yīng)用的開發(fā)越來越復(fù)雜,管理不斷變化的 state 變得非常困難曹铃。這些狀態(tài)有很多:UI 狀態(tài)缰趋,服務(wù)器數(shù)據(jù),緩存數(shù)據(jù)等陕见。而組件秘血,頁面之間狀態(tài)共享也是一個(gè)問題。
在 React 中评甜,數(shù)據(jù)在組件中是單向流動(dòng)的灰粮,數(shù)據(jù)從父組件傳遞給子組件是通過 props,由于這個(gè)特征忍坷,兩個(gè)非父子關(guān)系的組件(或叫兄弟組件)之間的通信就比較麻煩粘舟。
Redux 的出現(xiàn)正式是為了解決 state 數(shù)據(jù)的管理問題。
設(shè)計(jì)思想
Redux 將整個(gè)應(yīng)用的狀態(tài)都存儲(chǔ)在一個(gè)地方佩研,就是 Store(容器)柑肴,里面保存著應(yīng)用的 state(狀態(tài)樹),組件可以通過 dispach(派發(fā))action(行為)給 store(狀態(tài)容器)旬薯,而不是直接通知其他組件晰骑。其他組件可以通過訂閱 store 中的 state(狀態(tài))來更新視圖。
三大原則
寫 Redux 需要遵循三個(gè)原則绊序,有制約硕舆,有規(guī)則隶症,狀態(tài)才會(huì)可預(yù)測。因此 Redux 需要遵循以下三個(gè)原則:
單一數(shù)據(jù)源
整個(gè)應(yīng)用的 state 被儲(chǔ)存在一棵 Object tree 中岗宣,并且這個(gè) Object tree 只存在于唯一一個(gè) store 中蚂会;
State 是只讀
State 是只讀的,惟一改變 state 的方法就是dispatch(觸發(fā)) action(動(dòng)作)耗式;
使用純函數(shù)來執(zhí)行修改
通過 disptch action 來改變 state胁住,action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象,使用純函數(shù)(沒有副作用的函數(shù):函數(shù)的輸出完全取決函數(shù)的參數(shù)刊咳;副作用:獲取網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)彪见,修改 dom 標(biāo)題等都屬于副作用)來執(zhí)行修改,為了描述 action 如何改變 state tree 娱挨,需要編寫 reducer (處理器)余指。
單一數(shù)據(jù)源的設(shè)計(jì)讓組件之間的通信更加方便,同時(shí)也便于狀態(tài)的統(tǒng)一管理跷坝。
基本使用
通過一個(gè) React 計(jì)數(shù)器組件來使用一下酵镜,新建一個(gè)項(xiàng)目,刪除多余的文件柴钻,啟動(dòng):
npx create-react-app redux-family
cd redux-family
npm install redux -S
npm start
編寫一個(gè) Counter 組件淮韭,應(yīng)用 redux :
通過使用createStore
來創(chuàng)建一個(gè)srore
倉庫,參數(shù)就是reducer
和初始 state
贴届,通過 store.getState()
方法獲取狀態(tài)靠粪,store.dispatch
來派發(fā)動(dòng)作,store. subscribe
訂閱更新毫蚓,store.unsubscribe
取消訂閱占键,這些基本就是 redux 的全部了:
// src/components/Counter1.js
import React, { Component } from 'react';
import { createStore} from 'redux';
const INCREMENT = 'ADD';
const DECREMENT = 'MINUS';
const reducer = (state = initState, action) => {
switch (action.type) {
case INCREMENT:
return { number: state.number + 1 };
case DECREMENT:
return { number: state.number - 1 };
default:
return state;
}
}
let initState = { number: 0 };
const store = createStore(reducer, initState);
export default class Counter extends Component {
unsubscribe;
constructor(props) {
super(props);
this.state = { number: 0 };
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => this.setState({ number: store.getState().number }));
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => store.dispatch({ type: 'ADD' })}>+</button>
<button onClick={() => store.dispatch({ type: 'MINUS' })}>-</button>
<button onClick={
() => {
setTimeout(() => {
store.dispatch({ type: 'ADD' });
}, 1000);
}
}>1秒后加1</button>
</div>
)
}
}
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1';
ReactDOM.render(
<Counter1 />,
document.getElementById('root')
);
結(jié)合 React 框架 react-redux
react-redux
用來整合 react 狀態(tài)和數(shù)據(jù)訂閱,使其使用起來更簡便元潘。提供 Provider
使所有組件都可以獲取狀態(tài)畔乙,connect
聯(lián)接狀態(tài)和組件,獲取state
狀態(tài)柬批,dispatch
派發(fā)動(dòng)作:
npm install react-redux -S
npm start
// src/components/Counter2.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
class Counter extends Component {
render() {
return (
<div>
<p>{this.props.number}</p>
<button onClick={() => this.props.add()}>+</button>
<button onClick={() => this.props.minus()}>-</button>
<button onClick={
() => {
setTimeout(() => {
this.props.add({ type: 'ADD' });
}, 1000);
}
}>1秒后加1</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
number: state.number
}
}
const mapDispatchToProps = (dispatch) => {
return {
add: () => dispatch({type: 'ADD'}),
minus: () => dispatch({type: 'MINUS'})
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore} from 'redux';
import { Provider } from 'react-redux'
import Counter2 from './components/Counter2'
const INCREMENT = 'ADD';
const DECREMENT = 'MINUS';
const reducer = (state = initState, action) => {
switch (action.type) {
case INCREMENT:
return { number: state.number + 1 };
case DECREMENT:
return { number: state.number - 1 };
default:
return state;
}
}
let initState = { number: 0 };
const store = createStore(reducer, initState);
ReactDOM.render(
<Provider store={store}>
<Counter2 />
</Provider>,
document.getElementById('root')
);
不僅僅使用在 React 上
Redux 是一個(gè)狀態(tài)數(shù)據(jù)流管理方案啸澡,不是為了 react 單獨(dú)實(shí)現(xiàn)的,可以使用在任何框架以及原生 Javascript 中氮帐,這更體現(xiàn)了 Redux 的廣闊的視角和先進(jìn)的思想。
下面在原生 Javascript 應(yīng)用中使用如下:
需要修改下 index.html
洛姑,增加顯示數(shù)據(jù)的 dom上沐,以及操作按鈕的 dom:
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
<div id="counter">
<p id="number">0</p>
<button id="add">+</button>
<button id="minus">-</button>
</div>
</body>
</html>
index.js
文件中通過訂閱渲染函數(shù)來做數(shù)據(jù)的更新:
// src/index.js
import { createStore} from 'redux';
let counterValue = document.getElementById('number');
let incrementBtn = document.getElementById('add');
let decrementBtn = document.getElementById('minus');
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
let initState = { number: 0 };
const reducer = (state = initState, action) => {
switch (action.type) {
case INCREMENT:
return { number: state.number + 1 };
case DECREMENT:
return { number: state.number - 1 };
default:
return state;
}
}
let store = createStore(reducer);
function render() {
counterValue.innerHTML = store.getState().number;
}
store.subscribe(render);
render();
incrementBtn.addEventListener('click', function () {
store.dispatch({ type: INCREMENT });
});
decrementBtn.addEventListener('click', function () {
store.dispatch({ type: DECREMENT });
});
以上就是Redux
的基本應(yīng)用,學(xué)會(huì)使用一個(gè)框架很快楞艾,但是不能僅限于使用参咙,還要知道其內(nèi)部的實(shí)現(xiàn)原理龄广,下面就一起來寫一個(gè)具有簡單功能的模仿的 redux
。
脫離框架的簡單實(shí)現(xiàn)(源碼)
createStore 創(chuàng)建倉庫
從使用中可以看出蕴侧,redux
需要返回一個(gè)createStore
方法择同,這個(gè)方法返回一個(gè)對(duì)象,對(duì)象上有getState()净宵,dispatch敲才,subscribe
方法,getState()
返回最新的state
择葡,dispatch
需要派發(fā)action
紧武,subscribe
需要訂閱更新函數(shù)并返回一個(gè)取消訂閱的函數(shù)
。redux內(nèi)部
需要先派發(fā)一次INIT動(dòng)作
來填充初始狀態(tài)
敏储。如此分析下來阻星,這樣一個(gè)redux殼
就有了,先來寫殼已添,再往里面填充功能:
redux/index.js
import createStore from './createStore.js'
export {
createStore
}
redux/utils/ActionType.js
/**
* These are private action types reserved(保留的) by Redux.
* For any unknown actions, you must return the current state.
* (所有未知的action都必須返回當(dāng)前的state)
* If the current state is undefined, you must return the initial state.
* (如果當(dāng)前state是undefined妥箕,必須返回和一個(gè)初始的state)
* Do not reference these action types directly in your code.
* (不要在代碼中直接引入這些action type)
*/
const randomString = () => {
return Math.random().toString(36).substring(7).split('').join('.')
}
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`
}
export default ActionTypes
redux/createStore.js
import ActionType from './utils/ActionType'
function createStore(reducer, preloadedState) {
let currentState = preloadedState || {}
let currentReducer = reducer
let currentListeners = []
const getState = () => {
return currentState
}
const dispatch = (action) => {
}
const subscribe = (listener) => {
return function unsubscribe() {
}
}
// 內(nèi)部需要先派發(fā)一次動(dòng)作
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates(填充)
// the initial state tree.
dispatch({type: ActionType.INIT})
return {
getState,
dispatch,
subscribe,
}
}
export default createStore
這樣的殼子就寫好了,現(xiàn)在填充功能更舞,訂閱函數(shù)需要收集所有的訂閱事件
存儲(chǔ)到數(shù)組
中矾踱,并返回一個(gè)取消訂閱
的函數(shù);
dispatch
派發(fā)action
給reducer
獲取最新的狀態(tài)
疏哗,最后通知所有的訂閱組件
(這里是更新函數(shù))呛讲;
const subscribe = (listener) => {
currentListeners.push(listener)
// 返回取消訂閱的函數(shù)
return function unsubscribe() {
const index = currentListeners.indexOf(listener)
// 取消訂閱就是將這個(gè)訂閱事件從數(shù)組中刪除
currentListeners.splice(index, 1)
}
}
const dispatch = (action) => {
// reducer:傳入舊的狀態(tài)和action,返回新的state
currentState = currentReducer(currentState, action)
const listeners = currentListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 執(zhí)行每一個(gè)訂閱函數(shù)
listener()
}
return action
}
這樣基本的redux功能
就實(shí)現(xiàn)完了返奉,將原生 Javascript 應(yīng)用改成自己實(shí)現(xiàn)的redux贝搁,功能依然正常。
bindActionCreator
使用action creator
直接派發(fā)動(dòng)作芽偏,不直接調(diào)用 dispatch雷逆,這是一種更便捷的方法。bindActionCreator
將action creator
轉(zhuǎn)換為帶有相同key
污尉,但是每個(gè)函數(shù)都包含dispatch
調(diào)用膀哲。
將之前直接在 react 中使用 redux 的例子重新改寫一下,使用 bindActionCreator
重寫一下實(shí)現(xiàn):
import React, { Component } from 'react';
import { createStore, bindActionCreators} from 'redux';
const INCREMENT = 'ADD';
const DECREMENT = 'MINUS';
const reducer = (state = initState, action) => {
switch (action.type) {
case INCREMENT:
return { number: state.number + 1 };
case DECREMENT:
return { number: state.number - 1 };
default:
return state;
}
}
+ function Add() {
+ return { type: INCREMENT }
+}
+function Minus() {
+ return { type: DECREMENT }
+}
let initState = { number: 0 };
const store = createStore(reducer, initState);
+ const actions = { Add, Minus }
+ const boundActions = bindActionCreators(actions, store.dispatch)
export default class Counter extends Component {
unsubscribe;
constructor(props) {
super(props);
this.state = { number: 0 };
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => this.setState({ number: store.getState().number }));
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
+ <button onClick={boundActions.Add}>+</button>
+ <button onClick={boundActions.Minus}>-</button>
<button onClick={
() => {
setTimeout(() => {
+ boundActions.Add();
}, 1000);
}
}>1秒后加1</button>
</div>
)
}
}
可以看到所有 action 都由 action creator
派發(fā)被碗,不再顯式的調(diào)用store.dispatch某宪,bindActionCreator
內(nèi)部就是返回了 dispatch 函數(shù)的調(diào)用,下面我們來寫一下這個(gè) bindActionCreator 函數(shù)實(shí)現(xiàn):
思路:就是給每一個(gè)action creator 都返回 dispatch 方法調(diào)用锐朴,如果傳入的是函數(shù)兴喂,也就是單個(gè) action creator,直接調(diào)用返回 type,將參數(shù)傳給調(diào)用 dispatch 的函數(shù)衣迷,如果是一個(gè)對(duì)象畏鼓,就給這個(gè)對(duì)象上的每一個(gè) action creator 都加上 dispatch 方法調(diào)用,所以代碼的核心就是bindActionCreator
function bindActionCreator(actionCreator, dispatch) {
// 這里派發(fā) action壶谒,執(zhí)行 actionCreator 函數(shù)云矫,返回一個(gè) type 作為 dispatch 的參數(shù)調(diào)用
return function(...args) {
dispatch(actionCreator.apply(this, args))
}
}
完整的實(shí)現(xiàn)代碼如下:
/**
* function Add() {
return { type: INCREMENT }
}
function Minus() {
return { type: DECREMENT }
}
* const actions = { Add, Minus }
* const boundActions = bindActionCreators(actions, store.dispatch)
*/
function bindActionCreator(actionCreator, dispatch) {
// 這里派發(fā) action,執(zhí)行 actionCreator 函數(shù)汗菜,返回一個(gè) type 作為 dispatch 的參數(shù)調(diào)用的函數(shù)
return function(...args) {
dispatch(actionCreator.apply(this, args))
}
}
function bindActionCreators(actionCreators, dispatch) {
// 如果是一個(gè)函數(shù)让禀,表示就是一個(gè) actionCreator,直接返回包裝了調(diào)用dispatch的函數(shù)
if (actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// 如果是一個(gè)對(duì)象呵俏,給每個(gè) actionCreator 都包裝返回調(diào)用dispatch的函數(shù)
if (typeof actionCreators === 'object' && actionCreators !== null) {
const bindActionCreators = {}
for(const key in actionCreators) {
const actionCreator = actionCreators[key]
bindActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
return bindActionCreators
}
}
export default bindActionCreators
combineReducers
通常組件不會(huì)只有一個(gè) reducer堆缘,每個(gè)組件都可以有自己的 reducer 和 state,便于模塊化管理普碎。但是 redux 規(guī)定吼肥,一個(gè)應(yīng)用只能有一個(gè) store,一個(gè) reducer麻车,一個(gè) state缀皱,根據(jù)程序模塊化,分而治之的管理模式动猬,這個(gè)時(shí)候就需要合并多個(gè) reducer啤斗,多個(gè)狀態(tài)為一個(gè) reducer,一個(gè)state赁咙,combineReducers 就是做合并 reducer
操作的钮莲,下面先來看看用法,然后再實(shí)現(xiàn)一個(gè)彼水。
這里只貼重要的代碼崔拥,其他細(xì)致的可以到 github 倉庫看實(shí)戰(zhàn)。
src/components/Counter5.js
import React, { Component } from 'react';
import { bindActionCreators} from '../redux';
import * as actionType from '../actionTypes'
// import { bindActionCreators } from '../redux'
import store from '../store'
const actions = {
Add5: actionType.Add5,
Minus5: actionType.Minus5
}
const boundActions = bindActionCreators(actions, store.dispatch)
export default class Counter5 extends Component {
constructor(props) {
super(props)
this.state = { number : 5}
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => this.setState({ number: store.getState().Counter5.number }));
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={boundActions.Add5}>+</button>
<button onClick={boundActions.Minus5}>-</button>
<button onClick={
() => {
setTimeout(() => {
boundActions.Add5();
}, 1000);
}
}>1秒后加5</button>
</div>
)
}
}
新建 reducers 和 action types
src/reducers/Counter5.js
凤覆,src/actionTypes
// src/reducers/Counter5.js
import * as actionType from '../actionTypes'
export let Counter5State = { number: 5 };
const reducer = (state = Counter5State, action) => {
switch (action.type) {
case actionType.INCREMENT5:
return { number: state.number + 5 };
case actionType.DECREMENT5:
return { number: state.number - 5 };
default:
return state;
}
}
export default reducer
// src/actionTypes.js
export const INCREMENT5 = 'ADD5';
export const DECREMENT5 = 'MINUS5';
export const INCREMENT6 = 'ADD6';
export const DECREMENT6 = 'MINUS6';
export function Add5() {
return { type: INCREMENT5 }
}
export function Minus5() {
return { type: DECREMENT5 }
}
export function Add6() {
return { type: INCREMENT6 }
}
export function Minus6() {
return { type: DECREMENT6 }
}
src/store.js
import { createStore } from 'redux'
import reducers from './reducers'
const store = createStore(reducers);
export default store
src/reducers/index.js
import { combineReducers } from 'redux'
import Counter5 from '../reducers/Counter5'
import Counter6 from '../reducers/Counter6'
const reducers = {
Counter5,
Counter6
}
const combinedReducers = combineReducers(reducers)
export default combinedReducers
其他的不貼了链瓦,效果就是組件的狀態(tài)互不干擾:
combineReducers
就是將多個(gè)組件的 reducer 合并為一個(gè) reducer,內(nèi)部實(shí)現(xiàn)是過濾 reducers 對(duì)象上的函數(shù)屬性盯桦,然后返回一個(gè)combination
函數(shù)慈俯,這個(gè)combination
函數(shù)內(nèi)部會(huì)遍歷過濾之后的對(duì)象,解析出來每一個(gè) reducer拥峦,拿到對(duì)應(yīng)的狀態(tài)贴膘,最后再根據(jù) reducer 的 key,返回一個(gè)新的狀態(tài)對(duì)象事镣。根據(jù) reducer 解析出來的 state 是最重要的一段步鉴,const reducer = finalReducers[key]
揪胃,const nextStateForKey = reducer(previousStateForKey, action)
璃哟。這里面有一個(gè)優(yōu)化操作:定義一個(gè) hasChanged
的標(biāo)志氛琢,新狀態(tài)和老狀態(tài)不一致就返回新狀態(tài),否則返回老的狀態(tài)随闪。
具體代碼實(shí)現(xiàn)如下阳似,跟源碼的實(shí)現(xiàn)是一致的,代碼有備注解釋:
src/redux/combineReducers
export default function combineReducers (reducers) {
// 1. 過濾 reducers 對(duì)象上的函數(shù)屬性
const reducerKeys = Object.keys(reducers) // ["counter1", "counter2"]
const finalReducers = {}
for(let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i] // "counter1"
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key] // finalReducers: {counter1: counter1, counter2: counter2}
}
}
// 2. 返回一個(gè)函數(shù) 遍歷finalReducers铐伴,生成state
const finalReducerKeys = Object.keys(finalReducers) // ['counter1', 'counter2']
return function combination (state = {}, action) {
let hasChanged = false // hasChanged是作性能優(yōu)化撮奏,沒有改變就返回原來的state
const nextState = {}
for(let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i] // "counter1"
const reducer = finalReducers[key] // reducer counter1 函數(shù)
const previousStateForKey = state[key] // 取出reducer對(duì)應(yīng)的state
// 這步最重要:給reducer傳入老的state和action,返回新的state
const nextStateForKey = reducer(previousStateForKey, action)
// 將新的state的key 拼到總的state上,組件通過 state.counter1.number獲取
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey // 前后狀態(tài)不一樣就是改變了
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length // reducers的key不一樣也是變了
return hasChanged ? nextState : state
}
以上就是 combineReducers 的實(shí)現(xiàn)当宴。
react-redux 實(shí)現(xiàn)
從上面可以 Counter5畜吊,Counter6 組件可以看出來,這兩個(gè)組件其實(shí)有很多的相似代碼户矢,比如訂閱更新玲献,獲取狀態(tài)等,這些都可以抽離出來梯浪,減少冗余代碼捌年,將 store 綁定到 context 上,至上而下的傳遞挂洛,這就是 react-redux 做的事情礼预。
先來看一下 react-redux 的應(yīng)用,實(shí)現(xiàn)一個(gè) Counter7 組件虏劲,對(duì)應(yīng)的 actions 和 reducer 和上面一樣托酸,就不贅述了。Counter7 使用 react-redux 的 connect 返回一個(gè)高階組件
:
src/components/Counter7.js
import React, { Component } from 'react'
import * as actionType from '../actionTypes'
import { connect } from 'react-redux'
const actions = {
add: actionType.Add7,
minus: actionType.Minus7
}
const mapStateToProps = (state) => {
console.log(state)
return {
number: state.Counter7.number
}
}
const mapDispatchToProps = actions
class Counter7 extends Component {
render() {
const { number, add, minus } = this.props
return (
<div>
<p>{number}</p>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
<button onClick={
() => {
setTimeout(() => {
add()
}, 1000)
}
}>1秒后加5</button>
</div>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter7)
src/index.js
使用 Provider
共享 store:
import React from 'react';
import ReactDOM from 'react-dom';
import Counter7 from './components/Counter7'
import store from './store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<Counter7 />
</Provider>
,
document.getElementById('root')
)
可以看到 Counter7 使用 connect 連接柒巫,mapStateToProps 將 store 上的state 拼到 props 上
励堡,mapDispatchToProps 將派發(fā)函數(shù)拼接到 props
。
下面來看看 Provider 和 connect 實(shí)現(xiàn)吻育,根據(jù) react-redux 官方的實(shí)現(xiàn)念秧,我們也采用函數(shù)和 hooks 的方式實(shí)現(xiàn)。
Provider 是利用 react 的 context 創(chuàng)建上下文容器布疼,value 可以設(shè)置成共享的數(shù)據(jù)摊趾,provider 提供數(shù)據(jù),consumer 消費(fèi)這些數(shù)據(jù)游两。
const { Provider, Consumer } = React.createContext(defaultValue);
新建一個(gè) react-redux 文件目錄砾层,里面來寫代碼實(shí)現(xiàn):
src/react-redux/index.js
import Provider from './Provider'
import connect from './connect'
export {
Provider,
connect
}
src/react-redux/ReactReduxContext.js
import React from 'react'
export const ReactReduxContext = React.createContext(null)
export default ReactReduxContext
src/react-redux/Provider.js
import React from 'react'
import ReactReduxContext from './ReactReduxContext'
export default function Provider (props) {
return (
<ReactReduxContext.Provider value={{ store: props.store }}>
{props.children}
</ReactReduxContext.Provider>
)
}
最復(fù)雜的是 connnect 的實(shí)現(xiàn):
connnect 是一個(gè)函數(shù),返回一個(gè)被包裝過后的組件贱案,里面做的事情就是將 state肛炮,dispatch 派發(fā)函數(shù)作為 props 拼到被包裝的組件上,然后在渲染的時(shí)候訂閱更新函數(shù),更新狀態(tài)侨糟。
mapStateToProps 映射 state 到 props 上const stateProps = useMemo(() => mapStateToProps(preState), [preState])
const mapStateToProps = (state) => {
return {
number: state.Counter7.number
}
}
mapDispatchToProps 就是映射 dispatch 函數(shù)到 props碍扔,這里分三種情況,如果參數(shù)是對(duì)象就要做 bindActionCreators秕重,分別包裝返回具有 dispatch能力的函數(shù)對(duì)象不同,像這樣:{add: ?, minus: ?}
;如果參數(shù)是函數(shù)溶耘,就直接拼mapDispatchToProps(props, dispatch)
二拐,否則就直接返回一個(gè)包含 dispatch 的對(duì)象;
const dispatchProps = useMemo(() => {
let dispatchProps
if (typeof mapDispatchToProps === 'object') {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch)
} else if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(props, dispatch)
} else {
dispatchProps = { dispatch }
}
return dispatchProps
}, [dispatch, props])
然后訂閱更新函數(shù)凳兵,更新狀態(tài):
const [, forceUpdate] = useReducer(x => x + 1, 0)
useLayoutEffect(() => subscribe(forceUpdate), [subscribe])
useLayoutEffect 會(huì)在 dom 更新完之后百新,瀏覽器繪制之前執(zhí)行。
最后返回包裝之后的組件:
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />
完整的 connect 的實(shí)現(xiàn)代碼如下:
import React, { useContext, useMemo, useReducer, useLayoutEffect } from 'react'
import ReactReduxContext from './ReactReduxContext'
import { bindActionCreators } from '../redux'
export default function connect (mapStateToProps, mapDispatchToProps) {
return function (WrappedComponent) {
return function (props) {
const { store } = useContext(ReactReduxContext)
const { getState, dispatch, subscribe} = store
const preState = getState()
// 映射狀態(tài)到 props
const stateProps = useMemo(() => mapStateToProps(preState), [preState])
// 映射 dispatch 到 props
const dispatchProps = useMemo(() => {
let dispatchProps
if (typeof mapDispatchToProps === 'object') {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch)
} else if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(props, dispatch)
} else {
dispatchProps = { dispatch }
}
return dispatchProps
}, [dispatch, props])
// 訂閱更新函數(shù)庐扫,更新狀態(tài)
const [, forceUpdate] = useReducer(x => x + 1, 0)
useLayoutEffect(() => subscribe(forceUpdate), [subscribe])
// 返回被包裝的組件
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />
}
}
}
到目前為止饭望,redux 和 react-redux 的基本核心都實(shí)現(xiàn)了,參照的是 github 上的源碼聚蝶。還剩下一個(gè) 中間件
的功能杰妓,這個(gè)準(zhǔn)備在下一章單獨(dú)去寫,分析一下時(shí)常用的幾個(gè) redux 中間件用法和實(shí)現(xiàn)碘勉,以及連接中間件的實(shí)現(xiàn)原理巷挥,到 react 生態(tài)的整合
,比如 redux-saga验靡,dva倍宾,umi
等。目前項(xiàng)目用到的生態(tài)就是這些胜嗓,所以想單獨(dú)開一篇專門寫這個(gè)(希望不要開天窗)高职,加深理解,溫故知新辞州。
參考:
https://github.com/reduxjs/redux
https://github.com/reduxjs/react-redux