来自 Web前端 2020-04-29 17:43 的文章
当前位置: 网上澳门金莎娱乐 > Web前端 > 正文

动手实现一个react-redux

时间: 2019-09-08阅读: 115标签: reduxreact-redux 是什么

一、安装相关包

react-redux是redux官方React绑定库。它帮助我们连接UI层和数据层。本文目的不是介绍react-redux的使用,而是要动手实现一个简易的react-redux,希望能够对你有所帮助。

  

首先思考一下,倘若不使用react-redux,我们的react项目中该如何结合redux进行开发呢。

npm install redux react-redux --save

每个需要与redux结合使用的组件,我们都需要做以下几件事:在组件中获取store中的状态监听store中状态的改变,在状态改变时,刷新组件在组件卸载时,移除对状态变化的监听。

  

如下:

二、根据具体情形创建模块文件

import React from 'react';import store from '../store';import actions from '../store/actions/counter';/** * reducer 是 combineReducer({counter, ...}) * state 的结构为 * { * counter: {number: 0}, * .... * } */class Counter extends React.Component { constructor(props) { super(props); this.state = { number: store.getState().counter.number } } componentDidMount() { this.unsub = store.subscribe(() = { if(this.state.number === store.getState().counter.number) { return; } this.setState({ number: store.getState().counter.number }); }); } render() { return ( div p{`number: ${this.state.number}`}/p button onClick={() = {store.dispatch(actions.add(2))}}+/button button onClick={() = {store.dispatch(actions.minus(2))}}-/button div ) } componentWillUnmount() { this.unsub(); }}

  Store.js、Reducer.js、Actions.js

如果我们的项目中有很多组件需要与redux结合使用,那么这些组件都需要重复写这些逻辑。显然,我们需要想办法复用这部分的逻辑,不然会显得我们很蠢。我们知道,react中高阶组件可以实现逻辑的复用。

  Store.js的作用在于引出store,方便在使用的时候引入。

文中所用到的Counter代码在中的myreact-redux/counter中,建议先clone代码,当然啦,如果觉得本文不错的话,给个star鼓励。

  Reducer.js的作用在于定义reducer,最好用combineReducers拆分reducer。

逻辑复用

  Actions.js用于定义ActionCreator,方便在dispatch action的时候根据不同变量生成不同action。

在src目录下新建一个react-redux文件夹,后续的文件都新建在此文件夹中。

 

创建 connect.js 文件

三、使用Provider组件以便于所有组件都能直接使用store

文件创建在react-redux/components文件夹下:

  在使用根组件(通常是入口文件index.js)处引入Provider组件。

我们将重复的逻辑编写connect中。

  在index.js中引入store,

import React, { Component } from 'react';import store from '../../store';export default function connect (WrappedComponent) { return class Connect extends Component { constructor(props) { super(props); this.state = store.getState(); } componentDidMount() { this.unsub = store.subscribe(() = { this.setState({ this.setState(store.getState()); }); }); } componentWillUnmount() { this.unsub(); } render() { return ( WrappedComponent {...this.state} {...this.props}/ ) } }}

  在ReactDOM.render()方法的第一个参数处使用Provider包裹根组件,

有个小小的问题,尽管这逻辑是重复的,但是每个组件需要的数据是不一样的,不应该把所有的状态都传递给组件,因此我们希望在调用connect时,能够将需要的状态内容告知connect。另外,组件中可能还需要修改状态,那么也要告诉connect,它需要派发哪些动作,否则connect无法知道该绑定那些动作给你。

  然后给Provider组件的store属性赋值为之前引入的store。

为此,我们新增两个参数:mapStateToProps和mapDispatchToProps,这两个参数负责告诉connect组件需要的state内容和将要派发的动作。

  

mapStateToProps 和 mapDispatchToProps

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import store from './Store';

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

我们知道mapStateToProps和mapDispatchToProps的作用是什么,但是目前为止,我们还不清楚,这两个参数应该是一个什么样的格式传递给connect去使用。

  

