以前咱们实现了 redux 的功能,此次咱们来实现一下配合 redux 开发中常常会用到的一个库—— react-redux。本文不会详细介绍 react-redux 的使用,另外须要了解 Context API, 再看此文就很容易理解了。能够看看我以前写的几篇文章javascript
用个简单的加减数字做例子, 把代码贴出来:java
import { createStore } from 'redux' // actions const ADD_NUM = 'ADD_NUM' const DESC_NUM = 'DESC_NUM' // action creators export function addNumAction() { return { type: ADD_NUM, } } export function reduceNumAction() { return { type: DESC_NUM, } } const defaultState = { num: 0, } // reducer 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.jsreact
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.jsredux
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 的增减:segmentfault
其实一个简单的 react-redux, 主要也就是实现 connect 和 Provider 的基本功能dom
首先咱们看它的用法,就知道它不是一个函数,而是一个组件:ide
<Provider store={store}> <Demo /> </Provider>
React 的 Context API 提供了一种经过组件树传递数据的方法,无需在每一个级别手动传递 props 属性。函数
Provider 的实现比较简单,核心就是把 store 放到 context 里面,全部的子元素能够直接取到 store。this
export class Provider extends Component { static childContextTypes = { store: PropTypes.object } constructor(props) { super(props) this.store = props.store // 也就是 Provider 组件从外部传入的 store } getChildContext() { return { store: this.store } } render() { return this.props.children // 中间包的组件传入了 context,其他原封不动返回组件 } }
还有个地方你们知道就好,两种写法同样的,对context type 的约束spa
export class Provider extends Component { // 不用 static 来声明... } Provider.childContextTypes = { store: PropTypes.object }
connect用法
export default connect(mapStateToProps, mapDispatchToProps)(Demo)
connect 是一个高阶组件,就是以组件做为参数,返回一个组件。
connect 负责链接组件,给到 redux 的数据放到组件的属性里
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()) // 传入 redux 的 state let dispatchToProps = mapDispatchToProps(store.dispatch) // 传入 redux 的 dispatch this.setState({ props: { ...this.state.props, ...stateToProps, ...dispatchToProps, }, }) } render() { return <WrapComponent {...this.state.props} /> } } } }
这样 connect 就实现了,但还有一个问题,像上面的例子,咱们其实能够直接传入 action creators, 而不用本身定义函数传入 dispatch
export default connect(mapStateToProps, { addNumAction, reduceNumAction })(Demo)
调用的时候:
<button onClick={this.props.addNumAction}>增长1</button> <button onClick={this.props.reduceNumAction}>减小1</button>
那它的 dispatch 哪里来的,实际上是用了 redux 的 bindActionCreators 函数,在我介绍 redux 的文章有提到,它做用是将 actionCreator 转化成 dispatch 形式,即
{ addNumAction } => (...args) => dispatch(addNumAction(args))
因此咱们须要再更改 connect 函数,同时,此次咱们用箭头函数的形式简化代码
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 { // 传递了一个 actionCreator 对象过来 dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch) } this.setState({ props: { ...this.state.props, ...stateToProps, ...dispatchToProps, }, }) } render() { return <WrapComponent {...this.state.props} /> } } }
以上,咱们实现了最基本版的 react-redux,而后接下来,咱们用新版的 Context API 再写一次
import React, { Component } from 'react' import { bindActionCreators } from 'redux' const StoreContext = React.createContext(null) // 此次 Provider 采起更简洁的形式写 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 { // 传递了一个 actionCreator 对象过来 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> ) } }