做爲前端開發者,相信即便沒有使用過 redux,必定也聽過 redux 的大名。咱們經常和 react 搭配一塊兒使用 redux,這樣更能發揮 redux 的做用,由於這類庫容許你經過改變 state 來更新試圖。但 redux 一樣能夠與 Angular、jQuery 等庫搭配使用,一樣也能夠在原生的 js 中使用。瞭解清楚了在原生 js 中的使用後更容易幫助開發者理解 redux。javascript
簡單說,Redux 是 JavaScript 的一個狀態容器,提供可預測化的狀態管理。html
隨着 JavaScript 單頁應用開發日趨複雜,JavaScript 須要管理比任什麼時候候都要多的 state (狀態)。 這些 state 可能包括服務器數據、本地生成還沒有持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。前端
管理不斷變化的 state 很是困難。若是一個 model 的變化會引發另外一個 model 變化,那麼當 view 變化時,就可能引發對應 model 以及另外一個 model 的變化,依次地,可能會引發另外一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在何時,因爲什麼緣由,如何變化已然不受控制。 當系統變得錯綜複雜的時候,想重現問題或者添加新功能就會變得舉步維艱。java
而 Redux 要作的就是試圖讓 state 的變化變得可預測。react
redux 能夠用這三個基本原則來描述:redux
整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。瀏覽器
惟一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。bash
爲了描述 action 如何改變 state tree,你須要編寫 reducers服務器
爲了可以在原生 js 中使用 Redux,咱們須要先來了解一個 Redux 的基本使用和經常使用的概念。架構
store 是 Redux 中最核心的概念,是 Redux 架構的根本。前文提到過 Redux 是一個可預測狀態的 「容器」,這裏所說的容器,其實就是指 store。store 是唯一的, 保存着整個頁面的狀態數據數,而且爲開發者提供了重要的 API。
事實上,store 就是一個 JavaScript 對象,裏面包含了如下方法:
import { createStore } from 'redux';
import reducer from './reducers';
// 利用 createStore 來建立
let store = createStore(reducer);
複製代碼
reducer 參數爲 createStore 的必傳參數,也就是說,當開發者建立一個 store 的時候,必須同時定義一個 reducer,用來告知 store 數據狀態應該如何根據 action 進行變動。
action 描述了狀態變動的信息,也就是須要頁面作出的變化。這是由開發者定義並藉助於 store.dispatch 派發的,action 本質上也是一個 JavaScript 對象。咱們約定,action 對象內必需要有一個 type 屬性,做爲描述這個 action 的名稱來惟一肯定這個 action。
const action = {
type: 'ADD_TODO',
text: 'Build my first Redux app'
}
複製代碼
定義好了 action 以後,能夠利用 dispatch 派發這個 action
store.dispatch(action)
複製代碼
action 描述了一種變化,並攜帶了這種變化的數據信息,可是 action 只是描述了有事情發生這一事實,並無描述應用如何更新state。真正執行這種變化並生成正確數據狀態的是 reducer 函數。
爲了保證數據的可預測性,即相同的輸入有相同的輸出,因此 reducer 必須是一個純函數。下面是一個最簡單的 reducer 函數:
// initialState 爲初始化的 state
function updateState(state = initialState, action) {
// 這裏暫不處理任何 action,
// 僅返回傳入的 state。
return state
}
複製代碼
固然,一個完整的 reducer 函數可能須要對多個 action 進行處理,以下:
function updateState(state = initialState, action) {
switch (action.type) {
case 'case1':
return newState1;
case 'case2':
return newState2;
case 'case3':
return newState3;
default:
return state
}
}
複製代碼
嚴格的單向數據流 是 Redux 架構的設計核心。這意味着應用中全部的數據都遵循相同的生命週期,這樣可讓應用變得更加可預測且容易理解。
Redux 應用中數據的生命週期遵循下面 4 個步驟:
備註: 當咱們執行 store.dispatch 派發一個 action 以後,Redux 會 「自動」 幫咱們執行處理變化並更新數據的 reducer 函數。從 store.dispatch 到 reducer 這個過程能夠認爲是由 Redux 內部處理的。
前面介紹了 Redux 的一些基礎知識,可是可能仍是感到無從下手,有點 「鏡花水月」 的感受,這是由於還須要打通 「任督二脈」,將全部的知識點進行鏈接,下面經過一個簡單的在原生 JavaScript 中的實例來進一步加深理解。
如下是一個很是常見的場景,頁面截圖來自豆瓣 App,我能夠對影評 「點贊」 或者 「踩」,而且記錄 「點贊」 和 「踩」 的數量。以下圖所示:
組件邏輯比較簡單,點擊 「有用」 按鈕,頁面展現點贊數 + 1,點擊 「沒用」 按鈕,頁面展現沒用的數目 + 1。接下來咱們來看下在原生 JavaScript 中利用 Redux 架構應該如何實現:
<!DOCTYPE html>
<html lang="en">
<head>
<title>redux 在原生 JavaScript 中的使用</title>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body>
<div class="wrap">
<div class="like">點贊 <span>0</span></div>
<div class="hate">踩 <span>0</span></div>
</div>
</body>
</html>
複製代碼
直接在瀏覽器打開上面的 html 文件, 而後在控制檯經過全局變量 window.Redux 便可訪問 Redux 對象
須要有兩個狀態來存儲點擊 「有用」 和 「無用」 的人數
// 定義初始狀態
var initialState = {
likeNum: 0, // 點讚的總數
hateNum: 0 // 踩的總數
}
複製代碼
分析後得知,這裏只須要兩個 action,一個爲點擊 「有用」 按鈕,一個爲點擊 「沒用」 按鈕。
// 定義有用 action
var likeAction = {
type: 'like'
}
// 定義沒用 action
var hateAction = {
type: 'hate'
}
複製代碼
function reducer(state = initialState, action) {
switch(action.type){
case 'like':
return {
likeNum: state.likeNum + 1,
hateNum: state.hateNum
};
case 'hate':
return {
likeNum: state.likeNum,
hateNum: state.hateNum + 1
};
default:
return state;
}
}
複製代碼
// 引入 createStore
var createStore = window.Redux.createStore;
// 建立 store
var store = createStore(reducer);
複製代碼
// 點擊有用按鈕,派發 likeAction
var likeButton = document.querySelector('.like');
likeButton.addEventListener('click', function(){
store.dispatch(likeAction);
})
// 點擊沒用按鈕,派發 hateAction
var hateButton = document.querySelector('.hate');
hateButton.addEventListener('click', function(){
store.dispatch(hateAction);
})
複製代碼
// 定義更新視圖的方法
var render = function() {
document.querySelector('.like span').innerText = store.getState().likeNum;
document.querySelector('.hate span').innerText = store.getState().hateNum;
}
// 監聽 store 的變化,store 發生變化後調用 render 方法,利用原生 JavaScript 的方法更新視圖
store.subscribe(render);
複製代碼
以上就是 Redux 的一個最簡單的實例了,比較清晰地闡述了 Redux 的思想,Redux 徹底能夠獨立於 React 而存在。
它的架構也很簡單:須要開發者預先定義 action 及 reducer 函數,同時在恰當的時候派發 action,即 dispatch(action)。若是所編寫的 reducer 函數沒有問題,那麼在正常更新狀態以後,就能夠經過 store.subscribe 註冊每一次數據更新後的回調邏輯,這個回調邏輯每每就是對頁面的渲染。在回調邏輯中,使用 store.subscribe() 獲取最新數據,完成正確的頁面響應,貌似一個發佈訂閱系統。
使用過 React 搭配 Redux 開發的讀者可能對 store.subscribe() 有些陌生,由於它已經由 react-redux 庫進行了封裝,這也是 store 數據更新後即可以直接觸發相關組件從新渲染的緣由。 後續有時間的話會講一下 react-redux 相關的內容。