參考reac-redux 庫 源碼
以下實現的功能代碼演示地址
使用react-redux庫
官方 react-redux 庫,使用
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './reducers/store';
ReactDOM.render(
<Provider store={store}>
<CoutA />
<CoutB />
</Provider>,
document.getElementById('root'),
);
- 該庫先導出一個
Provider
組件, 傳入一個store
屬性, 值是redux
的createStore(reducer)
方法的返回值
在組件中使用 react-redux
, 需要導出一個 connect
函數, 進行連接組件和倉庫
CoutA.js
import { connect } from 'react-redux';
class CoutA extends Component {
render() {
const { add, num } = this.props;
return (
<>
<p>CoutA組件:{num}</p>
<button onClick={add}>CoutA組件++</button>
</>
);
}
}
const mapStateToProps = (state) => state.count1;
const mapDispatchToProps = (dispatch) => ({
add() {
dispatch({type: ADD1});
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);
流程圖
- 通過
redux
的createStore(reducer)
創建store對象 - 通過
react-redux
的Peovider
組件 包裹所有的react 組件
, 通過store屬性
把 上邊的store 對象
傳入 - 通過
react-redux
提供的connect
方法連接 組件與數據源倉庫
實現 Provider
組件
在src / react-redux文件夾下:
該文件木有啥邏輯, 就是導出 connect
方法, Provider
組件
index.js
import Provider from './Provider';
import connect from './connect';
export { Provider, connect };
context.js
創建react 的上下文對象
import React from 'react';
export default React.createContext(null);
Provider.js
這個組件拿到外部提供的 store屬性, 把所有的子組件通過this.props.children
渲染
import React, { PureComponent } from 'react';
import Context from './context';
export default class extends PureComponent {
render() {
// 拿到頂級組件傳入的 store 屬性
const { store, children } = this.props;
return <Context.Provider value={store}>{children}</Context.Provider>;
}
}
接着看 connect 方法
const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch) => ({
add() {
dispatch({type: ADD1});
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);
這個方法傳入倆參數,執行後,又返回一個函數,接着把組件傳進去, 可能剛接觸的感到挺難理解的,從函數層面上講呢,算是高階函數,同時又使用了函數的科裏化,從react 上說,這就是個高階組件,
- 先執行
connect(mapStateToProps, mapDispatchToProps)
,執行完後,它返回一個函數,所以你需要再次調用 傳入組件, 可以拆分成以下兩步,比較好理解
// 1. 先執行第一步,返回一個函數
let connectFn = connect(mapStateToProps, mapDispatchToProps)
// 2. 再次調用這個函數,返回一個包裝後的組件(就是高階組件)
export default connectFn (CoutA)
根據以上梳理邏輯,接着實現這個 connect 方法
// connect(mapStateToProps, mapDispatchToProps)(CoutA)
// 第一層: 導出一個函數接收倆參數
export default function connect(mapStateToProps, mapDispatchToProps) {
// 第二層,返回一個函數,接收組件作爲參數,同時最終渲染的還是你傳入的這個組件,所以直接在render 返回
return function (WarpComponent) {
return class extends PureComponent {
render() {
return <WarpComponent />;
}
};
};
}
先讓包裝後的組件拿到狀態
通過 react 的 context 上下文對象
export default function connect(mapStateToProps, mapDispatchToProps) {
// 第二層,返回一個函數,接收組件作爲參數,同時最終渲染的還是你傳入的這個組件,所以直接在render 返回
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
constructor(props, context) {
super(props);
this.state = context.getState();
}
render() {
return <WarpComponent {...state}/>;
}
};
};
}
到這裏,你在組件中直接這樣調用,就可以通過props 拿到值了
connect()(CoutA)
再看傳入connect剛調用時傳入的參數格式, 傳入了倆函數,倆函數的返回值都是個對象
const mapStateToProps = (state) => state
const mapDispatchToProps = (dispatch) => ({
add() {
dispatch({type: ADD1});
},
});
connect(mapStateToProps, mapDispatchToProps)(CoutA)
處理上邊倆參數
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
constructor(props, context) {
super(props);
this.state = context.getState();
}
UNSAFE_componentWillMount() {
// 判斷傳入的第一個參數是不是函數,用戶可能傳,或者不傳,需要判斷處理
if (typeof mapDispatchToProps === 'function') {
// 這裏是把 你傳入的自定義的方法,綁定到props 上
this.setState({
...this.state,
...mapDispatchToProps(this.context.dispatch),
});
// 把dispacth 當作它的返回值
mapDispatchToProps(this.context.dispatch);
}
if (typeof mapStateToProps === 'function') {
// 把傳入的數據替換到 state 上
this.setState({
...this.state,
...mapStateToProps(this.state),
});
// 把 this .state 當作它的返回值
mapStateToProps(this.state);
}
}
render() {
return <WarpComponent {...this.state} />;
}
};
};
}
接着進行訂閱數據的變化
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
// .... 略
// 訂閱數據
componentDidMount() {
this.unsubscribe = this.context.subscribe(() =>
this.setState(this.context.getState()),
);
}
// 組件銷燬前,取消訂閱
componentWillUnmount() {
this.unsubscribe();
}
// .... 略
};
};
}
到這已經基本實現了,
優化簡化代碼
希望只獲取自己組件依賴的數據,而不關心其它的數據, 可以這樣寫
const mapStateToProps = (state) => state.count1;
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);
但上邊的代碼是不支持這樣的寫法,需要進行優化, 既然 第二個參數,是動作,返回的恰好是個對象
可以考慮使用 redux
的 bindActionCreators
這個方法,它剛好適用於這種對象的語法
import React, { PureComponent } from 'react';
import Context from './context';
import { bindActionCreators } from './../redux';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
constructor(props, context) {
super(props);
// 這裏 直接執行 mapStateToProps 函數,把默認的 值返回給回調函數
this.state = mapStateToProps ? mapStateToProps(context.getState()) : context.getState();
}
componentDidMount() {
this.unsubscribe = this.context.subscribe(() =>
this.setState(mapStateToProps(this.context.getState())),
);
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
// 使用 bindActionCreators 集中處理 所有的 actions,
let boundActions = bindActionCreators(
mapDispatchToProps(),
this.context.dispatch,
);
return <WarpComponent {...this.state} {...boundActions} />;
}
};
};
}
所以組件中就簡寫了
import React, { Component } from 'react';
import { ADD1 } from '../reducers/type';
import { connect } from './../react-redux';
class CoutA extends Component {
render() {
const { add, num } = this.props;
return (
<>
<p>CoutA組件:{num}</p>
<button onClick={add}>CoutA組件++</button>
</>
);
}
}
const mapStateToProps = (state) => state.count1;
// 這裏不在dispatch 了
const mapDispatchToProps = () => ({
add() {
return { type: ADD1 };
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);
但是使用官方,它是需要在組件中手動dispacth 的, 所以再接着簡化,改成官方的那種
import React, { PureComponent } from 'react';
import Context from './context';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
componentDidMount() {
this.unsubscribe = this.context.subscribe(() =>
this.setState(mapStateToProps(this.context.getState())),
);
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { getState, dispatch } = this.context;
return (
<WarpComponent
{...(mapStateToProps && mapStateToProps(getState()))}
{...(mapDispatchToProps && mapDispatchToProps(dispatch))}
/>
);
}
};
};
}
這時組件就需要手動 dispatch
了,但是現在還有個問題, 不管哪個組件的數據變化了,每個組件都會再執行一次render
(演示地址代碼,爲了重現這個問題, 沒有改)
那這就需要處理了,因爲原先每次render的時候都新建了函數,所以 PureComponent
淺比較的話, 就是不相等的 , 所以
shoudComponentUpdate
就會返回true,導致每個都會更新
import React, { PureComponent } from 'react';
import Context from './context';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
constructor(props, context) {
super(props);
// 構造函數裏定義死
this.boundActions =
mapDispatchToProps && mapDispatchToProps(context.dispatch);
}
render() {
const { getState } = this.context;
return (
<WarpComponent
{...(mapStateToProps && mapStateToProps(getState()))}
{...this.boundActions}
/>
);
}
};
};
}