import { connect } from 'react-redux';....//connect 的使用export default connect(mapStateToProps, mapDispatchToProps)(Counter);

 

mapStateToProps 告诉connect,组件需要绑定的状态。

四、编写子组件

mapStateToProps需要从整个状态中挑选组件需要的状态,但是在调用connect时,我们并不能获取到store,不过connect内部是可以获取到store的,为此,我们将mapStateToProps定义为一个函数,在connect内部调用它,将store中的state传递给它,然后将函数返回的结果作为属性传递给组件。组件中通过this.props.XXX来获取。因此,mapStateToProps的格式应该类似下面这样:

  先写UI Component,即通常所写的普通组件;

//将 store.getState() 传递给 mapStateToPropsmapStateToProps = state = ({ number: state.counter.number});

  然后定义Container Component,使用connect方法(从react-redux包中引入)即可将UI Component转化为Container Component。

mapDispatchToProps 告诉connect,组件需要绑定的动作。

  注意要传递mapStateToProps、mapDispatchToProps两个参数。

回想一下,组件中派发动作:store.dispatch({actions.add(2)})。connect包装之后,我们仍要能派发动作,肯定是this.props.XXX()这样的一种格式。

       最后导出组件以便于使用的时候引入。

比如,计数器的增加,调用this.props.add(2),就是需要派发store.dispatch({actions.add(2)}),因此add属性,对应的内容就是(num) = { store.dispatch({actions.add(num)}) }。传递给组件的属性类似下面这样:

  

{ add: (num) = { store.dispatch(actions.add(num)) }, minus: (num) = { store.dispatch(actions.minus(num)) }}
import React from 'react';
import { connect } from 'react-redux';
import { toggleItemAction, removeItemAction } from './Reducer';

class Display extends React.Component {
    componentWillMount(){
        this.toggleCompleted = this.props.toggleCompleted;
        this.removeItem = this.props.removeItem;
        console.log(this.props);
    }
    render(){
        return <div>
            <ul>
                {
                    this.props.items.map(item => {
                        let tmp = null;
                        if(item.completed){
                            tmp = <del>{item.value}</del>
                        }
                        else {
                            tmp = item.value;
                        }
                        if(tmp === null || tmp ===''){
                            return null;
                        }
                        return <li key={item.id}>
                            {tmp}
                            <button id={'toggle' + item.id} onClick={this.toggleCompleted}>Toggle</button>
                            <button id={'remove' + item.id} onClick={this.removeItem}>Remove</button>
                        </li>
                    })
                }
            </ul>
        </div>
    }
}

const mapStateToProps = (state) => {
    return {
        items: state.items
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        toggleCompleted(e){
            let id = e.target.id.substring(6);
            dispatch(toggleItemAction(id));
        },
        removeItem(e){
            let id = e.target.id.substring(6);
            dispatch(removeItemAction(id));
        }
    };
};

const DisplayContainer = connect(mapStateToProps, mapDispatchToProps)(Display);

export default DisplayContainer;

和mapStateToProps一样,在调用connect时,我们并不能获取到store.dispatch,因此我们也需要将mapDispatchToProps设计为一个函数,在connect内部调用,这样可以将store.dispatch传递给它。所以,mapStateToProps应该是下面这样的格式:

  

//将 store.dispacth 传递给 mapDispatchToPropsmapDispatchToProps = (dispatch) = ({ add: (num) = { dispatch(actions.add(num)) }, minus: (num) = { dispatch(actions.minus(num)) }})

五、注意点

至此,我们已经搞清楚mapStateToProps和mapDispatchToProps的格式,是时候进一步改进connect了。

  1.mapStateToProps参数其实是一个函数,默认带有一个参数为state,作用是将state中的值映射到我们定义的组件的props属性中。

connect 1.0 版本

   一下定义一个mapStateToProps:

