摘要:就是發(fā)出的通知,表示應(yīng)該要發(fā)生變化了。其中的屬性是必須的,表示的名稱。不同的函數(shù)負(fù)責(zé)處理不同屬性,最終把它們合并成一個(gè)大的即可。上面的代碼通過(guò)方法將三個(gè)子合并成一個(gè)大的函數(shù)。該函數(shù)根據(jù)的去執(zhí)行相應(yīng)的子,并將返回結(jié)果合并成一個(gè)大的對(duì)象。
一、什么是Redux
Redux 的設(shè)計(jì)思想很簡(jiǎn)單,就兩句話。
(1)Web 應(yīng)用是一個(gè)狀態(tài)機(jī),視圖與狀態(tài)是一一對(duì)應(yīng)的。
(2)所有的狀態(tài),保存在一個(gè)對(duì)象里面。
二、基本概念和API 1. StoreStore 就是保存數(shù)據(jù)的地方,你可以把它看成一個(gè)容器。整個(gè)應(yīng)用只能有一個(gè) Store。
Redux 提供createStore這個(gè)函數(shù),用來(lái)生成 Store。
import { createStore } from "redux"; const store = createStore(fn);
上面代碼中,createStore函數(shù)接受另一個(gè)函數(shù)作為參數(shù),返回新生成的 Store 對(duì)象。
2. StateStore對(duì)象包含所有數(shù)據(jù)。如果想得到某個(gè)時(shí)點(diǎn)的數(shù)據(jù),就要對(duì) Store 生成快照。這種時(shí)點(diǎn)的數(shù)據(jù)集合,就叫做 State。
當(dāng)前時(shí)刻的 State,可以通過(guò)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. ActionState 的變化,會(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: "ADD_TODO", payload: "Learn Redux" };
上面代碼中,Action 的名稱是ADD_TODO,它攜帶的信息是字符串Learn Redux。
可以這樣理解,Action 描述當(dāng)前發(fā)生的事情。改變 State 的唯一辦法,就是使用 Action。它會(huì)運(yùn)送數(shù)據(jù)到 Store。
4. Action CreatorView 要發(fā)送多少種消息,就會(huì)有多少種 Action。如果都手寫,會(huì)很麻煩??梢远x一個(gè)函數(shù)來(lái)生成 Action,這個(gè)函數(shù)就叫 Action Creator。
const ADD_TODO = "添加 TODO"; function addTodo(text) { return { type: ADD_TODO, text } }
const action = addTodo("Learn Redux");
上面代碼中,addTodo函數(shù)就是一個(gè) Action Creator。
store.dispatch()是 View 發(fā)出 Action 的唯一方法。
import { createStore } from "redux"; const store = createStore(fn); store.dispatch({ type: "ADD_TODO", payload: "Learn Redux" });
上面代碼中,store.dispatch接受一個(gè) Action 對(duì)象作為參數(shù),將它發(fā)送出去。
結(jié)合 Action Creator,這段代碼可以改寫如下。
store.dispatch(addTodo("Learn Redux"));6. Reducer
Store 收到 Action 以后,必須給出一個(gè)新的 State,這樣 View 才會(huì)發(fā)生變化。這種 State 的計(jì)算過(guò)程就叫做 Reducer。
Reducer 是一個(gè)函數(shù),它接受 Action 和當(dāng)前 State 作為參數(shù),返回一個(gè)新的 State。
const reducer = function (state, action) { // ... return new_state; };
整個(gè)應(yīng)用的初始狀態(tài),可以作為 State 的默認(rèn)值。下面是一個(gè)實(shí)際的例子。
const defaultState = 0; const reducer = (state = defaultState, action) => { switch (action.type) { case "ADD": return state + action.payload; default: return state; } }; const state = reducer(1, { type: "ADD", payload: 2 });
上面代碼中,reducer函數(shù)收到名為ADD的 Action 以后,就返回一個(gè)新的 State,作為加法的計(jì)算結(jié)果。其他運(yùn)算的邏輯(比如減法),也可以根據(jù) Action 的不同來(lái)實(shí)現(xiàn)。
實(shí)際應(yīng)用中,Reducer 函數(shù)不用像上面這樣手動(dòng)調(diào)用,store.dispatch方法會(huì)觸發(fā) Reducer 的自動(dòng)執(zhí)行。為此,Store 需要知道 Reducer 函數(shù),做法就是在生成 Store 的時(shí)候,將 Reducer 傳入createStore方法。
import { createStore } from "redux"; const store = createStore(reducer);
上面代碼中,createStore接受 Reducer 作為參數(shù),生成一個(gè)新的 Store。以后每當(dāng)store.dispatch發(fā)送過(guò)來(lái)一個(gè)新的 Action,就會(huì)自動(dòng)調(diào)用 Reducer,得到新的 State。
為什么這個(gè)函數(shù)叫做 Reducer 呢?因?yàn)樗梢宰鳛閿?shù)組的reduce方法的參數(shù)。請(qǐng)看下面的例子,一系列 Action 對(duì)象按照順序作為一個(gè)數(shù)組。
const actions = [ { type: "ADD", payload: 0 }, { type: "ADD", payload: 1 }, { type: "ADD", payload: 2 } ]; const total = actions.reduce(reducer, 0); // 3
上面代碼中,數(shù)組actions表示依次有三個(gè) Action,分別是加0、加1和加2。數(shù)組的reduce方法接受 Reducer 函數(shù)作為參數(shù),就可以直接得到最終的狀態(tài)3。
7. 純函數(shù)Reducer 函數(shù)最重要的特征是,它是一個(gè)純函數(shù)。也就是說(shuō),只要是同樣的輸入,必定得到同樣的輸出。
純函數(shù)是函數(shù)式編程的概念,必須遵守以下一些約束。
不得改寫參數(shù) 不能調(diào)用系統(tǒng) I/O 的API 不能調(diào)用Date.now()或者M(jìn)ath.random()等不純的方法,因?yàn)槊看螘?huì)得到不一樣的結(jié)果
由于 Reducer 是純函數(shù),就可以保證同樣的State,必定得到同樣的 View。但也正因?yàn)檫@一點(diǎn),Reducer 函數(shù)里面不能改變 State,必須返回一個(gè)全新的對(duì)象,請(qǐng)參考下面的寫法。
// State 是一個(gè)對(duì)象 function reducer(state, action) { return Object.assign({}, state, { thingToChange }); // 或者 return { ...state, ...newState }; } // State 是一個(gè)數(shù)組 function reducer(state, action) { return [...state, newItem]; }
最好把 State 對(duì)象設(shè)成只讀。你沒(méi)法改變它,要得到新的 State,唯一辦法就是生成一個(gè)新對(duì)象。這樣的好處是,任何時(shí)候,與某個(gè) View 對(duì)應(yīng)的 State 總是一個(gè)不變的對(duì)象。
8. store.subscribe()Store 允許使用store.subscribe方法設(shè)置監(jiān)聽(tīng)函數(shù),一旦 State 發(fā)生變化,就自動(dòng)執(zhí)行這個(gè)函數(shù)。
import { createStore } from "redux"; const store = createStore(reducer); store.subscribe(listener);
顯然,只要把 View 的更新函數(shù)(對(duì)于 React 項(xiàng)目,就是組件的render方法或setState方法)放入listen,就會(huì)實(shí)現(xiàn) View 的自動(dòng)渲染。
store.subscribe方法返回一個(gè)函數(shù),調(diào)用這個(gè)函數(shù)就可以解除監(jiān)聽(tīng)。
let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();三、Store 的實(shí)現(xiàn)
上一節(jié)介紹了 Redux 涉及的基本概念,可以發(fā)現(xiàn) Store 提供了三個(gè)方法。
store.getState() store.dispatch() store.subscribe() import { createStore } from "redux"; let { subscribe, dispatch, getState } = createStore(reducer);
createStore方法還可以接受第二個(gè)參數(shù),表示 State 的最初狀態(tài)。這通常是服務(wù)器給出的。
let store = createStore(todoApp, window.STATE_FROM_SERVER)
上面代碼中,window.STATE_FROM_SERVER就是整個(gè)應(yīng)用的狀態(tài)初始值。注意,如果提供了這個(gè)參數(shù),它會(huì)覆蓋 Reducer 函數(shù)的默認(rèn)初始值。
下面是createStore方法的一個(gè)簡(jiǎn)單實(shí)現(xiàn),可以了解一下 Store 是怎么生成的。
const createStore = (reducer) => { let state; let listeners = []; const getState = () => state; const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; const subscribe = (listener) => { listeners.push(listener); return () => { listeners = listeners.filter(l => l !== listener); } }; dispatch({}); return { getState, dispatch, subscribe }; };四、Reducer 的拆分
Reducer 函數(shù)負(fù)責(zé)生成 State。由于整個(gè)應(yīng)用只有一個(gè) State 對(duì)象,包含所有數(shù)據(jù),對(duì)于大型應(yīng)用來(lái)說(shuō),這個(gè) State 必然十分龐大,導(dǎo)致 Reducer 函數(shù)也十分龐大。
請(qǐng)看下面的例子。
const chatReducer = (state = defaultState, action = {}) => { const { type, payload } = action; switch (type) { case ADD_CHAT: return Object.assign({}, state, { chatLog: state.chatLog.concat(payload) }); case CHANGE_STATUS: return Object.assign({}, state, { statusMessage: payload }); case CHANGE_USERNAME: return Object.assign({}, state, { userName: payload }); default: return state; } };
上面代碼中,三種 Action 分別改變 State 的三個(gè)屬性。
ADD_CHAT:chatLog屬性 CHANGE_STATUS:statusMessage屬性 CHANGE_USERNAME:userName屬性
這三個(gè)屬性之間沒(méi)有聯(lián)系,這提示我們可以把 Reducer 函數(shù)拆分。不同的函數(shù)負(fù)責(zé)處理不同屬性,最終把它們合并成一個(gè)大的 Reducer 即可。
const chatReducer = (state = defaultState, action = {}) => { return { chatLog: chatLog(state.chatLog, action), statusMessage: statusMessage(state.statusMessage, action), userName: userName(state.userName, action) } };
上面代碼中,Reducer 函數(shù)被拆成了三個(gè)小函數(shù),每一個(gè)負(fù)責(zé)生成對(duì)應(yīng)的屬性。
這樣一拆,Reducer 就易讀易寫多了。而且,這種拆分與 React 應(yīng)用的結(jié)構(gòu)相吻合:一個(gè) React 根組件由很多子組件構(gòu)成。這就是說(shuō),子組件與子 Reducer 完全可以對(duì)應(yīng)。
Redux 提供了一個(gè)combineReducers方法,用于 Reducer 的拆分。你只要定義各個(gè)子 Reducer 函數(shù),然后用這個(gè)方法,將它們合成一個(gè)大的 Reducer。
import { combineReducers } from "redux"; const chatReducer = combineReducers({ chatLog, statusMessage, userName }) export default todoApp;
上面的代碼通過(guò)combineReducers方法將三個(gè)子 Reducer 合并成一個(gè)大的函數(shù)。
這種寫法有一個(gè)前提,就是 State 的屬性名必須與子 Reducer 同名。如果不同名,就要采用下面的寫法。
const reducer = combineReducers({ a: doSomethingWithA, b: processB, c: c }) // 等同于 function reducer(state = {}, action) { return { a: doSomethingWithA(state.a, action), b: processB(state.b, action), c: c(state.c, action) } }
總之,combineReducers()做的就是產(chǎn)生一個(gè)整體的 Reducer 函數(shù)。該函數(shù)根據(jù) State 的 key 去執(zhí)行相應(yīng)的子 Reducer,并將返回結(jié)果合并成一個(gè)大的 State 對(duì)象。
下面是combineReducer的簡(jiǎn)單實(shí)現(xiàn)。
const combineReducers = reducers => { return (state = {}, action) => { return Object.keys(reducers).reduce( (nextState, key) => { nextState[key] = reducers[key](state[key], action); return nextState; }, {} ); }; };
你可以把所有子 Reducer 放在一個(gè)文件里面,然后統(tǒng)一引入。
import { combineReducers } from "redux" import * as reducers from "./reducers" const reducer = combineReducers(reducers)五、工作流程
本節(jié)對(duì) Redux 的工作流程,做一個(gè)梳理。
首先,用戶發(fā)出 Action。
store.dispatch(action);
然后,Store 自動(dòng)調(diào)用 Reducer,并且傳入兩個(gè)參數(shù):當(dāng)前 State 和收到的 Action。 Reducer 會(huì)返回新的 State 。
let nextState = todoApp(previousState, action);
State 一旦有變化,Store 就會(huì)調(diào)用監(jiān)聽(tīng)函數(shù)。
// 設(shè)置監(jiān)聽(tīng)函數(shù) store.subscribe(listener);
listener可以通過(guò)store.getState()得到當(dāng)前狀態(tài)。如果使用的是 React,這時(shí)可以觸發(fā)重新渲染 View。
function listerner() { let newState = store.getState(); component.setState(newState); }六、實(shí)例:計(jì)數(shù)器
下面我們來(lái)看一個(gè)最簡(jiǎn)單的實(shí)例。
const Counter = ({ value }) => ({value}
); const render = () => { ReactDOM.render(, document.getElementById("root") ); }; store.subscribe(render); render();
上面是一個(gè)簡(jiǎn)單的計(jì)數(shù)器,唯一的作用就是把參數(shù)·value·的值,顯示在網(wǎng)頁(yè)上。Store 的監(jiān)聽(tīng)函數(shù)設(shè)置為render,每次 State 的變化都會(huì)導(dǎo)致網(wǎng)頁(yè)重新渲染。
下面加入一點(diǎn)變化,為Counter添加遞增和遞減的 Action。
const Counter = ({ value, onIncrement, onDecrement }) => (); const reducer = (state = 0, action) => { switch (action.type) { case "INCREMENT": return state + 1; case "DECREMENT": return state - 1; default: return state; } }; const store = createStore(reducer); const render = () => { ReactDOM.render({value}
store.dispatch({type: "INCREMENT"})} onDecrement={() => store.dispatch({type: "DECREMENT"})} />, document.getElementById("root") ); }; render(); store.subscribe(render);
如果你覺(jué)得這篇文章對(duì)你有所幫助,那就順便點(diǎn)個(gè)贊吧,點(diǎn)點(diǎn)關(guān)注不迷路~
黑芝麻哇,白芝麻發(fā),黑芝麻白芝麻哇發(fā)哈!
前端哇發(fā)哈
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/109603.html
摘要:一組件將所有組件分成兩大類組件和容器組件。前者負(fù)責(zé)與外部的通信,將數(shù)據(jù)傳給后者,由后者渲染出視圖。也就是說(shuō),用戶負(fù)責(zé)視覺(jué)層,狀態(tài)管理則是全部交給它。前者需要從計(jì)算得到,后者需要向外發(fā)出。最后,生成對(duì)象,并使用在根組件外面包一層。 一、UI 組件 React-Redux 將所有組件分成兩大類:UI 組件(presentational component)和容器組件(container c...
摘要:作為開(kāi)發(fā)者,需要不斷的對(duì)技術(shù)點(diǎn)進(jìn)行總結(jié),并且把它沉淀下來(lái),寫技術(shù)博文無(wú)疑是最好的方式,隨著時(shí)間流逝,還可以作為自己每個(gè)階段的技術(shù)認(rèn)知軌跡進(jìn)行回顧和反思,這里將會(huì)持續(xù)記錄對(duì)開(kāi)發(fā)相關(guān)總結(jié)內(nèi)容后端開(kāi)發(fā)大話后端開(kāi)發(fā)的奇淫技巧大集合高并發(fā)大話程序猿眼 作為WEB開(kāi)發(fā)者,需要不斷的對(duì)技術(shù)點(diǎn)進(jìn)行總結(jié),并且把它沉淀下來(lái),寫技術(shù)博文無(wú)疑是最好的方式,隨著時(shí)間流逝,還可以作為自己每個(gè)階段的技術(shù)認(rèn)知軌跡進(jìn)行...
摘要:作為開(kāi)發(fā)者,需要不斷的對(duì)技術(shù)點(diǎn)進(jìn)行總結(jié),并且把它沉淀下來(lái),寫技術(shù)博文無(wú)疑是最好的方式,隨著時(shí)間流逝,還可以作為自己每個(gè)階段的技術(shù)認(rèn)知軌跡進(jìn)行回顧和反思,這里將會(huì)持續(xù)記錄對(duì)開(kāi)發(fā)相關(guān)總結(jié)內(nèi)容后端開(kāi)發(fā)大話后端開(kāi)發(fā)的奇淫技巧大集合高并發(fā)大話程序猿眼 作為WEB開(kāi)發(fā)者,需要不斷的對(duì)技術(shù)點(diǎn)進(jìn)行總結(jié),并且把它沉淀下來(lái),寫技術(shù)博文無(wú)疑是最好的方式,隨著時(shí)間流逝,還可以作為自己每個(gè)階段的技術(shù)認(rèn)知軌跡進(jìn)行...
閱讀 2160·2023-04-25 19:03
閱讀 1327·2021-10-14 09:42
閱讀 3520·2021-09-22 15:16
閱讀 1079·2021-09-10 10:51
閱讀 1759·2021-09-06 15:00
閱讀 2477·2019-08-30 15:55
閱讀 547·2019-08-29 16:22
閱讀 945·2019-08-26 13:49