手拉手学React:Redux
何为Redux
Redux 是 JavaScript 状态容器,用来存储项目中一些公共的数据,做到数据“一处存储,多处取出”的事情,跟 Vue 的 Vuex 属于一类角色。
它的诞生是为了解决用 React 开发大型项目中解决不了难题。如果你的项目中组件繁多,而且组件之间的通信又必不可少,这个时候 Redux 能很好的解决这个问题。
举个例子,原本组件通信是只有父子通信,如果爷孙俩通信就需要借助“父”来实现:
没有Redux时的组件通信问题就在于,如果组件非常多,数以千计,再通信性能就非常低,而且也不方便。那这个时候,把大家都需要的数据放在一个公共的地方,谁要谁就去取:
有Redux的通信这样,不管有多少个组件、组件的层次有多深,都能快速的拿到需要拿的数据。
Redux 工作流程
开篇一张图,内容全靠编:
imageStore
就相当于一个房地产中介一样,我们想要一个地区的所有房源信息,就需要通过它来获取,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;
}