React Redux 與胖虎

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

對 React Redux 已經比較熟悉的同窗能夠直接看 《React Redux 與胖虎他媽》react

是什麼

React ReduxRedux 的 React 版,Redux 自己獨立於其餘框架而存在,又能夠結合其餘視圖框架使用,好比此處的 React。android

幹嗎的

按我的理解,Redux 是應用的狀態管理框架,以事件流的形式來發送事件、處理事件、操做狀態和反饋狀態。webpack

這麼說仍是太抽象了,舉個簡單的例子。好比有個 A 組件,它要改變它本身的一個 div 裏面的文字,假設這個文字內容由 this.props.content 決定,那麼它能夠發送一個事件,這個事件通過一系列的處理,最終會改變 this.props.contentgit

龜龜,這也太秀了吧,改個文字都得這麼複雜?沒錯,若是是這種狀況去用 React Redux,那簡直就是多此一舉,沒事找事。這裏有一篇文章 You Might Not Need Redux,能夠考慮本身編寫的應用,是否真的須要 React Redux。github

回到上面的例子,假若 A 組件要去改變同級的一個 B 組件裏面的文字呢?按照咱們以前的作法,咱們會在 A B 組件的上一層套上一個 Parent 組件,將修改 B 組件文字的方法傳給 A 組件,A 組件調用後改變 Parent 組件的 state,進而改變 B 組件的文字。web

那麼咱們的代碼大約是這個樣子:redux

//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 組件來改變裏面的文字...bash

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

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

爲了實現這兩個功能,回調函數滿天飛,特別是第二個功能,你得把回調函數往下傳 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 組件,前者會處理邏輯,後者只負責顯示和交互,內部不處理邏輯,狀態徹底由外部掌控。

「老子有的組件又負責顯示又負責處理邏輯,你怕不是在爲難我胖虎」

是的,不少狀況都是這樣,因此通常作法是在外面封裝一層,將邏輯和 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 了。

等等,胖虎有話要說。

你說 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。

相關文章
相關標籤/搜索