import React, { Component } from 'react';import store from '../../store';export default function connect (mapStateToProps, mapDispatchToProps) { return function wrapWithConnect (WrappedComponent) { return class Connect extends Component { constructor(props) { super(props); this.state = mapStateToProps(store.getState()); this.mappedDispatch = mapDispatchToProps(store.dispatch); } componentDidMount() { this.unsub = store.subscribe(() = { const mappedState = mapStateToProps(store.getState()); //TODO 做一层浅比较,如果状态没有改变,则不setState this.setState(mappedState); }); } componentWillUnmount() { this.unsub(); } render() { return ( WrappedComponent {...this.props} {...this.state} {...this.mappedDispatch} / ) } } }}

    

我们知道,connect是作为react-redux库的方法提供的,因此我们不可能直接在connect.js中去导入store,这个store应该由使用react-redux的应用传入。react中数据传递有两种:通过属性props或者是通过上下文对象context,通过connect包装的组件在应用中分布,而context设计目的是为了共享那些对于一个组件树而言是“全局”的数据。

const mapStateToProps = (state) => {
    return {
        items: state.items
    };
};

我们需要把store放在context上,这样根组件下的所有子孙组件都可以获取到store。这部分内容,我们当然可以自己在应用中编写相应代码,不过很显然,这些代码在每个应用中都是重复的。因此我们把这部分内容也封装在react-redux内部。

  这样的话,在组件中就可以通过this.props.items获取到state.items。

此处,我们使用旧的Context API来写(鉴于我们实现的 react-redux 4.x 分支的代码,因此我们使用旧版的 context API)。

  mapStateToProps函数所返回的对象可以认为是组件props属性的一个子集。

Provider

 

我们需要提供一个Provider组件,它的功能就是接收应用传递过来的store,将其挂在context上,这样它的子孙组件就都可以通过上下文对象获取到store。

  2.mapDispatchToProps参数是一个对象或者函数,我习惯于写成一个函数,函数具有一个默认属性即为dispatch,相当于store.dispatch(),mapDispatchToProps函数会返回一          个对象。对象中的所有方法都会传递给组件的props属性。也可以认为它所返回的对象是组件props属性的一个子集。

新建 Provider.js 文件

const mapDispatchToProps = (dispatch) => {
    return {
        toggleCompleted(e){
            let id = e.target.id.substring(6);
            dispatch(toggleItemAction(id));
        },
        removeItem(e){
            let id = e.target.id.substring(6);
            dispatch(removeItemAction(id));
        }
    };
};

文件创建在react-redux/components文件夹下:

  以上代码定义了一个mapDispatchToProps函数,这样,在组件内部可以通过this.props[propertyName]访问到toggleCompleted函数或者removeItem函数。

import React, { Component } from 'react';import PropTypes from 'prop-types';export default class Provider extends Component { static childContextTypes = { store: PropTypes.shape({ subscribe: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired, getState: PropTypes.func.isRequired }).isRequired } constructor(props) { super(props); this.store = props.store; } getChildContext() { return { store: this.store } } render() { /** * 早前返回的是 return Children.only(this.props.children) * 导致Provider只能包裹一个子组件,后来取消了此限制 * 因此此处,我们直接返回 this.props.children */ return this.props.children }}

 

新建一个 index.js 文件

  3.如何在mapDispatchToProps函数定义的一系列函数中获取组件内部数据?

文件创建在react-redux目录下:

  解决方法有两种:

此文件只做一件事,即将connect和Provider导出

  (1)通过event.target来获取事件发生节点,然后通过DOM属性获取组件内部的数据。注意点2中的代码即是例子。

import connect from './components/connect';import Provider from './components/Provider';export { connect, Provider}

  (2)在组件内部定义事件处理函数,不直接利用this.props[propertyName]来处理组件事件。组件内部自定义事件处理函数可以通过this等获取到组件内部数据,然后在组件自定义事件处理函数内部调用this.props[propertyName],传递参数即可。

Provider 的使用

 

本文由网上澳门金莎娱乐发布于Web前端,转载请注明出处:动手实现一个react-redux

关键词: