從 React 說起
剛開始接觸 React 的時候其實是感到非常驚艷的毡鉴,在 React 的架構下可以像搭積木一樣把前端的視圖組件組合在一起,非常好玩揭芍,React 這種模塊化的結構保證了組件內部的高度自治性羞秤,使得前端開發(fā)高度正交化。以至于剛剛使用 React 的時候簡直覺得是在上帝之手的幫助之下寫前端饭聚,觸手絲滑,效率飛起搁拙,但是用了一段時間之后就很快發(fā)現(xiàn)了一些問題秒梳,比如父組件可以通過給子組件賦值 props,但是子組件向父組件傳值卻只能通過較為復雜的雙向綁定箕速,如果要向不是父子關系的組件傳值怎么辦呢酪碘?React 的文檔會告訴你只能再 copy 一個組件過來了。EXM弧满?組件之間傳值這么常見的操作 React 卻只能給出一個這么簡單粗暴的辦法婆跑?負分此熬!差評庭呜!
但是知道了 Redux 這么一個神奇的存在之后,感覺 React 終于有救了犀忱,在 Redux 的框架之下募谎,所有的狀態(tài)被集中起來統(tǒng)一管理,使得應用內的數(shù)據(jù)完美地同步起來阴汇,再也不用擔心組件之間的通訊問題了数冬。
當然這里要提一下,Redux 本身是一個單獨的框架,跟 React 沒什么關系拐纱,但是由于在 React 的框架下視圖和 state 和 view 是一一對應的铜异,這樣就使得 Redux 更適用于 React 環(huán)境下的開發(fā)。
Redux 的基本思想
Redux 是一個基于Flux思想實現(xiàn)的一個針對web應用的狀態(tài)管理庫秸架,在Redux 里 Web 應用被視為一個有窮狀態(tài)機揍庄,在這個狀態(tài)機里所有狀態(tài)的變化都是可以追溯甚至是可以撤銷的,為了實現(xiàn)這樣的機制东抹,Redux 進行了以下三個約束:
- 所有的 state 構成一棵 object tree蚂子,這棵 object tree 只存在于唯一一個store 中。
- 所有state都是只讀的缭黔,唯一改變 state 的方法是觸發(fā) action
- 使用純函數(shù)來執(zhí)行修改食茎,以保證每次對狀態(tài)修改的執(zhí)行結果都是一致的
在 redux 中主要引入了 action、reducer馏谨、store 這三個概念别渔,action 用于定義一個請求,reducer 用于根據(jù) action 產(chǎn)生新狀態(tài)惧互,store 用于存儲和管理 state钠糊,監(jiān)聽 action,將 action 自動分配給 reducer 并根據(jù) reducer 的執(zhí)行結果更新 state壹哺。
在這里你可以根據(jù)字面意思把 store 想象成一個倉庫抄伍,這個倉庫里的貨物就是 state ,但是這個倉庫的管理員實在是懶得跟自己的客戶溝通管宵,就請了好幾個助手來幫自己截珍,比如助手 A 專門負責某個手機廠商的請求,助手B專門負責電腦廠商的請求箩朴,他們計算好進出貨量之后通知管理員岗喉,管理員如果接到手機廠商的請求就把他分配給 A,接到電腦廠商的請求就分配給B炸庞,得到 A 和 B 的結果之后管理員就可以馬上更新倉庫了钱床,在這里這些助手就是 reducer,像手機和電腦廠商的請求就是 action埠居。執(zhí)行任務分配的這個過程就是 store.despatch(action)
函數(shù) 查牌。
Redux 的基本概念
Action
action 是把數(shù)據(jù)從應用傳到 store 的有效載荷。它是 store 數(shù)據(jù)的唯一來源滥壕。用來表明一個事件的發(fā)生纸颜,但并不對狀態(tài)如何修改做任何描述。一個action 的結構是一個 javascript 普通對象绎橘,一個 action 的結構如下
{
//type字段用于標識action的類型胁孙,一般用一個字符串來表示
type: 'ADD_TODO',
//text是用戶自定義的字段,一般用來傳遞和狀態(tài)修改相關的參數(shù)
text: 'Build my first Redux app'
}
Reducer
action 只是描述了有事情發(fā)生了這一事實,但是并沒有指名如何更新 state涮较,reducer 就是對狀態(tài)修改過程的描述稠鼻,但是需要注意的有以下兩點:
由于狀態(tài)是只讀的,reducer 本身并不能真正實現(xiàn)狀態(tài)的修改狂票,而是只把新狀態(tài)作為返回值返回枷餐。
-
為了確保每次對狀態(tài)修改的結果都是一致的,reducer 必須是一個純函數(shù)苫亦,也就是說毛肋,只要是同樣的輸入,必定得到同樣的輸出屋剑。純函數(shù)需要遵循以下約束:
不得改寫參數(shù) 不能調用系統(tǒng) I/O 的API 不能調用Date.now()或者Math.random()等不純的方法润匙,因為每次會得到不一樣的結果
一個 reducer 的結構如下
/* 在這個 reducer 中,對于一個類型為 ADD_TODO 的 action唉匾,返回的新狀態(tài)是在傳入狀態(tài)數(shù)組中追加了一個元素 */
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
default:
return state
}
}
Store
在定義了描述事件發(fā)生的 action 和描述狀態(tài)修改方案的 reducer 之后孕讳,如何把它們聯(lián)系到一起真正實現(xiàn)狀態(tài)的改變呢?這就要牽扯到 store 啦巍膘,前面已經(jīng)提過 redux 的原則之一就是所有的 state 構成的對象樹都存到了 store 中厂财,除此之外 store 還有以下功能:
- 維持應用的 state
- 提供
getState()
方法獲取 state - 提供
dispatch(action)
方法更新 state; - 通過
subscribe(listener)
注冊監(jiān)聽器; - 通過
subscribe(listener)
返回的函數(shù)注銷監(jiān)聽器峡懈。
創(chuàng)建一個 store 其實很簡單:
import { createStore } from 'redux'
import reducers from './reducers'
let store = createStore(reducers)
createStore()
的第二個參數(shù)是可選的, 用于設置 state 初始狀態(tài)璃饱。這對開發(fā)同構應用時非常有用,服務器端 redux 應用的 state 結構可以與客戶端保持一致, 那么客戶端可以將從網(wǎng)絡接收到的服務端 state 直接用于本地數(shù)據(jù)初始化肪康。
let store = createStore(todoApp, window.STATE_FROM_SERVER)
接下來就可以簡單實現(xiàn)一個創(chuàng)建處理 action 的過程了
import { createStore } from 'redux'
const ADD_TODO = 'ADD_TODO'
//創(chuàng)建一個 action
const simpleAction1 = {
type: 'ADD_TODO',
text: 'Build my first Redux app'
}
const simpleAction2 = {
type: 'ADD_TODO',
text: 'Build my second Redux app'
}
//創(chuàng)建一個 reducer, 功能為收到類型為 ADD_TODO 的 action 后為在 state 中添加一個條目
function reducer(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
default:
return state
}
}
//創(chuàng)建一個初始狀態(tài)為空數(shù)組的 store
let store = createStore(reducer,[])
// 打印初始狀態(tài)
console.log(store.getState())
// 每次 state 更新時荚恶,打印日志
// 注意 subscribe() 返回一個函數(shù)用來注銷監(jiān)聽器
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
// 發(fā)起一個 action, 執(zhí)行之后會更新狀態(tài),由于注冊了監(jiān)聽器你可以看到每次更新都會打印當前狀態(tài)
store.dispatch(simpleAction1)
store.dispatch(simpleAction1)
// 停止監(jiān)聽 state 更新
unsubscribe();