Redux 的設計思想很簡單,就兩句話。前端
(1)Web 應用是一個狀態機,視圖與狀態是一一對應的。編程
(2)全部的狀態,保存在一個對象裏面。redux
Store 就是保存數據的地方,你能夠把它當作一個容器。整個應用只能有一個 Store。數組
Redux 提供createStore
這個函數,用來生成 Store。服務器
import { createStore } from 'redux'; const store = createStore(fn);
上面代碼中,createStore
函數接受另外一個函數做爲參數,返回新生成的 Store 對象。dom
Store
對象包含全部數據。若是想獲得某個時點的數據,就要對 Store 生成快照。這種時點的數據集合,就叫作 State。函數式編程
當前時刻的 State,能夠經過store.getState()
拿到。函數
import { createStore } from 'redux'; const store = createStore(fn); const state = store.getState();
Redux 規定, 一個 State 對應一個 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什麼樣,反之亦然。spa
State 的變化,會致使 View 的變化。可是,用戶接觸不到 State,只能接觸到 View。因此,State 的變化必須是 View 致使的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。設計
Action 是一個對象。其中的type
屬性是必須的,表示 Action 的名稱。其餘屬性能夠自由設置,社區有一個規範能夠參考。
const action = { type: 'ADD_TODO', payload: 'Learn Redux' };
上面代碼中,Action 的名稱是ADD_TODO
,它攜帶的信息是字符串Learn Redux
。
能夠這樣理解,Action 描述當前發生的事情。改變 State 的惟一辦法,就是使用 Action。它會運送數據到 Store。
View 要發送多少種消息,就會有多少種 Action。若是都手寫,會很麻煩。能夠定義一個函數來生成 Action,這個函數就叫 Action Creator。
const ADD_TODO = '添加 TODO'; function addTodo(text) { return { type: ADD_TODO, text } }
const action = addTodo('Learn Redux');
上面代碼中,addTodo
函數就是一個 Action Creator。
store.dispatch()
是 View 發出 Action 的惟一方法。
import { createStore } from 'redux'; const store = createStore(fn); store.dispatch({ type: 'ADD_TODO', payload: 'Learn Redux' });
上面代碼中,store.dispatch
接受一個 Action 對象做爲參數,將它發送出去。
結合 Action Creator,這段代碼能夠改寫以下。
store.dispatch(addTodo('Learn Redux'));
Store 收到 Action 之後,必須給出一個新的 State,這樣 View 纔會發生變化。這種 State 的計算過程就叫作 Reducer。
Reducer 是一個函數,它接受 Action 和當前 State 做爲參數,返回一個新的 State。
const reducer = function (state, action) { // ... return new_state; };
整個應用的初始狀態,能夠做爲 State 的默認值。下面是一個實際的例子。
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
函數收到名爲ADD
的 Action 之後,就返回一個新的 State,做爲加法的計算結果。其餘運算的邏輯(好比減法),也能夠根據 Action 的不一樣來實現。
實際應用中,Reducer 函數不用像上面這樣手動調用,store.dispatch
方法會觸發 Reducer 的自動執行。爲此,Store 須要知道 Reducer 函數,作法就是在生成 Store 的時候,將 Reducer 傳入createStore
方法。
import { createStore } from 'redux'; const store = createStore(reducer);
上面代碼中,createStore
接受 Reducer 做爲參數,生成一個新的 Store。之後每當store.dispatc
h發送過來一個新的 Action,就會自動調用 Reducer,獲得新的 State。
爲何這個函數叫作 Reducer 呢?由於它能夠做爲數組的reduce
方法的參數。請看下面的例子,一系列 Action 對象按照順序做爲一個數組。
const actions = [ { type: 'ADD', payload: 0 }, { type: 'ADD', payload: 1 }, { type: 'ADD', payload: 2 } ]; const total = actions.reduce(reducer, 0); // 3
上面代碼中,數組actions
表示依次有三個 Action,分別是加0
、加1
和加2
。數組的reduce
方法接受 Reducer 函數做爲參數,就能夠直接獲得最終的狀態3
。
Reducer 函數最重要的特徵是,它是一個純函數。也就是說,只要是一樣的輸入,一定獲得一樣的輸出。
純函數是函數式編程的概念,必須遵照如下一些約束。
不得改寫參數 不能調用系統 I/O 的API 不能調用Date.now()或者Math.random()等不純的方法,由於每次會獲得不同的結果
因爲 Reducer 是純函數,就能夠保證一樣的State,一定獲得一樣的 View。但也正由於這一點,Reducer 函數裏面不能改變 State,必須返回一個全新的對象,請參考下面的寫法。
// State 是一個對象 function reducer(state, action) { return Object.assign({}, state, { thingToChange }); // 或者 return { ...state, ...newState }; } // State 是一個數組 function reducer(state, action) { return [...state, newItem]; }
最好把 State 對象設成只讀。你無法改變它,要獲得新的 State,惟一辦法就是生成一個新對象。這樣的好處是,任什麼時候候,與某個 View 對應的 State 老是一個不變的對象。
Store 容許使用store.subscribe
方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。
import { createStore } from 'redux'; const store = createStore(reducer); store.subscribe(listener);
顯然,只要把 View 的更新函數(對於 React 項目,就是組件的render
方法或setState
方法)放入listen
,就會實現 View 的自動渲染。
store.subscribe
方法返回一個函數,調用這個函數就能夠解除監聽。
let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();
上一節介紹了 Redux 涉及的基本概念,能夠發現 Store 提供了三個方法。
store.getState() store.dispatch() store.subscribe() import { createStore } from 'redux'; let { subscribe, dispatch, getState } = createStore(reducer);
createStore
方法還能夠接受第二個參數,表示 State 的最初狀態。這一般是服務器給出的。
let store = createStore(todoApp, window.STATE_FROM_SERVER)
上面代碼中,window.STATE_FROM_SERVER
就是整個應用的狀態初始值。注意,若是提供了這個參數,它會覆蓋 Reducer 函數的默認初始值。
下面是createStore
方法的一個簡單實現,能夠了解一下 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 函數負責生成 State。因爲整個應用只有一個 State 對象,包含全部數據,對於大型應用來講,這個 State 必然十分龐大,致使 Reducer 函數也十分龐大。
請看下面的例子。
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 的三個屬性。
ADD_CHAT:chatLog屬性 CHANGE_STATUS:statusMessage屬性 CHANGE_USERNAME:userName屬性
這三個屬性之間沒有聯繫,這提示咱們能夠把 Reducer 函數拆分。不一樣的函數負責處理不一樣屬性,最終把它們合併成一個大的 Reducer 便可。
const chatReducer = (state = defaultState, action = {}) => { return { chatLog: chatLog(state.chatLog, action), statusMessage: statusMessage(state.statusMessage, action), userName: userName(state.userName, action) } };
上面代碼中,Reducer 函數被拆成了三個小函數,每個負責生成對應的屬性。
這樣一拆,Reducer 就易讀易寫多了。並且,這種拆分與 React 應用的結構相吻合:一個 React 根組件由不少子組件構成。這就是說,子組件與子 Reducer 徹底能夠對應。
Redux 提供了一個combineReducers
方法,用於 Reducer 的拆分。你只要定義各個子 Reducer 函數,而後用這個方法,將它們合成一個大的 Reducer。
import { combineReducers } from 'redux'; const chatReducer = combineReducers({ chatLog, statusMessage, userName }) export default todoApp;
上面的代碼經過combineReducers方法將三個子 Reducer 合併成一個大的函數。
這種寫法有一個前提,就是 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()
作的就是產生一個總體的 Reducer 函數。該函數根據 State 的 key 去執行相應的子 Reducer,並將返回結果合併成一個大的 State 對象。
下面是combineReducer
的簡單實現。
const combineReducers = reducers => { return (state = {}, action) => { return Object.keys(reducers).reduce( (nextState, key) => { nextState[key] = reducers[key](state[key], action); return nextState; }, {} ); }; };
你能夠把全部子 Reducer 放在一個文件裏面,而後統一引入。
import { combineReducers } from 'redux' import * as reducers from './reducers' const reducer = combineReducers(reducers)
本節對 Redux 的工做流程,作一個梳理。
首先,用戶發出 Action。
store.dispatch(action);
而後,Store 自動調用 Reducer,而且傳入兩個參數:當前 State 和收到的 Action。 Reducer 會返回新的 State 。
let nextState = todoApp(previousState, action);
State 一旦有變化,Store 就會調用監聽函數。
// 設置監聽函數 store.subscribe(listener);
listener能夠經過store.getState()獲得當前狀態。若是使用的是 React,這時能夠觸發從新渲染 View。
function listerner() { let newState = store.getState(); component.setState(newState); }
下面咱們來看一個最簡單的實例。
const Counter = ({ value }) => ( <h1>{value}</h1> ); const render = () => { ReactDOM.render( <Counter value={store.getState()}/>, document.getElementById('root') ); }; store.subscribe(render); render();
上面是一個簡單的計數器,惟一的做用就是把參數·value·的值,顯示在網頁上。Store 的監聽函數設置爲render
,每次 State 的變化都會致使網頁從新渲染。
下面加入一點變化,爲Counter
添加遞增和遞減的 Action。
const Counter = ({ value, onIncrement, onDecrement }) => ( <div> <h1>{value}</h1> <button onClick={onIncrement}>+</button> <button onClick={onDecrement}>-</button> </div> ); 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( <Counter value={store.getState()} onIncrement={() => store.dispatch({type: 'INCREMENT'})} onDecrement={() => store.dispatch({type: 'DECREMENT'})} />, document.getElementById('root') ); }; render(); store.subscribe(render);
若是你以爲這篇文章對你有所幫助,那就順便點個贊吧,點點關注不迷路~
黑芝麻哇,白芝麻發,黑芝麻白芝麻哇發哈!
前端哇發哈