Redux入門學(xué)習(xí)系列教程(一)

Redux入門學(xué)習(xí)系列教程(一)
Redux入門學(xué)習(xí)系列教程(二)
Redux入門學(xué)習(xí)系列教程(三)
Redux入門學(xué)習(xí)系列教程(四)

作者結(jié)合文檔,給出入門Redux學(xué)習(xí) Demo示例

https://github.com/guangqiang-liu/react-native-reduxDemo

本教程主要講解Redux的核心 API 以及 工作流程

什么是Redux

Redux 是 JavaScript 狀態(tài)容器,提供可預(yù)測化的狀態(tài)管理??梢宰屇銟?gòu)建一致化的應(yīng)用,運(yùn)行于不同的環(huán)境(客戶端、服務(wù)器、原生應(yīng)用),并且易于測試。不僅于此,它還提供 超爽的開發(fā)體驗(yàn),比如有一個(gè)時(shí)間旅行調(diào)試器可以編輯后實(shí)時(shí)預(yù)覽

安裝Redux

  • npm install redux --save

多數(shù)情況下,我們還需要使用 React 綁定庫和開發(fā)者工具。

  • npm install redux-devtools --save-dev

核心API

  • Action
  • Reducer
  • Store

Redux三大原則

  • 單一數(shù)據(jù)源

整個(gè)應(yīng)用的 state 被儲(chǔ)存在一棵 object tree 中,并且這個(gè) object tree 只存在于一個(gè) 唯一的store 中。

  • State 只讀

惟一改變 state 的方法就是觸發(fā) action,action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象。

  • 使用純函數(shù)來執(zhí)行修改

為了描述 action 如何改變 state tree ,你需要編寫 reducers。

Action

  • Action官方解釋:把數(shù)據(jù)從應(yīng)用傳到 store 的有效載荷。它是 store 數(shù)據(jù)的唯一來源。一般來說你會(huì)通過 store.dispatch() 將 action 傳到 store。
  • 其實(shí)Action就是一個(gè)普通的對(duì)象,其中的type屬性是必須的,表示 Action 的名稱。其他屬性可以自由設(shè)置,參照 Flux 標(biāo)準(zhǔn) Action 獲取關(guān)于如何構(gòu)造 action 的建議。

redux約定 Action 內(nèi)使用一個(gè)字符串類型的type字段來表示將要執(zhí)行的動(dòng)作名稱。

{
    type: 'ADD_TODO'
}

除了type 之外,Action還可以攜帶需要的數(shù)據(jù)。

{
    type: 'ADD_ITEM',
    text: 'Learn Redux'
}

Action Creator

View 要發(fā)送多少種消息,就會(huì)有多少種 Action。如果都手寫,會(huì)很麻煩??梢远x一個(gè)函數(shù)來生成 Action,這個(gè)函數(shù)就叫 Action Creator。

const ADD_TODO = '添加 TODO'

function addTodo(text) {
  return {
    type: ADD_TODO,
    text:text
  }
}

const action = addTodo('Learn Redux')

上面代碼中,addTodo函數(shù)就是一個(gè) Action Creator,在 Redux 中的 Action Creator 只是簡單的返回一個(gè) action而已

傳統(tǒng)的 Flux 實(shí)現(xiàn)中,當(dāng)調(diào)用 Action Creator時(shí),一般會(huì)觸發(fā)一個(gè) dispatch

function addTodoWithDispatch(text) {
  const action = {
    type: ADD_TODO,
    text:text
  }
  dispatch(action)
}

在Redux 中只需把 Action Creator的結(jié)果傳給 dispatch() 方法即可發(fā)起一次 dispatch 過程。

dispatch(addTodo(text))

或者創(chuàng)建一個(gè)被綁定的 Action Creator 來自動(dòng)觸發(fā)dispatch

const bindAddTodo = (text) => dispatch(addTodo(text))

然后直接調(diào)用這個(gè)函數(shù)即可完成一次dispatch過程

bindAddTodo(text)

注意:
store 里能直接通過 store.dispatch() 調(diào)用dispatch()方法,但是多數(shù)情況下我們選擇使用 react-redux 提供的 connect() 幫助器來調(diào)用。bindActionCreators() 可以自動(dòng)把多個(gè) action 創(chuàng)建函數(shù) 綁定到 dispatch() 方法上

