手拉手学React:Redux

redux中文网

何为Redux

Redux 是 JavaScript 状态容器,用来存储项目中一些公共的数据,做到数据“一处存储,多处取出”的事情,跟 Vue 的 Vuex 属于一类角色。

它的诞生是为了解决用 React 开发大型项目中解决不了难题。如果你的项目中组件繁多,而且组件之间的通信又必不可少,这个时候 Redux 能很好的解决这个问题。

举个例子,原本组件通信是只有父子通信,如果爷孙俩通信就需要借助“父”来实现:

没有Redux时的组件通信

问题就在于,如果组件非常多,数以千计,再通信性能就非常低,而且也不方便。那这个时候,把大家都需要的数据放在一个公共的地方,谁要谁就去取:

有Redux的通信

这样,不管有多少个组件、组件的层次有多深,都能快速的拿到需要拿的数据。

Redux 工作流程

开篇一张图,内容全靠编:

Store 就相当于一个房地产中介一样,我们想要一个地区的所有房源信息,就需要通过它来获取,Reducers 是幕后人,中介的信息来源都是Reducers告诉它的,比喻成一个“房产资源手册”可能更合适。

中介给了用户三个行为:

  • getState()普通用户级别专门获取房源信息的唯一行为。
  • dispatch(action)房东用户级别通知中介,改变自家房源信息(state)的唯一行为。
  • subscribe()普通用户级别如果相中了一套房子,并且告诉中介:“我对你这的房源信息比较感兴趣,你这房源有啥新动向第一时间告诉我一下”,这样就能第一时间收到房源信息(state)改变的消息。

介绍完这些之后,咱们把整个流程串一下:

  • 普通用户(React Components):通过getState()这一行为向中介(store)拿(注意是,不是拿到)最新的房源信息,中介接受到消息后,再通过房产资源手册(Reducers)拿到最新的房源信息并反馈给用户。所以,用户最终的信息来源是通过Reducers获取到的。
  • 房东用户(React Components):通过dispatch(action)这一行为向中介发出信息,说:“我要修改我家房源的一些信息,你帮我弄一下”,其中,action.type是必不可少的,这是告诉中介自己房源的标识。然后,中介再把这些信息传给房产资源手册Reducers,然后在手册上对应的位置(该位置通过action.type找到)把对应的数据更改了,最后中介通知已经订阅的用户房源已经更改了(subscribe())。这个角色拥有两种身份:普通用户房东用户,它既能通过dispatch()修改自己发布到中介store上的房源信息,也能像普通用户一样订阅、获取房源的一切信息。在我这里,一个组件调用了dispatch()就视为房东用户,否则就是普通用户

这中间也有一些限制,比如action必须拥有type字段,其他属性可能由用户自定义,reducer是一个纯函数,应当遵循纯函数的一些设计理念(不能影响函数的输出)。

不得改写参数。
不能有异步操作。
不能调用Date.now()或者Math.random()等不纯的方法。

雇佣中介,自当用户

现在咱们用代码完成以上所述,第一步是需要有中介(store),才会有用户,所以先创建中介(store):

// /store/index.js
import { createStore } from 'redux';
import reducer from './reducer'; // 同时把房产资源手册(reducer)交给中介

const store = createStore(reducer);

export default store;
// /store/reducer.js
/**
 * 每次用户手动调用dispatch的时候,会自动调用这个方法
 *
 * <strong>这个方法只能接受state,绝不能修改state,这是明文限制!</strong>
 * @param {Object} state 上一个未改变的state
 * @param {Object} action 当前用户传递过来的action
 * @param {String} action.type action的类型,通过这个类型,得知用户需要改动哪个变量
 * @return {{inputValue: string, list: Array}}
 */
export default function (state = defaultState, action) {
    const newState = JSON.parse(JSON.stringify(state));
    if (action.type === 'CHANGE_INPUT_VALUE') {
        /** @namespace action.value */
        newState.inputValue = action.value;
    } else if (action.type === 'LIST_ADD') {
        newState.list.push(newState.inputValue);
        newState.inputValue = '';
    }
    return newState;
}

先不看房产资源手册(reducer)做了什么事情,第二步就是建立自己的用户(React Components):

// /Home.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from '../store'; // 第一步:引入中介(store)

class Home extends Component {
    constructor(...args) {
        super(...args);
        this.state = store.getState(); // 第二步:得到全部房源信息
        store.subscribe(() => { // 第三步:这个用户(React components)需要第一时间知道房源的最新动向
            this.setState(store.getState());
        }); // 订阅store,store数据发生改变的时候需要做的事情,这个方法同时会返回一个方法,用来取消订阅
    }
    inputChange = (e) => {
        const action = {
            type: 'CHANGE_INPUT_VALUE',
            value: e.target.value
        };
        store.dispatch(action);
    };
    btnSubmit = () => {
        store.dispatch({
            type: 'LIST_ADD'
        }) // 第四步:升级为房东,通知中介(store)修改自己的房源信息,这里对应房产资源手册的代码逻辑
    };
    render() {
        return (
            <div style={{ margin: '10px' }}>
                <Input
                    value={this.state.inputValue}
                    placeholder="Basic usage"
                    style={{ width: '300px' }}
                    onChange={this.inputChange}
                />
                <Button type="primary" style={{ marginLeft: '10px' }} onClick={this.btnSubmit}>提交</Button>
                <List
                    style={{ marginTop: '10px', width: '300px' }}
                    bordered
                    dataSource={this.state.list}
                    renderItem={item => (<List.Item>{item}</List.Item>)}
                />
            </div>
        );
    }
}

export default Home;

代码优化

  • 需要将上面action.type值单独提出来放在一个常量文件里,组件中和reducer里就更不容易出错。
  • 统一创建action
// actionCreateor.js
const defaultField = 'value'; // action默认的扩展字段
/**
 * 统一创建action
 * @param {String} type 类型
 * @param {any} value 值
 */
export function createAction(type, value) {
    if (!type) throw new Error('【非法参数】type必须是一个合法的参数值,当前type值是:' + type);
    let action = { type };
    switch (typeof value) {
        case 'object': {
            action = Object.assign(action, value);
            break;
        }
        default: {
            action[defaultField] = value;
        }
    }
    return action;
}
  • 使用combineReducers拆分reducers
    这就是一个基本的redux。
Last modification:February 8th, 2019 at 09:43 pm
If you think my article is useful to you, please feel free to appreciate

3 comments

  1. 知识共享网

    不错,学习了,谢谢小晨

  2. 文娱帝国

    写的很好,很喜欢

  3. 搬瓦工

    学习来了

Leave a Comment