基於 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 的管理分離,增強了工程結構的可讀性與可維護性。