Reducer

  • Store 收到 Action 以后,必須給出一個(gè)新的 State,這樣 View 才會(huì)發(fā)生變化。這種 State 的計(jì)算過程就叫做 Reducer。

  • Reducer 就是一個(gè)普通的函數(shù)

  • 當(dāng)被Redux調(diào)用的時(shí)候會(huì)給Reducer傳遞兩個(gè)參數(shù):State 和 Action

  • 它會(huì)根據(jù) Action 的type屬性來對(duì)舊的 State 進(jìn)行操作,返回新的State

const defaultState = 10

const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case Constants.INCREASE:
      return state + 1
    case Constants.DECREASE:
      return state - 1
    default:
      return state
  }
}

const state = reducer(10, {
  type: Constants.INCREASE
})

上面代碼中,reducer函數(shù)收到名為INCREASE的 Action 后,就返回一個(gè)新的 State,作為加法的計(jì)算結(jié)果。

實(shí)際開發(fā)中,Reducer 函數(shù)不用像上面這樣手動(dòng)去調(diào)用,store.dispatch方法會(huì)觸發(fā) Reducer 的自動(dòng)調(diào)用,為此,Store 需要知道 Reducer 函數(shù),做法就是在生成 Store 的時(shí)候,將 Reducer 傳入到createStore函數(shù)中。

import { createStore } from 'redux'
import reducer from '../reducer'

// 創(chuàng)建store
const store = createStore(reducer)

上面代碼中,createStore接受 Reducer 作為參數(shù),生成一個(gè)新的 Store。這樣以后每當(dāng)store.dispatch發(fā)送過來一個(gè)新的 Action,就會(huì)自動(dòng)調(diào)用 Reducer,得到新的 State

Reducer 的拆分

真正開發(fā)項(xiàng)目的時(shí)候State會(huì)涉及很多功能,在一個(gè)Reducer函數(shù)中處理所有邏輯會(huì)非?;靵y,所以需要拆分成多個(gè)子Reducer,每個(gè)子Reducer只處理它管理的那部分State數(shù)據(jù)。然后在由一個(gè)主rootReducers來專門管理這些子Reducer。

Redux提供了一個(gè)方法:combineReducers專門來管理這些子Reducer

import {createStore, combineReducers} from 'redux'

const list = (state = [], action) => {
  switch (action.type) {
    case ADD_ITEM:
      return [createItem(action.text), ...state]
    default:
      return state
  }
}

const counter = (state = defaultState, action) => {
  switch (action.type) {
    case Constants.INCREASE:
      return state + 1
    case Constants.DECREASE:
      return state - 1
    default:
      return state
  }
}

let rootReducers = combineReducers({list, counter})

combineReducers 生成了一個(gè)類似于Reducer的函數(shù)。為什么是類似于尼,因?yàn)樗皇钦嬲腞educer,它只是一個(gè)調(diào)用Reducer的函數(shù),只不過它接收的參數(shù)與真正的Reducer一模一樣

combineReducers 核心源碼解讀

function combineReducers(reducers) {

  // 過濾reducers,把非function類型的過濾掉~
  var finalReducers = pick(reducers, (val) => typeof val === 'function');

  var defaultState = mapValues(finalReducers, () => undefined);

  return function combination(state = defaultState, action) {
    // finalReducers 是 reducers
    var finalState = mapValues(finalReducers, (reducer, key) => {

      // state[key] 是當(dāng)前Reducer所對(duì)應(yīng)的State,可以理解為當(dāng)前的State
      var previousStateForKey = state[key];
      var nextStateForKey = reducer(previousStateForKey, action);

      return nextStateForKey;      
    });

    // finalState 是 Reducer的key和stat的組合。。
  }
}

從上面的源碼可以看出,combineReducers生成一個(gè)類似于Reducer的函數(shù)combination。

注意:
當(dāng)使用combination的時(shí)候,combination會(huì)把所有子Reducer都執(zhí)行一遍,子Reducer通過action.type 匹配操作,因?yàn)槭菆?zhí)行所有子Reducer,所以如果兩個(gè)子Reducer匹配的action.type是一樣的,那么都會(huì)匹配成功。

Store

  • Store 就是保存數(shù)據(jù)的地方,你可以把它看成一個(gè)容器。整個(gè)應(yīng)用只能有一個(gè) Store。
  • Redux 提供createStore這個(gè)函數(shù),用來生成 Store。
  • 再次強(qiáng)調(diào)一下: Redux 應(yīng)用只有一個(gè)單一的 store。當(dāng)需要拆分?jǐn)?shù)據(jù)處理邏輯時(shí),你應(yīng)該使用 reducer 組合 而不是創(chuàng)建多個(gè) store。
  • 根據(jù)已有的 reducer 來創(chuàng)建 store 是非常容易的。在前一個(gè)章節(jié)中,我們使用 combineReducers() 將多個(gè) reducer 合并成為一個(gè)?,F(xiàn)在我們將其導(dǎo)入,并傳遞給createStore函數(shù)。
