1. 程式人生 > 其它 >基於 React + Redux/Mobx 搞定複雜專案狀態管理 學習心得記錄

基於 React + Redux/Mobx 搞定複雜專案狀態管理 學習心得記錄

基於 React + Redux/Mobx 搞定複雜專案狀態管理

在React的使用過程中,經常會碰到元件間傳值和共享狀態的問題。

這個時候為了方便管理和使用,我們會引進狀態管理器,其中有一種就是redux。

Redux 是 Java 狀態容器,提供可預測化的狀態管理。

可以讓你構建一致化的應用,運行於不同的環境(客戶端、伺服器、原生應用),並且易於測試。Redux 和 React 之間沒有關係。

Redux 支援 React、Angular、Ember、jQuery 甚至純 Java。

Redux 無依賴。

Redux 可以用這三個基本原則來描述:

1、單一資料來源。

整個應用的 state 被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個 store 中。

2、State 是隻讀的。

唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通物件。

3、使用純函式來執行修改。

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

即Redux有三大概念,Action,Reducer和Store。

Action本質上是jaca的普通物件,通過store.dispatch()方法來觸發定義的action,這是唯一改變state的方法。

如:

Reducer 是什麼呢,Reducer 是用來處理資料邏輯的,具體就是根據 Action 處理 State 的函式。

Action並沒有指出如何處理資料,那麼更新state就是reducer要做的事情了。

在編寫reducer的過程中,有兩個問題。

1、怎樣設計 state 的資料結構?

2、有了 state 結構,怎麼編寫 reducer?

在 Redux 應用中,所有的 state 都被儲存在一個單一物件中。

通常,這個 state 樹還需要存放其它一些資料。我們應該儘可能將其分離開。

根據 State 結構編寫 reducer時,redux 提供 combineReducers() 函式來呼叫我們編寫的一系列 reducer。

每個 reducer 根據它們的 key 來篩選出 state 中的一部分資料並處理。

然後這個生成的函式所所有 reducer 的結果合併成一個大的物件。

如:

Store是什麼?

action 用來描述“發生了什麼”, reducers 會根據 action 更新 state。

Store 就是把它們聯絡到一起的物件,是用來維持應用的 state 的。

store有四個方法。

getState: 獲取應用當前 state。

subscribe:新增監聽器。

dispatch:分發 action。更新 state。

replaceReducer:替換 store 當前用來處理 state 的 reducer。

常用的是dispatch,這是修改State的唯一途徑,使用起來也非常簡單。

簡單來說,Redux 應用只有一個單一的 store。

當需要拆分處理資料的邏輯時,使用 reducer 組合,而不是建立多個 store。

combineReducers是用來合併reducer的,因為creatstore只能有一個reducer,但是一個reducer又只對應一個狀態。

單個reducer是一個純函式,定義了初始狀態和action,必須給初始狀態賦值,必須有返回值。

Redux 是React生態中重要的組成部分。很多人都說,簡單的應用可以不用此工具。但是我個人認為,中小型應用使用的話,可以使檔案結構更加規範,程式碼可讀性更強。因為React提出將展示元件與容器元件分離的思想,所以降低了React 與Redux之間的耦合度。

 

網上廣為流傳的Redux流向圖,可以幫助我們更好地理解並使用。   Redux Flux.png

我個人粗淺的理解是:
Store的角色是整個應用的資料儲存中心,集中大部分頁面需要的狀態資料;
ActionCreators ,view 層與data層的介質;
Reduce ,接收action並更新Store。
所以流程是 使用者通過介面元件 觸發ActionCreator,攜帶Store中的舊State與Action 流向Reducer,Reducer返回新的state,並更新介面。

所以也可以按照這個流程思想,來構建程式碼的結構了。


  實現一個很簡單的結構.png

上圖實現的就是輸入輸出的東西。輸入框內輸入一些內容,confirm後,label顯示相應內容。

最開始
先安裝幾個庫

npm install --save prop-types
npm install --save react-redux
npm install --save redux

1、首先構造介面

component/AddName.js

-這是一個純React程式碼 ,結構清晰。

//component/AddName.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'

