何為Redux
Redux 是 JavaScript 狀態(tài)容器楣号,用來存儲項目中一些公共的數(shù)據(jù)救斑,做到數(shù)據(jù)“一處存儲羹蚣,多處取出”的事情袁翁,跟 Vue 的 Vuex 屬于一類角色蜈七。
它的誕生是為了解決用 React 開發(fā)大型項目中解決不了難題秒拔。如果你的項目中組件繁多,而且組件之間的通信又必不可少飒硅,這個時候 Redux 能很好的解決這個問題砂缩。
舉個例子,原本組件通信是只有父子通信三娩,如果爺孫倆通信就需要借助“父”來實現(xiàn):
問題就在于庵芭,如果組件非常多,數(shù)以千計雀监,再通信性能就非常低双吆,而且也不方便。那這個時候会前,把大家都需要的數(shù)據(jù)放在一個公共的地方好乐,誰要誰就去取:
這樣瓦宜,不管有多少個組件蔚万、組件的層次有多深,都能快速的拿到需要拿的數(shù)據(jù)临庇。
Redux 工作流程
開篇一張圖反璃,內(nèi)容全靠編:
Store
就相當于一個房地產(chǎn)中介一樣,我們想要一個地區(qū)的所有房源信息假夺,就需要通過它來獲取淮蜈,Reducers
是幕后人,中介的信息來源都是Reducers
告訴它的侄泽,比喻成一個“房產(chǎn)資源手冊”可能更合適。
中介給了用戶三個行為:
-
getState()
:普通用戶級別專門獲取房源信息的唯一行為蜻韭。 -
dispatch(action)
:房東用戶級別通知中介悼尾,改變自家房源信息(state
)的唯一行為柿扣。 -
subscribe()
:普通用戶級別如果相中了一套房子,并且告訴中介:“我對你這的房源信息比較感興趣闺魏,你這房源有啥新動向第一時間告訴我一下”未状,這樣就能第一時間收到房源信息(state
)改變的消息。
介紹完這些之后析桥,咱們把整個流程串一下:
-
普通用戶(React Components):通過
getState()
這一行為向中介(store
)拿(注意是拿司草,不是拿到)最新的房源信息,中介接受到消息后泡仗,再通過房產(chǎn)資源手冊(Reducers
)拿到最新的房源信息并反饋給用戶埋虹。所以,用戶最終的信息來源是通過Reducers
獲取到的娩怎。 -
房東用戶(React Components):通過
dispatch(action)
這一行為向中介發(fā)出信息搔课,說:“我要修改我家房源的一些信息,你幫我弄一下”截亦,其中爬泥,action.type
是必不可少的,這是告訴中介自己房源的標識崩瓤。然后袍啡,中介再把這些信息傳給房產(chǎn)資源手冊Reducers
,然后在手冊上對應的位置(該位置通過action.type
找到)把對應的數(shù)據(jù)更改了却桶,最后中介通知已經(jīng)訂閱的用戶房源已經(jīng)更改了(subscribe()
)境输。這個角色擁有兩種身份:普通用戶和房東用戶,它既能通過dispatch()
修改自己發(fā)布到中介store
上的房源信息肾扰,也能像普通用戶一樣訂閱畴嘶、獲取房源的一切信息。在我這里集晚,一個組件調(diào)用了dispatch()
就視為房東用戶窗悯,否則就是普通用戶。
這中間也有一些限制偷拔,比如action
必須擁有type
字段蒋院,其他屬性可能由用戶自定義,reducer
是一個純函數(shù)莲绰,應當遵循純函數(shù)的一些設(shè)計理念欺旧。
不得改寫參數(shù)。
不能有異步操作蛤签。
不能調(diào)用Date.now()
或者Math.random()
等不純的方法辞友,因為每次會得到不一樣的結(jié)果。(引用自阮一峰)
雇傭中介,自當用戶
現(xiàn)在咱們用代碼完成以上所述称龙,第一步是需要有中介(store
)留拾,才會有用戶,所以先創(chuàng)建中介(store
):
import { createStore } from 'redux';
import reducer from './reducer'; // 同時把房產(chǎn)資源手冊(reducer)交給中介
const store = createStore(reducer);
export default store;
/**
* 每次用戶手動調(diào)用dispatch的時候鲫尊,會自動調(diào)用這個方法
*
* <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;
}
先不看房產(chǎn)資源手冊(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數(shù)據(jù)發(fā)生改變的時候需要做的事情,這個方法同時會返回一個方法匙奴,用來取消訂閱
}
inputChange = (e) => {
const action = {
type: 'CHANGE_INPUT_VALUE',
value: e.target.value
};
store.dispatch(action);
};
btnSubmit = () => {
store.dispatch({
type: 'LIST_ADD'
}) // 第四步:升級為房東堆巧,通知中介(store)修改自己的房源信息,這里對應房產(chǎn)資源手冊的代碼邏輯
};
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;
代碼優(yōu)化
需要將上面action.type
值單獨提出來放在一個常量文件里泼菌,組件中和reducer
里就更不容易出錯谍肤。
統(tǒng)一創(chuàng)建action
:
// actionCreateor.js
const defaultField = 'value'; // action默認的擴展字段
/**
* 統(tǒng)一創(chuàng)建action
* @param {String} type 類型
* @param {any} value 值
*/
export function createAction(type, value) {
if (!type) throw new Error('【非法參數(shù)】type必須是一個合法的參數(shù)值,當前type值是:' + type);
let action = { type };
switch (typeof value) {
case 'object': {
action = Object.assign(action, value);
break;
}
default: {
action[defaultField] = value;
}
}
return action;
}