import { createStore } from 'redux'
import reducer from '../reducer'

const store = createStore(reducer)

Store提供暴露出四個(gè)API方法

  • store.getState(): 獲取應(yīng)用當(dāng)前State。
  • store.subscribe():添加一個(gè)變化監(jiān)聽器。
  • store.dispatch():分發(fā) action。修改State。
  • store.replaceReducer():替換 store 當(dāng)前用來處理 state 的 reducer。
import { createStore } from 'redux'
let { subscribe, dispatch, getState, replaceReducer} = createStore(reducer)

下面是createStore方法的一個(gè)簡單實(shí)現(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 };
}

發(fā)起 Actions

現(xiàn)在我們已經(jīng)創(chuàng)建好了 store ,讓我們來驗(yàn)證一下!雖然還沒有界面,我們已經(jīng)可以測試數(shù)據(jù)處理邏輯了。

import { addTodo, toggleTodo, setVisibilityFilter, VisibilityFilters } from './actions'

// 打印初始狀態(tài)
console.log(store.getState())

// 每次 state 更新時(shí),打印日志
// 注意 subscribe() 返回一個(gè)函數(shù)用來注銷監(jiān)聽器
let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
)

// 發(fā)起一系列 action
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))

// 停止監(jiān)聽 state 更新
unsubscribe()

可以看到 store 里的 state 是如何變化的:

img

可以看到,在還沒有開發(fā)界面的時(shí)候,我們就可以定義程序的行為。而且這時(shí)候已經(jīng)可以寫 reducer 和 action 創(chuàng)建函數(shù)的測試。不需要模擬任何東西,因?yàn)樗鼈兌际羌兒瘮?shù)。只需調(diào)用一下,對(duì)返回值做斷言,寫測試就是這么簡單。

Redux 工作流

flow
  • 首先,用戶發(fā)出 Action
store.dispatch(action)
  • 然后,Store 自動(dòng)調(diào)用 Reducer,并且傳入兩個(gè)參數(shù):當(dāng)前 State 和收到的 Action。 Reducer 會(huì)返回新的 State
let nextState = todoApp(previousState, action)
  • State 一旦有變化,Store 就會(huì)調(diào)用監(jiān)聽函數(shù)
// 設(shè)置監(jiān)聽函數(shù)
store.subscribe(listener)
  • listener可以通過store.getState()得到當(dāng)前狀態(tài)。如果使用的是 React,這時(shí)可以觸發(fā)重新渲染 View
function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}

參考文獻(xiàn)

阮一峰講解redux
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html

http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html

http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html

Redux中文文檔

Redux中文文檔

Redux其他系列教程

https://github.com/lewis617/react-redux-tutorial

https://github.com/kenberkeley/redux-simple-tutorial

官方todoApp示例分析

Redux入門學(xué)習(xí)系列教程(一)
Redux入門學(xué)習(xí)系列教程(二)
Redux入門學(xué)習(xí)系列教程(三)
Redux入門學(xué)習(xí)系列教程(四)

總結(jié)

本系列教程是參照阮老師的Redux入門教程文章和Redux中文文檔進(jìn)行的整合和拓展。更多更詳細(xì)的Redux使用方式請(qǐng)參照上面的參考文獻(xiàn)。

福利時(shí)間

  • 作者React Native開源項(xiàng)目OneM地址(按照企業(yè)開發(fā)標(biāo)準(zhǔn)搭建框架設(shè)計(jì)開發(fā)):https://github.com/guangqiang-liu/OneM (歡迎小伙伴們 star)
  • 作者簡書主頁:包含50多篇RN開發(fā)相關(guān)的技術(shù)文章http://www.itdecent.cn/u/023338566ca5 (歡迎小伙伴們:多多關(guān)注,多多點(diǎn)贊)
  • 作者React Native QQ技術(shù)交流群:620792950 歡迎小伙伴進(jìn)群交流學(xué)習(xí)
  • 友情提示:在開發(fā)中有遇到RN相關(guān)的技術(shù)問題,歡迎小伙伴加入交流群(620792950),在群里提問、互相交流學(xué)習(xí)。交流群也定期更新最新的RN學(xué)習(xí)資料給大家,謝謝支持!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容