[React] 11 - Redux: redux

Ref: Redux中文文檔javascript

Ref: React 讀書會 - B團 - Level 19 Redux 深刻淺出html

Ref: React+Redux 分享會前端

Ruan Yifeng, Redux 架構: 教程一有代碼)、教程二教程三java

 

Redux 入門教程 #1 課程介紹react

Redux 入門教程 #2 爲何須要 Reduxgit

Redux 入門教程 #3 什麼是 Reduxgithub

Redux 入門教程 #4 建立頁面mongodb

Redux 入門教程 #5 單獨使用 Redux編程

Redux 入門教程 #6 使用 react-reduxredux

 

 

課前閱讀


(1) 首先,複習 Flux[React] 07 - Flux: react communicates with mongodb

  • 原始方案中,pages目錄下其實也是組件的效果;
  • 右邊的flux pattern到底帶來了什麼?

詳見Flux章節和連接。

     

  

(2) 以後,讓咱們開始對 Redux 認真學習。

2015年,Redux 出現,將 Flux 與函數式編程結合一塊兒,很短期內就成爲了最熱門的前端架構。

 

* 是什麼?

  • actions
  • components
  • constants
  • reducers
  • store

管理數據的狀態容器,單獨的js庫。

 

* 解決什麼問題?

A如何把參數傳給B?react的單向流很差處理。

或者,使用嵌入式寫法,好比A觸發事件,B監聽事件。

