React + Redux 入坑指南

Redux

原理

1. 單一數據源html

all states ==>Storereact

  • 隨着組件的複雜度上升(包括交互邏輯和業務邏輯),數據來源逐漸混亂,致使組件內部數據調用十分複雜,會產生數據冗餘或者混用等狀況。
  • Store 的基本思想是將全部的數據集中管理,數據經過 Store 分類處理更新,再也不在組件內放養式生長。

2. 單向數據流git

dispatch(actionCreator) => Reducer => (state, action) => stategithub

  • 單向數據流保證了數據的變化是有跡可循且受控制的。
  • 經過綁定 Store 能夠肯定惟一數據來源。
  • actionCreator 經過 dispatch 觸發,使組件內事件調用邏輯清晰,具體的事件處理邏輯不用放在組件寫,保持 view 層的純淨。
  • Reducer 經過判斷不一樣的 actionType 處理不一樣數據更新,保證數據有秩序更新。

React + Redux

Action

  • actionType 定義操做類型
  • actionCreator 定義操做具體執行函數

1. Action 基礎寫法

  • actionType 提供給 Reducer 判斷動做類型
  • actionCreator 爲可調用的執行函數,必須返回 actionType 類型
// actionType
export const ACTION_TYPE = 'ACTION_TYPE';

// actionCreator
let actionCreator = (config) => {
    return {
        type: ACTION_TYPE, // 必須定義 type
        config // 傳遞參數 => reducer
    }
}

2. Action 異步解決方法

2.1 redux-thunk 使用方法

  • redux-thunk 配置
    redux-thunk 爲獨立工具,須要另外安裝,經過 redux 提供的中間件 applyMiddleware ,綁定到 store 中。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../reducers';

let store = createStore(
  reducers,
  applyMiddleware(thunk)
);
  • Action 使用 redux-thunk
    獲取數據方法在異步獲取數據後須要再次調用接收方法接收數據。
// 接收方法
let receiveSomething = (res) => {
    return {
        type: RECEIVE_SOME,
        res
    }
}

// 獲取數據方法
export let fetchSomething = (args) => {
    return dispatch => {
        return fetch(args).then((res) => {
            return dispatch(receiveSomething(res))
        })
    }
}

Reducer

  • 引入 Action 中定義好的 actionType
  • 傳入 初始數據 和 actionType 後,返回更新數據(initialState, action) => newState

Reducer 基礎寫法

1.依據不一樣執行 ActionType 直接更新狀態

import { ACTION_A, ACTION_B } from '../actions';

let initialState = { ... }

function example(state = initialState, action) {
    switch(action.type) {
        case ACTION_A:
          return Object.assign({}, state, action.config)
        case ACTION_B:
          return Object.assign({}, state, action.config)
    }
}

2.對 Action 傳遞的數據多加一層處理

let doSomething = (config) => {
    let { a, b } = config;
    // do something with a, b
    return { a, b }
}

function example(state = initialState, action) {
    switch(action.type) {
        case ACTION_TYPE:
          return Object.assign({},
          state,
          doSomething(action.config))
    }
}

3.合併多個 Reducer

經過 redux 提供的 combineReducers 將不一樣處理邏輯的 reducer 合併起來。redux

import { combineReducers } from 'redux';

export default combineReducers({
  reducerA,
  reducerB
});

// or

export let reducer = (state = initialState, action) {
    a: processA(state.a, action),
    b: processB(state.b, action)
}

Store

1. 將 Store 綁定 React

使用 react-redux 提供的 Provider 能夠將 Store 注入到 react 中。api

Store 將合併後的 reducers 經過 createStore 建立,此外下面示例代碼還使用中間件加入了一層 react-thunk 處理。app

import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux'
import thunk from 'redux-thunk';
import reducers from './reducers';

let store = createStore(
  reducers,
  applyMiddleware(thunk)
);

ReactDOM.render((
  <Provider store={store}>
   // ...
  </Provider>
), document.querySelector('#app'));

2. 將 state 綁定到 Component

使用 react-redux 提供的 connect 方法 將組件和所需數據綁定。dom

須要注意的是,Store 建立時接收的是合併後的 reducers, 所以不一樣 reducer 上的處理數據綁定在了不一樣 reducer 對象上,而不是所有掛載在 Store 上。異步

mapStateToProps 將組件內部所需數據經過 props 傳入組件內部。更多綁定機制,具體可參考connectide

