我不萌,但新。
2018年要結束了,這一年,我。。。算了,不多說了。
想起上半年看的 react,到現在也沒怎麼用過,都快忘光了,趁現在有時間我來捋捋,首先從 redux 下手(與 react 有啥什麼關係????)
為什麼用 redux
我想學習 redux 的同學多少知道它的作用,官方定義 redux 是一個面向 JavaScript 應用的可預測狀態容器。如果用過 vuex 的,就能明白它是幹嘛的,它們作用是一樣的,主要用來管理共享狀態。不同於 vuex 與 vue 的關係,redux 與 react 是解耦的,雖然 redux 通常用在 react 中。至於為啥要用 redux,我覺得一張圖更好說明問題:
在react應用中,父子元件之間的資料傳遞比較容易,但是在複雜元件層級關係中,如圖左所示的那樣,資料的傳遞就顯得很麻煩了,而且容易混亂,這時如果有一個容器幫我們統一管理資料,並能共享到每個元件,那就很方便我們開發了,如圖右所示。而redux正是一個提供這樣功能的資料框架。
基本概念
概念這東西我不好說,我還是拿一張圖說明:
在 react 中使用 redux 時,某元件需要一個資料,那麼它就需要觸發一個 action,通過 dispatch 給 store,表明自己需要資料啦,action 是行為動作,它告訴 store 需要什麼樣的資料,它也是 store 資料的唯一來源。store 相當於一個管理員,它自己不直接處理資料,而是把當前的資料和收到的 action 告訴 reducer,讓 reducer 來處理資料,並將新資料返還給 store,再由 store 傳遞給元件,元件拿到的就是目標資料了。
初看時,可能有點繞,我們可以理解為一個在圖書館借書的場景。元件( Component)就相當於借書人,他要告訴管理員(Store),說:"我要借《時間簡史》",那麼說的這句話就可以理解為 Action,管理員(Store)聽到後,他不可能記得每本書的位置,於是就檢視記錄本看看書的資訊,那麼這個記錄本就相當於 Reducer,找到書的資訊後,管理員(Store)就把資訊告訴了借書人(Component)。可能比喻並不是那麼恰當,但表達的意思也差不多了。redux 就是圍繞 Action,Store,Reducer 這三點來展開的。
基本使用
為了方便起見,我直接用 create-react-app 腳手架建立一個專案來演示程式碼,安裝以及建立我就略過了。把專案中多餘的檔案刪除,src 目錄下只保留一個 index.js 檔案。
之前說過,redux 和 react 是解耦的,那麼我們先只在 index.js 檔案中來使用一下 redux,使用前別忘記安裝以下 redux
npm install redux
複製程式碼
index.js
// 引入 createStore 方法,用於建立 store
import { createStore } from "redux"
// 預設初始狀態
const defaultState = {
a: 1
}
// 建立 reducer
function reducer(state = defaultState, action) {
switch (action.type) {
case "ADD":
return Object.assign({}, state, {
b: action.num
});
default:
return state;
}
}
// 建立 store
const store = createStore(reducer)
// 獲取 state
console.log('dispatch action 之前的資料:',store.getState())
// 派發 action
store.dispatch({
type:"ADD",
num:2
})
// 獲取更新後的 state
console.log('dispatch action 之後的資料:',store.getState())
複製程式碼
執行結果如圖:
以上就是一個redux的最基本的使用過程,我們來看下。
reducer
首先說一下 reducer,它用來處理資料,本質上是一個函式,有兩個引數 state 和 action,state 即是儲存的狀態,如果不給他賦初始值,那麼它就等於undefined
,我在這裡給了它一個初始值 defaultState。action 即是行為,它是一個物件,必須有一個名為 type 的欄位來表示將要執行的動作,如程式碼裡的ADD
就是我傳入的一個表示新增的行為,(注意type的值可任意,應儘量語意化)。除了 type 欄位外,你可以任意新增自己需要的欄位,比如這裡我傳裡一個 num 欄位,我需要將它新增到 state 中去。
reducer的注意事項:
- 不要直接修改 state。可以看到,我再程式碼裡使用 Object.assign() 新建了一個副本,再返回的。若不建立副本,redux 的所有操作都將指向記憶體中的同一個 state,所有的 state 都將被最後一次操作的結果所取代,我們將無法追溯 state 變更的歷史記錄。
- 在 default 情況下返回舊的 state。即沒有 action 時,一定要返回舊的 state。
action
前面說了 action 本質上是一個JavaScript中的物件,約定 action 內必須使用一個字串型別的 type 欄位來表示將要執行的動作。多數情況下,type 會被定義成字串常量。當應用規模越來越大時,建議使用單獨的模組或檔案來存放 action,如下:
// actionTypes.js
export const ADD = "ADD"
複製程式碼
import { ADD } from './actionTypes'
const action = {
type: ADD,
num: 2
}
複製程式碼
通過store.dispatch()
方法將 action 傳到 store。
store
store將 action 和 reducer 聯絡起來,維持應用的 state,可以通過store.getState()
來獲取 state,通過 store.dispatch()
來更新 state。我們通過引入 createStore 方法,並傳入 reducer 為第一個引數來建立 store。
在 react 中使用 redux
前面介紹了 redux 的基本使用方法,但是我們最終還是要使用在 react 中,現在我們就結合 react 來用下 redux。
首先,修改專案目錄,新增store
目錄,用來存放 redux 相關檔案,而src/index.js
檔案為元件入口:
我們就寫一個爛大街的計數器的例子,通過加減按鈕實現數字的增減。
首先是store中的程式碼:
actionTypes.js 用來統一存放 action 的 type 型別,並匯出
// 增
export const ADD = "ADD";
// 減
export const REDUCE = "REDUCE";
複製程式碼
reducer.js 建立 reducer 函式,並匯出
import { ADD, REDUCE } from "./actionTypes";
const defaultVal = 0;
function reducer(state = defaultVal, action) {
switch (action.type) {
case ADD:
let newVal1 = state + 1;
return newVal1;
case REDUCE:
let newVal2 = state - 1;
return newVal2;
default:
return state;
}
}
export default reducer;
複製程式碼
index.js 檔案建立 store,並匯出
import { createStore } from "redux";
import reducer from "./reducer";
const store = createStore(reducer);
export default store;
複製程式碼
然後 src/index.js 檔案,是元件入口:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { ADD, REDUCE } from "./store/actionTypes";
import store from "./store";
class APP extends Component {
render() {
return (
<div>
<div>{store.getState()}</div>
<div>
<button onClick={this.handleReduce.bind(this)}>-</button>
<button onClick={this.handleAdd.bind(this)}>+</button>
</div>
</div>
);
}
// 執行減操作
handleReduce() {
store.dispatch({
type: REDUCE
});
}
// 執行加操作
handleAdd() {
store.dispatch({
type: ADD
});
}
}
ReactDOM.render(<APP />, document.getElementById("root"));
複製程式碼
此時執行專案,在瀏覽器中實現加減操作。但是會發現並沒有用,怎麼點選都沒有預想的效果。其實這裡少了關鍵的一步,在 react 中使用不同於第一個例子裡那樣,第一個例子中我 dispatch 後會重新獲取 state,也就是store.getState()
,但在 react 元件裡使用時,可以看到我只是獲取了一次 state,那麼我想要的效果是每當我 dispatch 後,這個 state 會自動更新。
redux 中提供了這樣的一個功能,就是store.subscribe(listener)
,它是 store 下的一個方法,會新增一個變化監聽器,每當 dispatch action 的時候就會執行。那麼有了監聽器,每次 dispatch 時我們需要執行啥呢?沒錯,我們給元件重新 render 下。修改ReactDOM.render
這部分程式碼,如下:
function render() {
ReactDOM.render(<APP />, document.getElementById("root"));
}
render();
store.subscribe(render);
複製程式碼
用一個函式包裹ReactDOM.render
方法,並執行,這個函式其實就是 listener 了,把它傳給 subscribe,這樣每次 dispatch 後,就可以更新 state 了。
以上就是結合 react 和 redux 的一個簡單入門小例子,我寫的不一定清楚,但是程式碼敲一遍也能瞭解個大概。
補充
兩天後,我覺得還是要補充介紹下 redux 三大原則:
- 整個應用只能有一個 store,即
const store = createStore(reducer)
這樣的建立 store,只能存在一個,保證單一資料來源; - state 是隻讀的,唯一改變 state 的方法就是觸發 action。通過
dispatch(action)
來表達想要修改的意圖,所有的修改會被集中化處理; - reducer 必須是純函式。前面也說過了,reducer 是接收先前的 state 和 action,並返回新的 state,而不是直接修改 state,這就是符合純函式的約束。
什麼是純函式?
- 不能改寫引數,我們是通過引數返回新的數值的,同樣的輸入,得到同樣的輸出
- 不能呼叫系統 I/O 的API
- 不能呼叫Date.now()或者Math.random()等不純的方法,因為每次會得到不一樣的結果
文中有寫的不對的地方,望有大佬指點。