但嵌入式寫法不推薦。

 

  • React兩大不利之處,
  • 代碼結構
  • 組件之間的通訊
  • 從需求角度看,
  • 用戶的使用方式複雜
  • 不一樣身份的用戶有不一樣的使用方式(好比普通用戶管理員
  • 多個用戶之間能夠協做
  • 與服務器大量交互,或者使用了WebSocket
  • View要從多個來源獲取數據
  • 從組件角度看,
  • 某個組件的狀態,須要共享
  • 某個狀態須要在任何地方均可以拿到
  • 一個組件須要改變全局狀態
  • 一個組件須要改變另外一個組件的狀態

 

 

 

原理解剖


1、基本步驟

第一步,從老阮開始。設計一個以下的"加減計數器"。

 

 

[1] ----------------------------------------------------------------------------------------------------------------------------------------> Store & State

兩個重要的功能:

(1)建立惟一的一個store。

(2)獲取該store裏面的state。

import { createStore } from 'redux';
const store = createStore(reducer);      // 1.接受另外一個函數做爲參數,返回新生成的 Store 對象
const state = store.getState(); // 2.獲得當前時刻的State,一個state對應一個view

Jeff: 在flux中的state計算問題放在了reducer中,reducerstore經過createStore()維持關係。

 

[2] ----------------------------------------------------------------------------------------------------------------------------------------> Action

用戶操做 on view --> action --> state

# 想要的結果

const action = { type: 'ADD_TODO',          // Action的名稱,是必須的;Action自己是個對象 payload: 'Learn Redux'         // Action的貨物,不是必須的; };

使用 Action Creator,避免所有「手寫定義」帶來的麻煩,返回以上的結果。

const ADD_TODO = '添加 TODO';

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

const action = addTodo('Learn Redux');

 

[3] ----------------------------------------------------------------------------------------------------------------------------------------> store.dispatch()

經過store.dispatch (...),view 發出 action (參數) to store.

store.dispatch({
  type   : 'ADD_TODO',
  payload: 'Learn Redux'
});

或者,對象經過函數生成:
store.dispatch( // <--- 推薦
 addTodo('Learn Redux')
);

 

[4] ----------------------------------------------------------------------------------------------------------------------------------------> Reducer

Store 收到 Action 之後,必須給出一個新的 State,View 才能發生變化。

ReducerState 的計算過程,based on old state。

const defaultState = 0;
const reducer = (state = defaultState, action) => {

/**
* reducer 函數裏面不能改變 State,必須返回一個全新的對象
*/ switch (action.type) { case 'ADD': return state + action.payload; default: return state; }
};
.................................................... const state = reducer(1, { type: 'ADD', payload: 2 });

由於createStore(reducer),每當store.dispatch發送過來一個新的 Action,就會自動調用 Reducer,獲得新的 State。 

reducer 做爲參數,爲 "action們" 提供趁手的工具。

const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];

/**
* action是數據,打算使用reducer這個工具來處理,最後返回一個新的狀態
*/ const total = actions.reduce(reducer, 0);   // 3

 

另外:最好把 State 對象設成只讀,這樣你無法改變它,要獲得新的 State,惟一辦法就是生成一個新對象。

// State 是一個對象
function reducer(state, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一個數組
function reducer(state, action) {
  return [...state, newItem];
}

 

[5] ---------------------------------------------------------------------------------------------------------------------------------------- listener

store改變,提醒 listener,觸發view改變。

解除 listener 居然是利用其"返回值"

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();

 

 

2、Store 的實現

Store 提供了三個方法:

    • store.getState()   [1]
    • store.dispatch()   [3]
    • store.subscribe()   [5]

store 的三個部分,以下:

import { createStore } from 'redux';
let { subscribe, dispatch, getState } = createStore(reducer);
let store                  = createStore(todoApp, window.STATE_FROM_SERVER)  
// 第二個參數表示:整個應用的狀態初始值,會覆蓋 Reducer 函數的默認初始值

【createStore內部實現暫不深究】 

 

 

3、Reducer 的拆分

  • 原始版本:Switch 寫法,各類狀況都在一個函數中

三種 Action 分別改變 State 的三個屬性。

const chatReducer = (state = defaultState, action = {}) => {
  const { type, payload } = action;
switch (type) {
............................................................. ADD_CHAT:chatLog屬性 case ADD_CHAT: return Object.assign({}, state, { chatLog: state.chatLog.concat(payload) });
............................................................. CHANGE_STATUS:statusMessage屬性 case CHANGE_STATUS: return Object.assign({}, state, { statusMessage: payload });
............................................................. CHANGE_USERNAME:userName屬性 case CHANGE_USERNAME: return Object.assign({}, state, { userName: payload });
default: return state; } };

 

  • 拆分版本:Reducer 函數中的 "switch部分" 被拆成了三個小函數,每個負責生成對應的屬性
const chatReducer = (state = defaultState, action = {}) => {
  return {
    chatLog:       chatLog(state.chatLog, action),              // 函數名字重複也礙眼,參數也不太想多寫
    statusMessage: statusMessage(state.statusMessage, action),
    userName:      userName(state.userName, action)
  }
};

理由: 這種拆分與 React 應用的結構相吻合:一個 React 根組件由不少子組件構成。這就是說,子組件與子 Reducer 徹底能夠對應。

 

  • 專業拆分使用combineReducers方法,專門用於拆分

這種寫法有一個前提,就是 State 的屬性名必須與子 Reducer 同名。

import { combineReducers } from 'redux';

/**
* 先找個地兒,定義各個子 Reducer 函數,

* 而後用這個方法,將它們合成一個大的 Reducer
*/
const chatReducer = combineReducers({ chatLog, statusMessage, userName }) export default todoApp;

 

  • Reducer文件:把全部子 Reducer 放在一個文件裏面,而後統一引入
import { combineReducers } from 'redux'
import * as reducers from './reducers' 
const reducer = combineReducers(reducers)

 

 

 

實例分析


1、流程梳理

怎麼改變狀態?

如何觸發界面更新?

這些都是Store的活兒!

 

* 首先,用戶發出 Action --> store

store.dispatch(action); 

 

* 而後,Store 自動調用 Reducer,而且傳入兩個參數:[當前 State][收到的 Action]返回新的 State 。

let nextState = todoApp(previousState, action); // 產生新狀態 

 

* State 一旦有變化,Store 就會調用監聽函數。

/* 設置監聽函數 */ store.subscribe(listener);

/* 若是使用的是 React,出發方式以下 */
function listerner() { let newState = store.getState();   // 得到新狀態 component.setState(newState); // 根據新狀態更新界面 }

 

 

2、代碼分析

Clicked: 3 times    

 

  • index.js之主頁面
const render = () => ReactDOM.render(
/* 自定義Counter控件的使用 */ <Counter value ={store.getState()}   # 得到new state onIncrement={() => store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, rootEl )

  render()
  store. subscribe( render)

 

  • reducer之代碼

在複雜的狀況下,state會是如何?這裏的例子有點過於簡單,沒有表現。

export default (state = 0, action) => {                         # 產生新狀態
  switch (action.type) {
case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } }

 

  • Counter控件的實現
一個重要的特徵是:
click後的執行實體都是外圍的 {() => store.dispatch({ type: 'INCREMENT' })}
也就是,只發送一個信號 action.
class Counter extends Component {
constructor(props) { super(props); this.incrementAsync = this.incrementAsync.bind(this); this.incrementIfOdd = this.incrementIfOdd.bind(this); }
////////////////////////////////////////////////////////
incrementIfOdd() { if (this.props.value % 2 !== 0) { this.props.onIncrement() } } incrementAsync() { setTimeout(this.props.onIncrement, 1000) }
////////////////////////////////////////////////////////
render() { const { value, onIncrement, onDecrement } = this.props return (
<p> Clicked: {value} times {' '} <button onClick={onIncrement}> + </button> {' '} <button onClick={onDecrement}> - </button> {' '} <button onClick={this.incrementIfOdd}> Increment if odd </button> {' '} <button onClick={this.incrementAsync}> Increment async </button> </p> ) } }
相關文章
相關標籤/搜索