React Redux 與胖虎

Mindjet發表於2018-01-12

這是一篇詳盡的 React Redux 掃盲文。

對 React Redux 已經比較熟悉的同學可以直接看 《React Redux 與胖虎他媽》

是什麼

React ReduxRedux 的 React 版,Redux 本身獨立於其他框架而存在,又可以結合其他檢視框架使用,比如此處的 React。

幹嘛的

按個人理解,Redux 是應用的狀態管理框架,以事件流的形式來傳送事件、處理事件、操作狀態和反饋狀態。

這麼說還是太抽象了,舉個簡單的例子。比如有個 A 元件,它要改變它自己的一個 div 裡面的文字,假設這個文字內容由 this.props.content 決定,那麼它可以傳送一個事件,這個事件經過一系列的處理,最終會改變 this.props.content

龜龜,這也太秀了吧,改個文字都得這麼複雜?沒錯,如果是這種情況去用 React Redux,那簡直就是畫蛇添足,沒事找事。這裡有一篇文章 You Might Not Need Redux,可以考慮自己編寫的應用,是否真的需要 React Redux。

回到上面的例子,倘若 A 元件要去改變同級的一個 B 元件裡面的文字呢?按照我們之前的做法,我們會在 A B 元件的上一層套上一個 Parent 元件,將修改 B 元件文字的方法傳給 A 元件,A 元件呼叫後改變 Parent 元件的 state,進而改變 B 元件的文字。

那麼我們的程式碼大約是這個樣子:

//Parent 元件
render() {
    return (
      <div>
        <A onClick={(newContent) => this.setState({content: newContent})}/>
        <B content={this.state.content}/>
      </div>
    )
}

//A 元件
render() {
    return (
      <div onClick={() => this.props.onClick('This is new content for B')}>Change B's content</div>
    )
}

//B 元件
render() {
    return (
      <div>{this.props.content}</div>
    )
}
複製程式碼

有點費勁呢...可是我們總算實現了功能~

什麼?多加了個同級的 C 元件,也要 A 元件來改變裡面的文字...

什麼?有個深度為 100 的元件,要它來改變 B 元件的文字...

我胖虎出去抽根菸,回來要看到這兩個功能實現,不然錘死在座各位。

React Redux 與胖虎

為了實現這兩個功能,回撥函式滿天飛,特別是第二個功能,你得把回撥函式往下傳 100 層...

差不多這個時候,你就該考慮 React Redux 了。

像第二個功能,只需要從深層的元件傳送一個事件出來,這個事件最終就會改變 B 元件的文字。

嗬,聽起來不錯。

長啥樣

講了這麼多,是時候一睹 React Redux 的真容。

RR架構

其中 Action、Dispatch 和 Reducer 都是 React Redux 的東西,View 則是代表我們的檢視層 React。

先理清幾個概念:Store,Action 和 Reducer(Dispatch 是 Store 的一個方法)

  • Store 是整個 React Redux 應用總的狀態容器,是一個物件
  • Action 也是一個物件,表明事件,需要有 type 欄位
  • Reducer 是一個函式,會根據不同 Action 來決定返回不同的資料

從上面的圖看到 View 層可以通過兩種方式來更新:

  1. View 層發出 Action,Dispatch 之後到達 Reducer,Reducer 處理後返回新的東西去更新 View
  2. 其它層發出 Action 以同樣的方式來更新 View

上面無論哪一種方式,都是遵循單向資料流的規則,即:傳送 Action -> Reducer 根據 Action 返回資料 -> Store 被更新 -> View 被更新

從 demo 講起

空談誤國,實幹興邦。還是邊寫邊介紹為好。

這裡實現一個小 demo,就一個按鈕和一個數字,點選按鈕數字加 1,即計數器。

先說一點,React Redux 將組建區分為 容器元件 和 UI 元件,前者會處理邏輯,後者只負責顯示和互動,內部不處理邏輯,狀態完全由外部掌控。

“老子有的元件又負責顯示又負責處理邏輯,你怕不是在為難我胖虎”

React Redux 與胖虎

是的,很多情況都是這樣,所以一般做法是在外面封裝一層,將邏輯和 UI 剝離,外層寫邏輯,內層純粹寫 UI。

所以對於計數器這個元件,我們需要多封裝一層,使用的是 react-reduxconnect 函式。這個函式顧名思義就是連線用的,連線 UI 元件,生成新的含有邏輯的元件。

connect 是一個高階函式,可以傳入兩個函式:

import { connect } from 'react-redux';
import Counter from './Counter';

function mapStateToProps(state) {
    return {
        count: state.count
    }
}

function mapDispatchToProps(dispatch) {
    return {
        onAdd: () => dispatch({type: 'ADD_COUNT'})
    }
}

const newComponent = connect(mapStateToProps, mapDispatchToProps)(Counter);
複製程式碼

connect 函式可以傳入兩個函式:

mapStateToProps

此函式接收 state 引數(後面會講到,這個 state 是從 reducer 那裡過來的),定義從 state 轉換成 UI 元件 props 的規則。該函式返回 props 物件,比如我們取 state 的 count 欄位生成新的 props 返回。

此函式還可以接收 ownProps 引數,代表直接在 UI 元件上宣告的 props:

function mapStateToProps(state, ownProps) {
    console.log(ownProps);	//{content: 'hello', color='white'}
  	return {};
}
 
