浅谈redux基本概念
網(wǎng)頁(yè)從遠(yuǎn)古時(shí)代的『webpage』尤其是一種靜態(tài)頁(yè)面的存在方式,發(fā)展到當(dāng)下?lián)碛兄鴱?fù)雜的功能與交互邏輯的面向「客戶端」更愿意被稱之為『webapp』的形態(tài)的整個(gè)過(guò)程中,網(wǎng)頁(yè)的開(kāi)發(fā)不再是簡(jiǎn)單的界面拼湊來(lái)顯示靜態(tài)的內(nèi)容,而是要通過(guò)維護(hù)和管理頁(yè)面上的各種狀態(tài),例如服務(wù)端返回的數(shù)據(jù)、本地臨時(shí)存儲(chǔ)的數(shù)據(jù)、視圖界面該被隱藏或者顯示、路由狀態(tài)等等,來(lái)決定用戶在不同的交互下,網(wǎng)頁(yè)該怎樣正確的顯示預(yù)期的結(jié)果。而整個(gè)『webapp』可以看做一個(gè)大型的狀態(tài)機(jī),當(dāng)管理這些龐大且又復(fù)雜的 states 時(shí),很容易出現(xiàn)不可預(yù)測(cè)甚至?xí)?duì)一些狀態(tài)的改變發(fā)生『失控』的情景:當(dāng)一個(gè)界面改動(dòng)而更新了某個(gè) model,而這個(gè)model又更新另一個(gè) model,最終產(chǎn)生的結(jié)果是與該另一個(gè)model相關(guān)的界面產(chǎn)生了不可預(yù)知的變更...這在擁有著雙向數(shù)據(jù)綁定的前端框架的項(xiàng)目里尤其的面臨著難以維護(hù)的局面。而redux通過(guò)基于單向數(shù)據(jù)流的模式,背靠其遵循的三大原則,確保每一次它在改變各種狀態(tài)之后,其結(jié)果是可預(yù)測(cè)的。
redux遵循的三個(gè)原則
所有和webapp相關(guān)的states都可以存放在一個(gè)對(duì)象內(nèi)
該對(duì)象被稱作一個(gè)狀態(tài)樹(shù)。例如,通過(guò)一個(gè)對(duì)象來(lái)描述一個(gè) todo list 的狀態(tài)可以如下:
{visibilityFilter: 'SHOW_ALL',todos: [{text: 'Consider using Redux',completed: true,},{text: 'Keep all state in a single tree',completed: false}] }即,當(dāng)前狀態(tài)下所包含的todo項(xiàng)列表以及過(guò)濾列表的顯示方式(顯示所有列表項(xiàng)包括完成與未完成等)
在狀態(tài)樹(shù)里的states只能是可讀
改變states的方法只能通過(guò) dispatch an action, action 是一個(gè)純object,用來(lái)描述發(fā)起了何種action以及它附帶的額外數(shù)據(jù):
以下是一個(gè)完成 todo list 中某一項(xiàng)的action
{type: 'COMPLETE_TODO',index: 1 }即,該動(dòng)作完成了索引為1的todo項(xiàng)。
狀態(tài)的變更是通過(guò)純函數(shù)來(lái)完成的
所謂純函數(shù),就是單純地將參數(shù)傳入并加工輸出成一個(gè)可以預(yù)測(cè)的返回值,在這個(gè)過(guò)程中沒(méi)有產(chǎn)生任何副作用。這里的副作用包括但不限于對(duì)原傳參的改動(dòng)、發(fā)起對(duì)數(shù)據(jù)庫(kù)的操作以及隨之產(chǎn)生的對(duì)DOM結(jié)構(gòu)的變更。純函數(shù)返回的值總是可預(yù)測(cè)的,而非純函數(shù)則更多的機(jī)會(huì)產(chǎn)生前面提到的狀態(tài)的不可控性。
//pure function function square(x){return x * x; }function squareAll(items){return items.map(square); } // Impure functions function square(x){updateXInDatabase(x);return x * x; }function squareAll(items){for (let i = 0; i < items.length; i++) {items[i] = square(items[i]);} }在redux里面,我們需要通過(guò)一個(gè)純函數(shù)來(lái)描述狀態(tài)是如何被改變的,這個(gè)純函數(shù)接受一個(gè)初始的狀態(tài),以及改變這個(gè)狀態(tài)的actions,并且返回一個(gè)新的狀態(tài)。這種方法稱之為reducer。
來(lái)看一個(gè)簡(jiǎn)單的reducer,一個(gè)純函數(shù),沒(méi)什么特別的:
const counter = (state = 0, action) => {switch (action.type) {case 'INCREMENT':return state + 1;case 'DECREMENT':return state - 1;default:return state;} }reducer通過(guò)返回一個(gè)新的state來(lái)確保上一個(gè)階段的state沒(méi)有被改寫,這就保證了它的輸出結(jié)果是可預(yù)測(cè)的:
expect(counter(2, { type: 'DECREMENT' }) ).toEqual(1);expect(counter(0, { type: 'INCREMENT' }) ).toEqual(1);需要留意的是,對(duì)于state為數(shù)組以及對(duì)象的這種情況,我們更要避免直接改變state本身而引起的副作用:
我們可以通過(guò)一個(gè)deep-freeze的庫(kù)來(lái)確保數(shù)組或者對(duì)象類型的state不能被更改,以便來(lái)檢測(cè)我們寫的reducer是否會(huì)產(chǎn)生副作用,所以當(dāng)reducer被定義成如下,
const initialState = []; const todos = (state = [], action) => {switch (action.type) {case 'ADD_TODO':state.push({index: action.index,text: action.text,completed: false});return state;default:return state;} };//凍住initialState,使其無(wú)法被更改 deepFreeze(initialState);expect(todos(initialState, {type: 'ADD_TODO',index: 0,text: 'redux',}) ).toEqual([{index: 0,text: 'redux',completed: false }]);我們發(fā)現(xiàn)reducer函數(shù)中的數(shù)組push方法未能生效,因?yàn)橐粋€(gè)被凍住的變量無(wú)法被更改。此時(shí)如果將產(chǎn)生副作用的push方法改為concat,
const todos = (state = [], action) => {switch (action.type) {case 'ADD_TODO':return state.concat({index: action.index,text: action.text,completed: false}); default:return state;} };可將以上concat的寫法用ES6的...spread方法代替為,
const todos = (state = [], action) => {switch (action.type) {case 'ADD_TODO':return [...state,{index: action.index,text: action.text,completed: false} ]; default:return state;} };該reducer返回了一個(gè)新的state,符合純函數(shù)的概念。類似的,借助slice數(shù)組方法,可以實(shí)現(xiàn)同樣純函數(shù)式的刪除todo或者是在指定位置插入todo,
const todos = (state = [], action) => {switch (action.type) {case 'REMOVE_TODO':return [...state.slice(0, action.index),...state.slice(action.index + 1) ]; case 'INSERT_TODO':return [...state.slice(0, action.index),{index: action.index,text: action.text,completed: false,},...state.slice(action.index + 1)]; default:return state;} };同樣當(dāng)state為object類型時(shí),我們也可以通過(guò)ES6 spread來(lái)實(shí)現(xiàn)純函數(shù)式的reducer,當(dāng)標(biāo)記某個(gè)todo項(xiàng)完成時(shí),
const todos = (state = {}, action) => {switch (action.type) {case 'TOGGLE_TODO':return {...state,completed: !action.completed}default:return state;} };等同于,
const todos = (state = {}, action) => {switch (action.type) {case 'TOGGLE_TODO':return Object.assign({}, state, {completed: !action.completed});default:return state;} };Redux Store --- 一個(gè)讓redux三原則融匯貫通的對(duì)象
通過(guò)創(chuàng)建store對(duì)象,我們可以調(diào)用其getState()、dispatch(action)、subscribe(listener)來(lái)依次獲取當(dāng)前state、執(zhí)行一次action、注冊(cè)當(dāng)state被改變時(shí)的回調(diào),以創(chuàng)建一個(gè)簡(jiǎn)單的計(jì)數(shù)器為例,
import { createStore } from Redux//創(chuàng)建一個(gè)store,reducer作為參數(shù)傳入 const store = createStore(counter);//執(zhí)行一個(gè)action store.dispatch({ type: 'INCREMENT' });//當(dāng)state被改變時(shí),在回調(diào)內(nèi)重新渲染DOM,執(zhí)行render() let unsubscribe = store.subscribe(render);//取消回調(diào)函數(shù)的注冊(cè) unsubscribe();關(guān)于redux store一個(gè)很重要的點(diǎn)在于,整個(gè)運(yùn)用了redux的應(yīng)用里,有且只有一個(gè)store,當(dāng)我們處理不同業(yè)務(wù)邏輯下的數(shù)據(jù)時(shí),我們需要通過(guò)不同的reducers來(lái)處理而不是對(duì)應(yīng)到多個(gè)store。所以這么一看來(lái)reducer的比重會(huì)比較大,我們可以利用redux 提供的 combineReducers() 合并多個(gè)reducers到一個(gè)根reducer。這樣組織reducers的方式有點(diǎn)類似react里一個(gè)根組件下有多個(gè)子組件。
createStore的源碼非常簡(jiǎn)潔,我們可以用不到20行的代碼來(lái)簡(jiǎn)單重現(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 }; };為了能夠在任何時(shí)刻返回對(duì)應(yīng)的state,我們需要一個(gè)state變量來(lái)記錄,getState()只需要負(fù)責(zé)返回它。
dispatch方法則負(fù)責(zé)把需要執(zhí)行的action傳給reducer,返回新的state,并同時(shí)執(zhí)行注冊(cè)過(guò)的回調(diào)函數(shù)。注冊(cè)的回調(diào)可能會(huì)有多個(gè),我們通過(guò)一個(gè)數(shù)組來(lái)保存即可。subscribe通過(guò)返回一個(gè)thunk函數(shù),來(lái)實(shí)現(xiàn)unsubscribe。最后為了能夠讓store.getState()可以獲得初始的state,直接dispatch一個(gè)空的action即可讓reducer返回initialState。
redux可以和react很好的結(jié)合一起使用,我們只需要把react對(duì)應(yīng)的ReactDOM.render()方法寫在subscribe回調(diào)里,而為了更優(yōu)雅的在react內(nèi)書寫redux,redux官方提供了react-redux
redux的源碼非常簡(jiǎn)單,它只有2kb大小,更多有關(guān)redux的介紹可以參考如下,
參考
redux
redux-cookbook
Getting Started with Redux by the author of Redux
react-redux
總結(jié)
以上是生活随笔為你收集整理的浅谈redux基本概念的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C# 身份证号码15位和18位验证
- 下一篇: Mysql 判断身份证号码是否满足15位