你可能不需要 Redux
首先明確一點(diǎn),Redux 是一個(gè)有用的架構(gòu),但不是非用不可。事實(shí)上,大多數(shù)情況,你可以不用它,只用 React 就夠了。
魯迅(∩_∩) 曾經(jīng)說過: <font color="red">"如果你不知道是否需要 Redux,那就是不需要它。"</font>
Redux 的創(chuàng)造者 Dan Abramov 又補(bǔ)充了一句:<font color="red">"只有遇到 React 實(shí)在解決不了的問題,你才需要 Redux 。"</font>
簡(jiǎn)單說,如果你的UI層非常簡(jiǎn)單,沒有很多互動(dòng),Redux 就是不必要的,用了反而增加復(fù)雜性。
不需要使用 Redux:
- 用戶的使用方式非常簡(jiǎn)單
- 用戶之間沒有協(xié)作
- 不需要與服務(wù)器大量交互,也沒有使用 WebSocket
- 視圖層(View)只從單一來源獲取數(shù)據(jù)
需要使用 Redux:
- 組件需要共享數(shù)據(jù)(或者叫做狀態(tài)state)的時(shí)候
- 某個(gè)狀態(tài)需要在任何地方都可以被隨時(shí)訪問的時(shí)候
- 某個(gè)組件需要改變另一個(gè)組件的狀態(tài)的時(shí)候
- 語言切換、暗黑模式切換、用戶登錄全局?jǐn)?shù)據(jù)共享 ...
簡(jiǎn)介
Redux 是 js 應(yīng)用的可預(yù)測(cè)狀態(tài)的容器。 可以理解為全局?jǐn)?shù)據(jù)狀態(tài)管理工具(狀態(tài)管理機(jī)),用來做組件通信等。
Redux 的設(shè)計(jì)思想很簡(jiǎn)單,就兩句話:
Web 應(yīng)用是一個(gè)狀態(tài)機(jī),視圖與狀態(tài)是一一對(duì)應(yīng)的。
所有的狀態(tài),保存在一個(gè)對(duì)象里面。
Redux架構(gòu)原理:
- 剝離組件數(shù)據(jù)(state)
- 數(shù)據(jù)統(tǒng)一存放在store中
- 組件訂閱store獲得數(shù)據(jù)
- store推送數(shù)據(jù)更新
類似與vue的狀態(tài)管理vuex
一句話總結(jié):Redux統(tǒng)一保存了數(shù)據(jù),在隔離了數(shù)據(jù)與UI的同時(shí),負(fù)責(zé)處理數(shù)據(jù)的綁定。
安裝
yarn add redux
或
npm install redux --save
原則
- store是唯一的 (整個(gè)應(yīng)用只能有一個(gè) store)
- 只有store能改變自己的內(nèi)容 (store里的數(shù)據(jù)不是reducer更新的)
- reducer必須是純函數(shù) (只要是同樣的輸入,必定得到同樣的輸出)
核心API
1. Store
Store 就是保存全局?jǐn)?shù)據(jù)的地方,你可以把它看成一個(gè)容器(帶有推送功能的數(shù)據(jù)倉庫)。整個(gè)應(yīng)用只能有一個(gè) Store。
Redux 提供createStore這個(gè)函數(shù),用來生成 Store。
import { createStore } from 'redux';
const store = createStore(fn);
.
2. State
Store對(duì)象包含所有數(shù)據(jù)。如果想得到某個(gè)時(shí)點(diǎn)的數(shù)據(jù),就要對(duì) Store 生成快照。這種時(shí)點(diǎn)的數(shù)據(jù)集合,就叫做 State。
當(dāng)前時(shí)刻的 State,可以通過store.getState()拿到。
import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();
Redux 規(guī)定, 一個(gè) State 對(duì)應(yīng)一個(gè) View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么樣,反之亦然。
.
3. Action
State 的變化,會(huì)導(dǎo)致 View 的變化。但是,用戶接觸不到 State,只能接觸到 View。所以,State 的變化必須是 View 導(dǎo)致的。
Action 就是 View 發(fā)出的通知,表示 State 應(yīng)該要發(fā)生變化了。
Action 是一個(gè)對(duì)象。其中的type屬性是必須的,表示 Action 的名稱。其他屬性可以自由設(shè)置,社區(qū)有一個(gè)規(guī)范可以參考。
const action = {
type: 'change_input',
value: 'Learn Redux',
};
上面代碼中,Action 的名稱是change_input,它攜帶的信息是字符串Learn Redux。
可以這樣理解,Action 描述當(dāng)前發(fā)生的事情。<font color="red">改變 State 的唯一辦法,就是使用 Action。它會(huì)運(yùn)送數(shù)據(jù)到 Store。</font>
.
4. Action Creator
過多的Action在組件中,會(huì)顯得代碼過于臃腫,不利于閱讀和維護(hù)
可以新建一個(gè)actionCreator.js文件,用來統(tǒng)一管理action
src/store/actionCreator.js
// action的統(tǒng)一管理
export const changeInputAction = value => ({
type: 'change_input',
value,
});
export const addItemAction = () => ({
type: 'add_item',
});
export const delItemAction = index => ({
type: 'del_item',
index,
});
export const initListAction = list => ({
type: 'init_list',
list,
});
每個(gè)函數(shù)都用來返回一個(gè)action,這個(gè)函數(shù)就叫 Action Creator。
.
5. store.dispatch()
store.dispatch()是 View 發(fā)出 Action 的唯一方法。
import { createStore } from 'redux';
const store = createStore(fn);
const action = {
type: 'change_input',
value: 'Learn Redux',
};
store.dispatch(action);
結(jié)合 Action Creator,這段代碼可以改寫如下:
import { createStore } from 'redux';
import { changeInputAction } from 'src/store/actionCreator.js'
const store = createStore(fn);
store.dispatch(changeInputAction('Learn Redux'));
.
6. Reducer
Store 收到 Action 以后,必須給出一個(gè)新的 State,這樣 View 才會(huì)發(fā)生變化。這種 State 的計(jì)算過程就叫做 Reducer。
Reducer 是一個(gè)函數(shù),它接受 Action 和當(dāng)前 State 作為參數(shù),返回一個(gè)新的 State。
可以新建一個(gè)reducer.js文件,用來統(tǒng)一管理
src/store/reducer.js
// 初始狀態(tài),作為 State 的默認(rèn)值
const defaultState = {
value: ''
};
const reducer = (state = defaultState, action) => {
let newState = JSON.parse(JSON.stringify(state)); // 深拷貝,不能直接修改state里的數(shù)據(jù)
if (action.type === 'change_input') {
newState.value = action.value;
}
if (action.type === 'add_item') {
newState.list.push(newState.value);
newState.value = '';
}
if (action.type === 'del_item') {
newState.list.splice(action.index, 1);
}
if (action.type === 'init_list') {
newState.list = action.list;
}
return newState;
};
export default reducer;
reducer函數(shù)收到名為change_input、add_item、del_item、init_list的 Action 后,就返回一個(gè)新的 State(newState),作為結(jié)果拋出。
在生成 Store 的時(shí)候,將 reducer傳入createStore()。以后每當(dāng)store.dispatch發(fā)送過來一個(gè)新的 Action,就會(huì)自動(dòng)調(diào)用 reducer,得到新的 State。
import { createStore } from 'redux';
import reducer from 'src/store/reducer.js';
const store = createStore(reducer);
.
7. store.subscribe()
Store 允許使用store.subscribe()方法設(shè)置監(jiān)聽函數(shù),一旦 State 發(fā)生變化,就自動(dòng)執(zhí)行這個(gè)函數(shù)。
import { Component} from 'react';
import store from './store/index';
class List extends Component {
constructor(props) {
super(props);
store.subscribe(this.storeChange.bind(this)); // store發(fā)生改變時(shí),自動(dòng)觸發(fā)storeChange方法
}
render() {
return (
// ....
);
}
storeChange() {
// store發(fā)生改變時(shí),將自動(dòng)觸發(fā)
let newState = store.getState(); // 得到當(dāng)前state狀態(tài),包含所有store屬性
this.setState(newState); // 重新渲染 View
}
}
export default List;
store.subscribe方法返回一個(gè)函數(shù),調(diào)用這個(gè)函數(shù)就可以解除監(jiān)聽。
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe(); // 解除監(jiān)聽
工作流程
1. 用戶發(fā)出 Action
const action = {
type: 'change_input',
value: 'Learn Redux',
};
store.dispatch(action);
2. Store 自動(dòng)調(diào)用 Reducer,并且傳入兩個(gè)參數(shù):當(dāng)前 State 和收到的 Action。 Reducer 會(huì)返回新的 State(newState)
const reducer = (state, action) => {
let newState = JSON.parse(JSON.stringify(state)); // 深拷貝,不能直接修改state里的數(shù)據(jù)
if (action.type === 'change_input') {
newState.value = action.value;
}
return newState;
};
export default reducer;
3. State 一旦有變化,Store 就會(huì)調(diào)用監(jiān)聽函數(shù)
store.subscribe(this.storeChange.bind(this)); // store發(fā)生改變時(shí),自動(dòng)觸發(fā)storeChange方法
4.storeChange()可以通過store.getState()得到當(dāng)前狀態(tài)。如果使用的是 React,這時(shí)可以觸發(fā)重新渲染 View
storeChange() {
// store發(fā)生改變時(shí),將自動(dòng)觸發(fā)
let newState = store.getState(); // 得到當(dāng)前state狀態(tài),包含所有store屬性
this.setState(newState); // 重新渲染 View
}
完整實(shí)例
src/store/index.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__());
export default store;
src/list.js
import store from './store/index';
import { changeInputAction } from './store/actionCreator';
class List extends Component {
constructor(props) {
super(props);
this.state = store.getState(); // 獲取store中所有的數(shù)據(jù)
store.subscribe(this.storeChange.bind(this)); // store發(fā)生改變時(shí),自動(dòng)觸發(fā)
}
render() {
return (
<div>
<input value={this.state.value} onChange={this.change.bind(this)} />
</div>
);
}
storeChange() {
this.setState(store.getState());
}
change(e) {
const action = changeInputAction(e.target.value);
store.dispatch(action); // 派發(fā)action給store
}
}
export default List;
src/store/actionCreator.js
export const changeInputAction = value => ({
type: 'change_input',
value,
});
src/store/reducer.js
const defaultState = {
value: ''
};
const reducer = (state = defaultState, action) => {
console.log(state, action);
let newState = JSON.parse(JSON.stringify(state)); // 深拷貝,不能直接修改state里的數(shù)據(jù)
if (action.type === 'change_input') {
newState.value = action.value;
}
return newState;
};
export default reducer;