import React, { Component } from 'react';
import { connect } from 'react-redux';

class ComponentA extends Component {
   //...
}

let mapStateToProps = (state) => {
  // attention !!!
  let { reducerA, reducerB } = state;
  return {
    propA: reducerA.propA,
    propB: reducerB.propB
  }
};

export default connect(mapStateToProps)(ComponentA);

Component

1. 概念

React bindings for Redux embrace the idea of separating presentational and container components.

Redux 的 React 綁定庫包含了 容器組件和展現組件相分離 的開發思想。

  • Presentational Components 展現型組件
  • Container Components 容器型組件

展現型組件和容器型組件的區別在官方文檔中已經給出很詳細的解釋了,可是中文文檔的翻譯有誤,因此直接看英文比較更容易懂。

Presentational Components Container Components
Purpose How things look (markup, styles) How things work (data fetching, state updates)
Aware of Redux No Yes
To read data Read data from props Subscribe to Redux state
To change data Invoke callbacks from props Dispatch Redux actions
Are written By hand Usually generated by React Redux

組件類型區分的模糊點在於怎麼界定組件的內部功能規劃。若是斷定一個組件爲展現型組件,那麼它所需數據和處理方法都應該從父級傳入,保持組件內部「純淨」。

在實際開發中,一個組件的邏輯跟業務緊密相關。若是須要將數據和方法從外部傳入,那麼父級組件所作的事情會不少,多重的子組件也會把父級邏輯弄亂,這就不是 redux 的初衷了。

中文文檔翻譯的意思是:容器組件應該爲路由層面的組件,但這樣既不符合實際開發須要,也違背了 redux 思想。真正界定兩種組件的因素是:

  • 展現型組件: 相似純模板引擎,外加一層樣式渲染,只負責渲染從props傳進來的數據或者監聽事件和父組件作小聯動。它是「純淨」的,不須要使用到 Redux 的一套規則。
  • 容器型組件: 須要異步獲取數據,更新組件狀態等等。須要跟業務邏輯打交道的組件均可以認爲是容器組件。這些邏輯的複雜性須要將數據整合到 Store 裏統一管理。

2. Component 基礎寫法

  • 組件渲染完成後調用Action

當組件 connect 後,dispatch 方法已經注入到 props 中,因此觸發 Action 能夠從 props 獲取 dispatch 方法。

import React, { Component } from 'react';
// actionCreator
import { actionA, actionB } from 'actions/actionA'

class ComponentA extends Component {
    constructor(props) {
        super(props);
    }
    componentDidMount() {
        let { dispatch } = this.props;
        dispatch(actionA())
    }
}
export default connect()(ComponentA);
  • 組件模板內調用Action

組件內部所需的渲染數據都已經綁定在了 props 上,直接獲取便可。

須要注意的是,在事件監聽中觸發 Action,須要用一個匿名函數封裝,不然 React 在渲染時就會執行事件綁定事件,而不是當事件發生再執行。

render() {
  let { dispatch, propA, propB } = this.props;

    return (
      <section>
        // Attention !!!
        <input type="text" onClick={(ev) => dispatch(actionB(ev))} />
        <p className={propA}>{propB}</p>
      </section>
    )
}
  • 容器組件傳遞方法

容器型組件須要鏈接 Redux,使用 dispatch 觸發 actionCreator。

展現型組件須要用到的方法調用在容器型組件內定義好,經過 props 傳入到展現型組件中。

// get actionCreator
import { actionA } from './actions/actionA';

class Parent extends Component {
  handleCallback(data) {
    // use dispatch
    let { dispatch } = this.props;
    dispatch(actionA(data));
  }
  render() {
    return (
      <Child onSomethingChange={this.handleCallback} />
    )
  }
}
// connet Redux
export default connect()(Parent);
  • 展現組件接收props

展現型組件不須要用到 Redux 的一切,它的 props 僅僅存在於父級傳入的數據和方法。

// don't need action/dispatch/connect
class Child extends Component {
  handleSomething(data) {
    // handle anything with props
    this.props.onSomethingChange(data);
  }
  render() {
    return (
     // just markup & style
      <input onChange={handleSomething} />
    )
  }
}

Conclusion

圖示箭頭表明各概念之間的相互關係,不表明數據流。( 能理解下面這張圖,這篇文章就沒白看了 -。- )

參考文檔

END.

相關文章
相關標籤/搜索