摘要: Redux 是「React 全家桶」中極為重要的一員,它試圖為 React 應用提供「可預測化的狀態管理」機制。Redux 本身足夠簡單,除了 React,它還能夠支持其他界面框架。所以如果要將 Redux 和 React 結合起來使用,就還需要一些額外的工具,其中最重要的莫過于 react-redux
react-redux 提供了兩個重要的對象,Provider 和 connect,前者使 React 組件可被連接(connectable),后者把 React 組件和 Redux 的 store 真正連接起來。react-redux 的文檔中,對 connect 的描述是一段晦澀難懂的英文,在初學 redux 的時候,我對著這段文檔閱讀了很久,都沒有全部弄明白其中的意思(大概就是,單詞我都認識,連起來啥意思就不明白了的感覺吧)。
在使用了一段時間 redux 后,本文嘗試再次回到這里,給這段文檔(同時摘抄在附錄中)一個靠譜的解讀。
預備知識
首先回顧一下 redux 的基本用法。如果你還沒有閱讀過 redux 的文檔,你一定要先去閱讀一下。
const reducer = (state = {count: 0}, action) => {
switch (action.type){
case 'INCREASE': return {count: state.count + 1};
case 'DECREASE': return {count: state.count - 1};
default: return state;
}
}
const actions = {
increase: () => ({type: 'INCREASE'}),
decrease: () => ({type: 'DECREASE'})
}
const store = createStore(reducer);
store.subscribe(() =>
console.log(store.getState())
);
store.dispatch(actions.increase()) // {count: 1}
store.dispatch(actions.increase()) // {count: 2}
store.dispatch(actions.increase()) // {count: 3}
通過 reducer 創建一個 store,每當我們在 store 上 dispatch 一個 action,store 內的數據就會相應地發生變化。
我們當然可以直接在 React 中使用 Redux:在最外層容器組件中初始化 store,然后將state 上的屬性作為 props 層層傳遞下去。
class App extends Component{
componentWillMount(){
store.subscribe((state)=>this.setState(state))
}
render(){
return <Comp state={this.state}
onIncrease={()=>store.dispatch(actions.increase())}
onDecrease={()=>store.dispatch(actions.decrease())}
/>
}
}
但這并不是最佳的方式。最佳的方式是使用 react-redux 提供的 Provider 和 connect 方法。
使用 react-redux
首先在最外層容器中,把所有內容包裹在 Provider 組件中,將之前創建的 store 作為prop 傳給 Provider。
const App = () => {
return (
<Provider store={store}>
<Comp/>
</Provider>
)
};
Provider 內的任何一個組件(比如這里的 Comp),如果需要使用 state 中的數據,就必須是「被 connect 過的」組件——使用 connect 方法對「你編寫的組件(MyComp)」進行包裝后的產物。
class MyComp extends Component {
// content...
}
const Comp = connect(...args)(MyComp);
可見,connect 方法是重中之重。
connect 詳解
究竟 connect 方法到底做了什么,我們來一探究竟。
首先看下函數的簽名:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect() 接收四個參數,它們分別是 mapStateToProps,mapDispatchToProps,mergeProps和options。
mapStateToProps(state, ownProps) : stateProps
這個函數允許我們將 store 中的數據作為 props 綁定到組件上。
const mapStateToProps = (state) => {
return {
count: state.count
}
}
這個函數的第一個參數就是 Redux 的 store,我們從中摘取了 count 屬性。因為返回了具有 count 屬性的對象,所以 MyComp 會有名為 count 的 props 字段。
class MyComp extends Component {
render(){
return <div>計數:{this.props.count}次</div>
}
}
const Comp = connect(...args)(MyComp);
當然,你不必將 state 中的數據原封不動地傳入組件,可以根據 state 中的數據,動態地輸出組件需要的(最?。傩?。
const mapStateToProps = (state) => {
return {
greaterThanFive: state.count > 5
}
}
函數的第二個參數 ownProps,是 MyComp 自己的 props。有的時候,ownProps 也會對其產生影響。比如,當你在 store 中維護了一個用戶列表,而你的組件 MyComp 只關心一個用戶(通過 props 中的 userId 體現)。
const mapStateToProps = (state, ownProps) => {
// state 是 {userList: [{id: 0, name: '王二'}]}
return {
user: _.find(state.userList, {id: ownProps.userId})
}
}
class MyComp extends Component {
static PropTypes = {
userId: PropTypes.string.isRequired,
user: PropTypes.object
};
render(){
return <div>用戶名:{this.props.user.name}</div>
}
}
const Comp = connect(mapStateToProps)(MyComp);
當 state 變化,或者 ownProps 變化的時候,mapStateToProps 都會被調用,計算出一個新的stateProps,(在與 ownProps merge 后)更新給 MyComp。
這就是將 Redux store 中的數據連接到組件的基本方式。
mapDispatchToProps(dispatch, ownProps): dispatchProps
connect 的第二個參數是 mapDispatchToProps,它的功能是,將 action 作為 props 綁定到MyComp 上。
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)),
decrease: (...args) => dispatch(actions.decrease(...args))
}
}
class MyComp extends Component {
render(){
const {count, increase, decrease} = this.props;
return (<div>
<div>計數:{this.props.count}次</div>
<button onClick={increase}>增加</button>
<button onClick={decrease}>減少</button>
</div>)
}
}
const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp);
由于 mapDispatchToProps 方法返回了具有 increase 屬性和 decrease 屬性的對象,這兩個屬性也會成為 MyComp 的 props。
如上所示,調用 actions.increase() 只能得到一個 action 對象 {type:'INCREASE'},要觸發這個 action 必須在 store 上調用 dispatch 方法。diapatch 正是 mapDispatchToProps 的第一個參數。但是,為了不讓 MyComp 組件感知到 dispatch 的存在,我們需要將 increase 和decrease 兩個函數包裝一下,使之成為直接可被調用的函數(即,調用該方法就會觸發dispatch)。
Redux 本身提供了 bindActionCreators 函數,來將 action 包裝成直接可被調用的函數。
import {bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch, ownProps) => {
return bindActionCreators({
increase: action.increase,
decrease: action.decrease
});
}
同樣,當 ownProps 變化的時候,該函數也會被調用,生成一個新的 dispatchProps,(在與statePrope 和 ownProps merge 后)更新給 MyComp。注意,action 的變化不會引起上述過程,默認 action 在組件的生命周期中是固定的。
[mergeProps(stateProps, dispatchProps, ownProps): props]
之前說過,不管是 stateProps 還是 dispatchProps,都需要和 ownProps merge 之后才會被賦給 MyComp。connect 的第三個參數就是用來做這件事。通常情況下,你可以不傳這個參數,connect 就會使用 Object.assign 替代該方法。
其他
最后還有一個 options 選項,比較簡單,基本上也不大會用到(尤其是你遵循了其他的一些 React 的「最佳實踐」的時候),本文就略過了。希望了解的同學可以直接看文檔。
(完)
附:connect 方法的官方英文文檔
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
Connects a React component to a Redux store.
It does not modify the component class passed to it. Instead, it returns a new, connected component class, for you to use.
Arguments
[mapStateToProps(state, [ownProps]): stateProps] (Function): If specified, the component will subscribe to Redux store updates. Any time it updates, mapStateToProps will be called. Its result must be a plain object*, and it will be merged into the component’s props. If you omit it, the component will not be subscribed to the Redux store. If ownProps is specified as a second argument, its value will be the props passed to your component, and mapStateToProps will be additionally re-invoked whenever the component receives new props (e.g. if props received from a parent component have shallowly changed, and you use the ownProps argument, mapStateToProps is re-evaluated).
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it will be assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props. If a function is passed, it will be given dispatch. It’s up to you to return an object that somehow uses dispatch to bind action creators in your own way. (Tip: you may use the bindActionCreators() helper from Redux.) If you omit it, the default implementation just injects dispatch into your component’s props. If ownProps is specified as a second argument, its value will be the props passed to your component, and mapDispatchToProps will be re-invoked whenever the component receives new props.
[mergeProps(stateProps, dispatchProps, ownProps): props] (Function): If specified, it is passed the result of mapStateToProps(), mapDispatchToProps(), and the parent props. The plain object you return from it will be passed as props to the wrapped component. You may specify this function to select a slice of the state based on props, or to bind action creators to a particular variable from props. If you omit it, Object.assign({}, ownProps, stateProps, dispatchProps) is used by default.
[options] (Object) If specified, further customizes the behavior of the connector.
[pure = true] (Boolean): If true, implements shouldComponentUpdate and shallowly compares the result of mergeProps, preventing unnecessary updates, assuming that the component is a “pure” component and does not rely on any input or state other than its props and the selected Redux store’s state. Defaults to true.
[withRef = false] (Boolean): If true, stores a ref to the wrapped component instance and makes it available via getWrappedInstance() method. Defaults to false.
轉載自:http://taobaofed.org/blog/2016/08/18/react-redux-connect/
作者: 葉齋