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