//比如我們是這麼使用 Counter 元件的
<Counter content='hello', color='white' />
複製程式碼

mapDispatchToProps

此函式接收 dispatch 引數(實際上是 Store 的 dispatch 方法),定義一系列傳送事件的方法,並返回 props 物件。比如上面我們定義傳送 ADD_COUNT 事件的方法 onAdd,其中 {type: 'ADD_COUNT'} 就是一個簡單的 Action 了。

等等,胖虎有話要說。

React Redux 與胖虎

你說 mapStateToProps 返回 UI 元件的 props,mapDispatchToProps 也返回 UI 元件的 props,同時 UI 元件自己還定義了 props,那他孃的最後 UI 元件的 props 是什麼啊?

答案是,這 3 個 props 合在一塊。也就是說,照上面的例子,在 Counter 元件內部可以呼叫到這些:

this.props.content;
this.props.color;
this.props.count;
this.props.onAdd();

export default class Counter extends React.Component {
    
  render() {
      return (
        <div>
          <p>{this.props.count}</p>
          <button onClick={this.props.onAdd}>Add</button>
        </div>
      )
  }
}
複製程式碼

恩,用 connect 就把外層的容器元件構造好了,我們把剛剛那個含有 connect 函式的檔案命名為 index.jsx

Reducer

我們剛剛寫的那個 Counter,其實還不能用,因為我們傳送事件出去之後,並沒有對事件進行處理。

Reducer 就是用來處理 Action 的,實際上是一個函式,比如我們處理上面提到的 ADD_COUNT 事件:

//counter-reducer.js
export default function reducer(state={count: 0}, action) {
    switch(action.type) {
      case 'ADD_COUNT':
        return {
            count: state.count + 1
        };
      default:
        return state;
    }
}
複製程式碼

像這裡,如果我們判斷事件 type 是 ADD_COUNT 時,將 state 裡面的 count 欄位屬性值 +1 並且返回新的 state 物件,這個物件會傳到 mapStateToProps 中。

Reducer 函式裡面有 2 點值得注意:

  • 第一個引數 state 表明當前的 state,比如說當數字為 1 時點選 Add 按鈕,此時在 Reducer 中該 state 為 {count: 1},隨後返回 {count: 2},再下次進來就是 {count: 2} 了。state 可以傳入初始化值,比如我們們這裡初始值為 0
  • 任何事件所有的 Reducer 都可以接收到,若 Reducer 沒有匹配的 case,代表不響應這個事件,要返回當前的 state,即 default 分支返回 state。

Store

有人又好奇了,那這個 state 到底是存在於哪裡的?目前我們討論到的 Reducer 和 mapStateToProps 函式,它們都是接受 state,本身並不持有 state。

。。。

實際上,state 存在於 Store 中。後面還會講到,多個 Reducer 的情況下,一個 Reducer 對應 Store 中的一個 state。

那麼,Store 又是怎麼作用到我們的 DOM 樹上的?

React Redux 是通過 Provider 元件將 Store 這一個全域性狀態容器繫結到 DOM 樹上,Provider 一般作為 React Redux 應用最頂層的元件(Provider 並不真實存在於 DOM 樹上):

import { createStore, Provider } from 'react-redux';
import React from 'react';
import reducer from './counter-reducer.js';
import Counter from './components/Counter';

const store = createStore(reducer);

ReactDOM.render(
  <Provider store={store}>
    <Counter/>
  </Provider>, document.getElementById('root'));
複製程式碼

可以看到我們從 react-redux 這個庫引入了 createStore Provider,並使用 createStore 傳入上面的 Reducer 建立出 Store,再將 Store 傳到 Provider 元件,從而作用於整個 DOM 結構。

此時的專案結構為(當然還有其他一些 webpack 配置檔案什麼的,就不列舉了):

Project
    - components
        - Counter
        - index.jsx
        - Counter.jsx
    - index.jsx
複製程式碼

到此為止,這個計數器已經能正常地運作了。

我們也稍微理解了 React Redux 的工作原理和方式了,再總結一番:

  • 事件流:dispatch(Action) -> Reducer -> new state (Store) -> new props -> update component
  • 分為容器元件和 UI 元件,傳統元件可能需要用 connect 作處理
  • Reducer 處理 Action 返回新的 state,需考慮 Action 不匹配的情況
  • 使用 createStore 函式建立 Store,Reducer 作為引數
  • 使用 Provider 作為頂層元件將全域性 Store 引入

胖虎射線

———

技術上的問題,歡迎討論。

個人部落格:mindjet.github.io

最近在 Github 上維護的專案:

  • LiteWeather [一款用 Kotlin 編寫,基於 MD 風格的輕量天氣 App],對使用 Kotlin 進行實際開發感興趣的同學可以看看,專案中會使用到 Kotlin 的委託機制、擴充套件機制和各種新奇的玩意。
  • Reask [用 React&Flask 開發的全棧專案,前端採用 react-redux]
  • LiteReader [一款基於 MD 的極輕閱讀 App,提供知乎日報、豆瓣電影等資源],專案主要使用了 MVVM 設計模式,介面遵循 Material Design 規範,提供輕量的閱讀體驗。
  • LiveMVVM [Kotlin 編寫的 Android MVVM 框架,基於 android-architecture],輕量 MVVM+Databinding 開發框架。

歡迎 star/fork/follow 提 issue 和 PR。

相關文章