Redux 03 React-Redux

Redux 03 React-Redux

连接开始。。。我来组成state,我来组成dispatch。。。。。。

高阶组件、展示型和容器型组件搞清楚了之后,就可以来弄一下这个connect了。

connect要解决的问题,就是第一篇文章末尾那个问题,到底怎么才能让前端渲染的东西和Redux结合起来,store的变化又能反映到展示组件上去。

React-Redux给我们提供了答案,那就是创建一个使用store的高级组件,把store的变化映射到React组件的props上。来看一下React-Redux的秘密.

规范地使用React-Redux

在上一篇的末尾,去掉了所有store相关的代码,为了使用connect,现在要按照比较规范的方式来使用store,也就是React-Redux提供的Provider组件,在上下文的范围内,让所有组件都可以访问到store

使用Provider为其他组件提供全局store

index.js中,使用之前编写好的Reducer来创建store,然后通过Provider组件包裹App组件:

// index.js
import {Provider} from "react-redux";

export const store = createStore(changeReducer, {value: 1});

ReactDOM.render(
    <React.StrictMode>
        <Provider store={store}>
            <App/>
        </Provider>
    </React.StrictMode>,
    document.getElementById('root')
);

ChangeValue.js中做好准备

我们的原本意图,是点击两个按钮向store分派动作,之后让MyResult获取store的最新内容并且予以展示,但是遗憾的是如果不进行特殊的操作(比如像第一篇文章一样加入额外的state),只分派动作并无法让MyResult刷新。

我们先去掉第二篇文章中那些花里胡哨的高阶组件,将ChangeValue.js修改成如下状态:

import React, {Component} from 'react';
import MyButton from "./MyButton";
import MyResult from "./MyResult";
import {store} from "../index";
import {selfDecrement, selfIncrement} from "../action/actionBuilder";

class ChangeValue extends Component {

    constructor(props) {
        super(props);
    }

    handleIncrement = (e) => {
        e.preventDefault();
        store.dispatch(selfIncrement(1));
    }

    handleDecrement = (e) => {
        e.preventDefault();
        store.dispatch(selfDecrement(1));
    }

    render() {

        return (
            <div className="App">
                <MyButton onClick={this.handleIncrement} text={"+1"}/>
                <MyButton onClick={this.handleDecrement} text={"-1"}/>
                <MyResult value=???/>
            </div>
        );
    }
}

export default ChangeValue;

两个按钮现在可以向store派发动作了,现在我们的最大问题就是MyResult需要有一个value来展示,在第一篇中已经知道由于MyResult是无状态组件,store改变并不会让其刷新,从哪里去获取动态的数据并且刷新呢?

connect()函数

这里我们正式请出connect()登场,这个函数是React-redux库的核心。

这个函数的签名是:

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

四个参数,简介如下:

  1. mapStateToProps,把store中存储的state的属性映射成props属性,最终传递给被连接的组件
  2. mapDispatchToProps,把派发动作映射为props属性,最终传递给被连接的组件
  3. mergeProps决定如何处理被连接组件上的各个props属性,如果不提供,实际上相当于把{ ...ownProps, ...stateProps, ...dispatchProps }这个props传递给被连接的组件,即把本来打算传给被连接的组件(现在传递给了连接后的高阶组件)的props,前两个参数新增的props,都直接传递给被连接组件。
  4. 配置,可以参考文档

在connect()函数执行完之后,返回的是一个高阶组件,给这个高阶组件再传入被连接的组件作为参数,就得到连接后的组件,写法如下:

const ConnectedComponent = connect(a,b)(OriginalComponent)

最后实际使用的,就是ConnectedComponent组件。看,这里又是高阶函数柯里化的老套路。

前两个参数非常重要,需要来详细看一下。

mapStateToProps

这个函数的签名是:

mapStateToProps?: (state, ownProps?) => Object​

这个函数的用处,是将storestate的状态变化,通知到被连接的组件,这样在state发生变化的时候,组件也会刷新。其内部实际上是订阅了store的更新。如果这个函数设置为null,那么被连接的组件将不会在store更新的时候得到通知。

第一个参数state,就是store中存储的state,即store.getState()获取的结果。

第二个参数可以不传,如果传递的话,被连接的组件store更新或者自己原本的props更新的时候,都会更新被连接的组件。

这个函数的返回值,必须是一个对象,这个对象会被拆解并传入(合并)到被连接组件的props中。

mapDispatchToProps

这个函数的签名是:

mapDispatchToProps?: Object | (dispatch, ownProps?) => Object

这个函数的用处和上边很类似,只不过映射的不是state,而是各种派发动作,在被连接的组件中调用对应的props就可以来派发传入的动作。

第一个参数是dispatch,这个实际上就是当前全局对象storedispatch,在返回值中就会使用到这个参数。

第二个参数可以不传,如果传递的话,被连接的组件自己原本的props更新的时候,都会重新执行这个函数以更新被连接的组件。

这个函数的返回值,必须是一个对象,自然这个对象也会被拆解并传入(合并)到被连接组件的props中。这个对象要注意,其中每一个键值对的值,是一个派发动作的函数。传入被连接组件之后,被连接组件实际上就可以通过调用传入的函数来派发动作。

其他的东西可以等以后再了解,目前关键就是用好这两个参数。

连接组件

这里我们先连接MyResult组件,然后来连接MyButton组件

连接MyResult组件

要连接MyResult组件,先需要编写符合mapStateToProps签名的函数作为参数,之后将这个函数作为参数来调用connect进行连接,代码如下:

class ChangeValue extends Component {

    ......

    myMapStateToProps = (state) => ({
        value: state.value
    });

    render() {
        const ConnectedMyResult = connect(this.myMapStateToProps, null)(MyResult);

        return (
            <div className="App">
                <MyButton onClick={this.handleIncrement} text={"+1"}/>
                <MyButton onClick={this.handleDecrement} text={"-1"}/>
                <ConnectedMyResult />
            </div>
        );
    }
}

这里需要注意:
myMapStateToProps返回的对象中value: state.value这个键值对,键名是将来传递给MyResult组件的props属性名,所以必须要和MyResult组件中所渲染的<h2>{this.props.value}</h2>一致。

state.value,则是根据创建store时候传入的初始state={value: 1}得来,注意虽然两个都是value名称,一个对应被连接的组件,一个对应state的属性名,千万不要搞错。

如此连接之后,会惊奇的发现,两个按钮派发动作之后,MyResult也会更新了,此时会发现,整个ChangeValue组件完全不需要组件内的state了,也就是说组件的状态都集中到store中去了。

连接MyButton组件

一样的考虑,这次需要编写符合mapDispatchToProps签名的函数来作为connect的参数。

class ChangeValue extends Component {

    ......

    mapIncrementToProps = (dispatch) => ({
        onClick: () => dispatch(selfIncrement(1))
    })

    mapDecrementToProps = (dispatch) => ({
        onClick: () => dispatch(selfDecrement(1))
    })

    render() {
        const ConnectedMyResult = connect(this.myMapStateToProps, null)(MyResult);

        const IncreButton = connect(null, this.mapIncrementToProps)(MyButton);
        const DecreButton = connect(null, this.mapDecrementToProps)(MyButton);

        return (
            <div className="App">
                <IncreButton text={"+1"}/>
                <DecreButton text={"-1"}/>
                <ConnectedMyResult />
            </div>
        );
    }
}

这里编写了两个map函数,分别将onClick属性对应到自增和自减两个派发动作函数上,最终传递给MyButton,而MyButton中本来就是拆解所有属性到button元素上,所以button元素的onClick属性就是对应的派发动作函数,使用了高阶组件之后,无需像原来再给MyButton传递onClick属性,这个动作在连接的时候已经做掉了。

回顾一下我们做的所有事情。

  1. 按照Redux的框架要求,编写了action,创建action的函数,reducer,并最终创建了一个store用于存放状态。
  2. 按照React的框架要求编写了两个按钮和一个展示结果组件,这两个组件本身都是无状态组件
  3. 使用React-Redux将两个按钮连接到派发事件函数,将展示结果组件连接到store变动。

这样本来没有关系的ReactRedux,就实现了UI事件派发动作,store更新之后刷新UI这样一个操作,就这个例子来说,与组件内保存state的区别就是状态集中存放到了另外一个地方。再有其他的组件需要操作store,只需要再进行连接即可,达到了状态集中管理的目的。

还记得容器型组件和展示型组件吗,connect之后形成的高阶组件,就是容器型组件,而展示型组件就是被连接的MyButton之流。

不错不错,趁热打铁终于搞清楚了connect(),美美睡上一觉,再去看Redux剩下的部分,比如中间件,拆分reducerenhancer之类,也都是比较高级的内容呢。

LICENSED UNDER CC BY-NC-SA 4.0
Comment