之前我们实现了 redux 的功能,这次我们来实现一下配合 redux 开发中经常会用到的一个库—— react-redux。本文不会详细介绍 react-redux 的使用,另外需要了解 Context API, 再看此文就很容易理解了。
前言
可以看看我之前写的几篇文章
react-redux 基本使用
用个简单的加减数字作例子, 把代码贴出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import { createStore } from 'redux'
const ADD_NUM = 'ADD_NUM' const DESC_NUM = 'DESC_NUM'
export function addNumAction() { return { type: ADD_NUM, } }
export function reduceNumAction() { return { type: DESC_NUM, } }
const defaultState = { num: 0, }
const reducer = (state = defaultState, action) => { switch (action.type) { case ADD_NUM: return { num: state.num + 1 } case DESC_NUM: return { num: state.num - 1 } default: return state } }
const store = createStore(reducer)
export default store
|
index.js
1 2 3 4 5 6 7 8 9 10 11 12
| import React from 'react' import ReactDOM from 'react-dom' import Demo from './Demo' import { Provider } from 'react-redux' import store from './redux.js'
ReactDOM.render( <Provider store={store}> <Demo /> </Provider>, document.getElementById('root') )
|
Demo.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import React, { Component } from 'react' import { connect } from 'react-redux' import { addNumAction, reduceNumAction } from './redux.js'
class Demo extends Component { render() { console.log(this.props) return ( <div> <p>{this.props.num}</p> <button onClick={this.props.addNum}>增加1</button> <button onClick={this.props.reduceNum}>减少1</button> </div> ) } }
const mapStateToProps = state => { return { num: state.num, } }
const mapDispatchToProps = dispatch => { return { addNum() { const action = addNumAction() dispatch(action) }, reduceNum() { const action = reduceNumAction() dispatch(action) }, } }
export default connect(mapStateToProps, mapDispatchToProps)(Demo)
|
就可以实现 num 的增减:
其实一个简单的 react-redux, 主要也就是实现 connect 和 Provider 的基本功能
- connect:可以把 state 和 dispatch 绑定到 react 组件,使得组件可以访问到 redux 的数据
- Provider:提供的是一个顶层容器的作用,实现 store 的上下文传递
使用旧版 Context API 实现
实现 Provider
首先我们看它的用法,就知道它不是一个函数,而是一个组件:
1 2 3
| <Provider store={store}> <Demo /> </Provider>
|
React 的 Context API 提供了一种通过组件树传递数据的方法,无需在每个级别手动传递 props 属性。
Provider 的实现比较简单,核心就是把 store 放到 context 里面,所有的子元素可以直接取到 store。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export class Provider extends Component { static childContextTypes = { store: PropTypes.object, }
constructor(props) { super(props) this.store = props.store }
getChildContext() { return { store: this.store } }
render() { return this.props.children } }
|
还有个地方大家知道就好,两种写法一样的,对 context type 的约束
1 2 3 4 5 6
| export class Provider extends Component { } Provider.childContextTypes = { store: PropTypes.object, }
|
实现 connect
connect 用法
1
| export default connect(mapStateToProps, mapDispatchToProps)(Demo)
|
connect 是一个高阶组件,就是以组件作为参数,返回一个组件。
connect 负责连接组件,给到 redux 的数据放到组件的属性里
- 负责接收一个组件,把 state 的一些数据放进去,返回一个组件
- 数据变化的时候,能够通知组件(需要进行监听)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| export function connect(mapStateToProps, mapDispatchToProps) { return function (WrapComponent) { return class ConnectComponent extends Component { static contextTypes = { store: PropTypes.object, }
constructor(props) { super(props) this.state = { props: {}, } }
componentDidMount() { const { store } = this.context store.subscribe(() => this.update()) this.update() }
update() { const { store } = this.context let stateToProps = mapStateToProps(store.getState()) let dispatchToProps = mapDispatchToProps(store.dispatch)
this.setState({ props: { ...this.state.props, ...stateToProps, ...dispatchToProps, }, }) }
render() { return <WrapComponent {...this.state.props} /> } } } }
|
这样 connect 就实现了,但还有一个问题,像上面的例子,我们其实可以直接传入 action creators, 而不用自己定义函数传入 dispatch
1
| export default connect(mapStateToProps, { addNumAction, reduceNumAction })(Demo)
|
调用的时候:
1 2
| <button onClick={this.props.addNumAction}>增加1</button> <button onClick={this.props.reduceNumAction}>减少1</button>
|
那它的 dispatch 哪里来的,其实是用了 redux 的 bindActionCreators 函数,在我介绍 redux 的文章有提到,它作用是将 actionCreator 转化成 dispatch 形式,即
1
| { addNumAction } => (...args) => dispatch(addNumAction(args))
|
所以我们需要再更改 connect 函数,同时,这次我们用箭头函数的形式简化代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { bindActionCreators } from 'redux'
export const connect = (mapStateToProps, mapDispatchToProps) => WrapComponent => { return class ConnectComponent extends Component { static contextTypes = { store: PropTypes.object, }
constructor(props) { super(props) this.state = { props: {}, } }
componentDidMount() { const { store } = this.context store.subscribe(() => this.update()) this.update() }
update() { const { store } = this.context let stateToProps = mapStateToProps(store.getState()) let dispatchToProps if (typeof mapDispatchToProps === 'function') { dispatchToProps = mapDispatchToProps(store.dispatch) } else { dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch) }
this.setState({ props: { ...this.state.props, ...stateToProps, ...dispatchToProps, }, }) }
render() { return <WrapComponent {...this.state.props} /> } } }
|
以上,我们实现了最基本版的 react-redux,然后接下来,我们用新版的 Context API 再写一次
使用新版 Context API 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import React, { Component } from 'react' import { bindActionCreators } from 'redux'
const StoreContext = React.createContext(null)
export class Provider extends Component { render() { return <StoreContext.Provider value={this.props.store}>{this.props.children}</StoreContext.Provider> } }
export function connect(mapStateToProps, mapDispatchToProps) { return function (WrapComponent) { class ConnectComponent extends Component { constructor(props) { super(props) this.state = { props: {}, } }
componentDidMount() { const { store } = this.props store.subscribe(() => this.update()) this.update() }
update() { const { store } = this.props let stateToProps = mapStateToProps(store.getState()) let dispatchToProps if (typeof mapDispatchToProps === 'function') { dispatchToProps = mapDispatchToProps(store.dispatch) } else { dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch) }
this.setState({ props: { ...this.state.props, ...stateToProps, ...dispatchToProps, }, }) }
render() { return <WrapComponent {...this.state.props} /> } }
return () => <StoreContext.Consumer>{value => <ConnectComponent store={value} />}</StoreContext.Consumer> } }
|