class AddName extends Component {
  //宣告屬性
  static propTypes = {
    lastname:PropTypes.string.isRequired,
    addNameCreater:PropTypes.func.isRequired,
    lastage:PropTypes.number.isRequired,
    addAgeCreater:PropTypes.func.isRequired,
    addNameAsync:PropTypes.func.isRequired
  }
//點選事件
  handlerFunc = () =>{
    const inputName = this.refs.inputValueTest.value;
    this.props.addNameCreater(inputName);
  }
  handlerAgeFunc = () =>{
    const inputage = this.refs.inputValueAge.value;
    this.props.addAgeCreater(inputage);
  }
  handlerAsyncFunc = () =>{
    const inputName = this.refs.inputValueTest.value;
    this.props.addNameAsync(inputName);
  }
//渲染介面
  render() {
    const {lastname,lastage} = this.props;   
    return (
      <div>
        <header className="App-header">
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <label> {lastname} </label><br/>
        <input ref="inputValueTest" /><br/>
        <button onClick={this.handlerFunc}>confirm</button><br/>

        <label> {lastage} </label><br/>
        <input ref="inputValueAge" />
        <button onClick={this.handlerAgeFunc}>confirm</button><br/>

        <button onClick={this.handlerAsyncFunc}>Async Confirm</button><br/>
      </div>
    );
  }
}

export default AddName;


2、然後,根據流程圖,我們需要定義一些操作了,也就是ActionCreator.它會傳達使用者的操作資訊以及一些資料

先定義一些常量供我們使用,這裡就兩種操作,一是新增名字,二是新增年齡,實際都一樣。為了後面實現reducer的合併強行寫了倆。這寫常量一般都定義在actionTpye檔案中

Redux/actionType.js

export const ADDNAME = 'ADDNAME'
export const ADDAGE = 'ADDAGE'

接著就是寫ActionCreator ,定義了一些操作型別,告訴store自己是幹什麼的,需要什麼樣的資料。

Redux/actions.js

import { ADDNAME,ADDAGE } from "./action-type";

//包含所有的action creator
export const addNameCreater = (name) =>({type:ADDNAME,data:name})
export const addAgeCreater = (age) => ({type:ADDAGE,data:age})
export const addNameAsync = (name) =>{
    return dispatch =>{
        setTimeout(()=>{
            dispatch(addNameCreater(name))
        },2000);
    }
}

3、Reducer 會接收到action的資訊。將會進行狀態(資料)的處理,相當於react中的setState()的功能。如果有多個reducer ,可以使用combineReducers方法將其合併,並暴露出去。

Redux/reducer.js

//包含n個reducer函式的模組
import {ADDNAME, ADDAGE} from './action-type'
import {combineReducers} from 'redux'
function addName(state='initRedux',action){ //形參預設值
    switch(action.type){
        case ADDNAME:
            return action.data
        default:
            return state
    }
}
function addAge(state=0,action){
    switch(action.type){
        case ADDAGE:
            return action.data
        default:
            return state
    }
}

export const finalReducer = combineReducers({
    addName,addAge
})

其中state='initRedux' 、state=0 相當於我們在React元件內部初始化state.

4、一切操作還是基於Store 的。類似於中央集權。所以還要把Store建立出來

Redux/store.js

import {createStore,applyMiddleware} from 'redux'
import {finalReducer } from './reducers'
import thunk from 'redux-thunk'
//生成store物件
const store = createStore(finalReducer,applyMiddleware(thunk));//內部會第一次呼叫reducer函式,得到初始state 

export default store

因為reducer會更新Store中的狀態(資料),所以需要引入reducer ,並建立store.

到此,流程圖到這裡就走完了。不過2、3、4都是redux中負責接管React 狀態的功能。1是React負責展示的元件。兩者並沒啥關係。既然有了展示元件,接下來就要有容器元件了。也就是能夠將React與redux相關聯的一個元件。

5、構建容器元件

containers/App.js

import React from 'react'
import {connect} from 'react-redux'
import {addNameCreater,addAgeCreater,addNameAsync} from '../redux/actions'
import AddName from '../component/AddName'
export default connect(
    state => ({
        lastname:state.addName,
        lastage:state.addAge
    }),
    {addNameCreater,addAgeCreater,addNameAsync}
)(AddName)

這裡用到了react- redux中的connect 。可以將React與redux關聯起來。AddName就是第一步寫的元件名。state中關聯了React中的屬性。這裡面涉及到兩個API,到第二章詳細描述。

6、新增store

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'

import App from './containers/App'
import store from './redux/store'

ReactDOM.render((
//使用Provider 元件將APP主元件包裹住,這樣內部元件都有Store種提供的屬性。
    <Provider store={store}>
        <App/>
    </Provider>
), document.getElementById('root'));

這樣就OK了。

  檔案結構.png

上圖是主要的檔案結構,
-redux 集中管理狀態(資料)
-component 專注於React 展示元件部分
-containers 集中處理React與redux互動的部分

此外,redux還可以處理一些非同步請求。這樣的話。可以做到data 和view 的管理分離,增強了工程結構的可讀性與可維護性。