Redux簡介
Redux從設(shè)計之初就不是為了編寫最短递瑰、最快的代碼介时,他是為了解決 “當有確定的狀態(tài)發(fā)生改變時报咳,數(shù)據(jù)從哪里來” 這種可預(yù)測行為的問題的,所以當你使用Redux傳值時椎咧,代碼或許會很復(fù)雜玖详,這時候請你不要去懷疑是不是不該使用Redux。
Redux和Vuex一樣 是 JavaScript 狀態(tài)容器邑退,提供可預(yù)測化的狀態(tài)管理竹宋。實際學(xué)習(xí)起這兩種狀態(tài)管理器來,我自個感覺Redux相對難學(xué)一點地技,Redux不止可以用于React的狀態(tài)管理蜈七,它也可以用于Vue,只是當他用于React的時候,還必須依 React 綁定庫和開發(fā)者工具莫矗。
npm install --save react-redux
npm install --save-dev redux-devtools
它和Vuex一樣有著store 理念,且action都不能直接修改state上面的狀態(tài)飒硅,Readux必須借助于reducers,所不同的是作谚,Vuex里面是由action發(fā)起三娩,然后再調(diào)用mutation修改(此處直接表明修改state上的何種屬性);Redux則直接由action修改妹懒,但必須由reducers去根據(jù)action.type去判斷修改的是state上面的哪個屬性雀监。
Redux也不支持多個Store,它只有一個單一的 store 和一個根級的 reduce 函數(shù)(reducer),隨著應(yīng)用不斷變大会前,你應(yīng)該把根級的 reducer 拆成多個小的 reducers好乐,分別獨立地操作 state 樹的不同部分,而不是添加新的 stores瓦宜。這就像一個 React 應(yīng)用只有一個根級的組件蔚万,這個根組件又由很多小組件構(gòu)成。
為什么要用狀態(tài)管理
當你一個state變化需要引起第一個view視圖層改變临庇,接著第一個的改變引起第二個反璃,以此類推,如果這個過程涉及到的組件很多假夺,那么交給你的任務(wù)將很龐大淮蜈,太繁瑣,往深了說如果這個過程是可逆的侄泽,那將是你的噩夢礁芦,而Redux的出現(xiàn)就是來解決這個問題的。
核心概念
state
可以把它理解成一個普通的對象悼尾,由鍵值對構(gòu)成柿扣,并且對鍵值對不做任何類型限制;
state是只讀的,唯一改變 state 的方法就是觸發(fā) action
{ key : value , key1 : value1 }
action
它也是一個普通的JS對象闺魏,只不過他有固定的鍵type
強制使用 action 來描述所有變化帶來的好處是可以清晰地知道應(yīng)用中到底發(fā)生了什么未状。如果一些東西改變了,就可以知道為什么變析桥。action 就像是描述發(fā)生了什么的指示器
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
reducer
最終司草,為了把 action 和 state 串起來,開發(fā)一些函數(shù)泡仗,就需要用到 reducer埋虹。
//其一
function userInfo (state = [],action){
switch (action.type){
case actionType.USERINFO_LOGIN:
return action.data;
case actionType.TENANTID:
return action.data;
case actionType.USERINFO_MENU:
return action.data;
default:
return state;
}
}
//其二
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter;
} else {
return state;
}
}
//匯總
function todoApp(state = {}, action) {
return {
userInfo : userInfo (state.userInfo , action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
};
}
另一種匯總的寫法
import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, userInfo })
let store = createStore(reducer)
純函數(shù):對于相同的輸入,永遠會得到相同的輸出娩怎,而且沒有任何可觀察的副作用搔课,也不會依賴外部環(huán)境的狀態(tài)。
注意 : 永遠不要在 reducer 里做這些操作:
- 修改傳入?yún)?shù)截亦;
- 執(zhí)行有副作用的操作爬泥,如 API 請求和路由跳轉(zhuǎn);
- 調(diào)用非純函數(shù)崩瓤,如 Date.now() 或 Math.random()袍啡。
方法
bindActionCreators(actionCreators, dispatch)
把一個 value 為不同 action creator 的對象,轉(zhuǎn)成擁有同名 key 的對象却桶。同時使用 dispatch
對每個 action creator 進行包裝境输,以便可以直接調(diào)用它們。
一般情況下你可以直接在 Store
實例上調(diào)用 dispatch
。如果你在 React 中使用 Redux嗅剖,react-redux 會提供 dispatch
函數(shù)讓你直接調(diào)用它 蛋逾。
惟一會使用到 bindActionCreators
的場景是當你需要把 action creator 往下傳到一個組件上,卻不想讓這個組件覺察到 Redux 的存在窗悯,而且不希望把 dispatch
或 Redux store 傳給它。
為方便起見偷拔,你也可以傳入一個函數(shù)作為第一個參數(shù)蒋院,它會返回一個函數(shù)。
參數(shù)
actionCreators
(Function or Object): 一個 action creator莲绰,或者一個 value 是 action creator 的對象欺旧。
返回值
(Function or Object): 一個與原對象類似的對象蛤签,只不過這個對象的 value 都是會直接 dispatch 原 action creator 返回的結(jié)果的函數(shù)辞友。如果傳入一個單獨的函數(shù)作為 actionCreators
,那么返回的結(jié)果也是一個單獨的函數(shù)震肮。
combineReducers(reducers)
combineReducers
輔助函數(shù)的作用是称龙,把一個由多個不同 reducer 函數(shù)作為 value 的 object,合并成一個最終的 reducer 函數(shù)戳晌,然后就可以對這個 reducer 調(diào)用 createStore
方法鲫尊。
技巧
- 使用 ES7 提案的 對象展開運算符
{ ...state, visibilityFilter: action.filter }
- 縮減樣板代碼
1.將每個 action type 定義為 string 常量:const ADD_TODO = 'ADD_TODO';const REMOVE_TODO = 'REMOVE_TODO';
這樣做的優(yōu)勢是:
- 幫助維護命名一致性,因為所有的 action type 匯總在同一位置沦偎。
- 有時疫向,在開發(fā)一個新功能之前你想看到所有現(xiàn)存的 actions 。而你的團隊里可能已經(jīng)有人添加了你所需要的action豪嚎,而你并不知道搔驼。
- Action types 列表在 Pull Request 中能查到所有添加,刪除侈询,修改的記錄舌涨。這能幫助團隊中的所有人及時追蹤新功能的范圍與實現(xiàn)。
- 如果你在 import 一個 Action 常量的時候拼寫錯了妄荔,你會得到 undefined 泼菌。在 dispatch 這個 action 的時候,Redux 會立即拋出這個錯誤啦租,你也會馬上發(fā)現(xiàn)錯誤哗伯。
2.通過創(chuàng)建函數(shù)生成 action 對象
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
export function editTodo(id, text) {
return {
type: 'EDIT_TODO',
id,
text
}
}
也可以用于生成 action creator 的函數(shù)
function makeActionCreator(type, ...argNames) {
return function(...args) {
let action = { type }
argNames.forEach((arg, index) => {
action[argNames[index]] = args[index]
})
return action
}
}
const ADD_TODO = 'ADD_TODO'
export const addTodo = makeActionCreator(ADD_TODO, 'todo')
問題
Redux 只能搭配 React 使用?
不是篷角,他可以Angular焊刹、 Angular 2、 Vue、 Mithril 等框架使用虐块。
Redux 需要特殊的編譯工具支持嗎俩滥?
Redux 寫法遵循 ES6 語法,但在發(fā)布時被 Webpack 和 Babel 編譯成了 ES5贺奠,所以在使用時可以忽略 JavaScript 的編譯過程霜旧。
將怎樣的數(shù)據(jù)放入 Redux 的經(jīng)驗法則:
- 應(yīng)用的其他部分是否關(guān)心這個數(shù)據(jù)?
- 是否需要根據(jù)需要在原始數(shù)據(jù)的基礎(chǔ)上創(chuàng)建衍生數(shù)據(jù)儡率?
- 相同的數(shù)據(jù)是否被用作驅(qū)動多個組件挂据?
- 能否將狀態(tài)恢復(fù)到特定時間點(在時光旅行調(diào)試的時候)?
- 是否要緩存數(shù)據(jù)(比如:數(shù)據(jù)存在的情況下直接去使用它而不是重復(fù)去請求他)儿普?
如何在 reducer 之間共享 state? combineReducers 是必須的嗎崎逃?
要共享state,建議將state再次拆分成小模塊眉孩;
combineReducers 不是 必須的个绍,它僅僅是通過簡單的 JavaScript 對象作為數(shù)據(jù),讓 state 層能與 reducer 一一關(guān)聯(lián)的函數(shù)而已浪汪。
如何組織State
設(shè)計規(guī)范化的State
范式化的數(shù)據(jù)包含下面幾個概念:
- 任何類型的數(shù)據(jù)在 state 中都有自己的 “表”巴柿。
- 任何 “數(shù)據(jù)表” 應(yīng)將各個項目存儲在對象中,其中每個項目的 ID 作為 key吟宦,項目本身作為 value篮洁。
- 對單個項目的引用都應(yīng)該根據(jù)存儲項目的 ID 來完成。
- ID 數(shù)組應(yīng)該用于排序
一個項目允許出現(xiàn)多個store嗎殃姓?
Flux 原始模型中一個應(yīng)用有多個 “store”袁波,每個都維護了不同維度的數(shù)據(jù)。這樣導(dǎo)致了類似于一個 store “等待” 另一 store 操作的問題蜗侈。Redux 項目中只有一個store篷牌,而且通過將 reducer 分解成多個小而美的 reducer,進而切分數(shù)據(jù)域踏幻,隱含的實現(xiàn)了多個store枷颊,但又避免了上述情況的發(fā)生。
為何 type 必須是字符串该面,或者至少可以被序列化夭苗? 為什么 action 類型應(yīng)該作為常量?
無法強制序列化 action隔缀,所以 Redux 只會校驗 action 是否是普通對象题造,以及 type 是否定義。其它的都交由你決定猾瘸,但是確保數(shù)據(jù)是可序列化將對調(diào)試以及問題的重現(xiàn)有很大幫助
雖然可以在任何地方手動創(chuàng)建 action 對象界赔、手動指定 type 值丢习,定義常量的方式使得代碼的維護更為方便。
是否存在 reducer 和 action 之間的一對一映射淮悼?
不存在咐低。建議的方式是編寫?yīng)毩⑶液苄〉?reducer 方法去更新指定的 state 部分,這種模式被稱為 “reducer 合成”袜腥。一個指定的 action 也許被它們中的全部见擦、部分、甚至沒有一個處理到羹令。這種方式把組件從實際的數(shù)據(jù)變更中解耦锡宋,一個 action 可能影響到 state 樹的不同部分,對組件而言再也不必知道這些了特恬。有些用戶選擇將它們緊密綁定在一起,就像 “ducks” 文件結(jié)構(gòu)徐钠,顯然是沒有默認的一對一映射癌刽。所以當你想在多個 reducer 中處理同一個 action 時,應(yīng)當避免此類結(jié)構(gòu)尝丐。
為何組件沒有被重新渲染显拜、或者 mapStateToProps 沒有運行?
如果直接返回同一對象爹袁,即使你改變了數(shù)據(jù)內(nèi)容远荠,Redux 也會認為沒有變化。
為何不在被連接的組件中使用 this.props.dispatch失息?
connect() 方法有兩個主要的參數(shù)譬淳,而且都是可選的。第一個參數(shù) mapStateToProps 是個函數(shù)盹兢,讓你在數(shù)據(jù)變化時從 store 獲取數(shù)據(jù)邻梆,并作為 props 傳到組件中。第二個參數(shù) mapDispatchToProps 依然是函數(shù)绎秒,讓你可以使用 store 的 dispatch 方法浦妄,通常都是創(chuàng)建 action 創(chuàng)建函數(shù)并預(yù)先綁定,那么在調(diào)用時就能直接分發(fā) action见芹。
如果在執(zhí)行 connect() 時沒有指定 mapDispatchToProps 方法剂娄,React Redux 默認將 dispatch 作為 prop 傳入。所以當你指定方法時玄呛, dispatch 將 不 會自動注入阅懦。如果你還想讓其作為 prop,需要在 mapDispatchToProps 實現(xiàn)的返回值中明確指出把鉴。
store 里能直接通過 store.dispatch()
調(diào)用 dispatch()
方法故黑,但是多數(shù)情況下你會使用 react-redux 提供的 connect()
幫助器來調(diào)用儿咱。bindActionCreators()
可以自動把多個 action 創(chuàng)建函數(shù) 綁定到 dispatch()
方法上。