手拉手学React:Redux

redux中文网

何为Redux

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

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

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

没有Redux时的组件通信

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

有Redux的通信

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

Redux 工作流程

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

image

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):

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

const store = createStore(reducer);

export default store;
/**
 * 每次用户手动调用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):

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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容