深入Redux架構

一、什么情況需要redux?

? ? 1、用戶的使用方式復雜

? ? 2、不同身份的用戶有不同的使用方式(比如普通用戶和管理員)

? ? 3、多個用戶之間可以協作

? ? 4、與服務器大量交互,或者使用了WebSocket

? ? 5、View要從多個來源獲取數據

簡單說,如果你的UI層非常簡單,沒有很多互動,Redux就是不必要的,用了反而增加復雜性。多交互、多數據源的場景就比較適合使用Redux。

二、設計思想

? ? 1、Web應用是一個狀態機,視圖與狀態是一一對應的。

? ? 2、所有的狀態,保存在一個對象里。

三、Redux的工作流程

Redux工作流程

首先,用戶發出Action

store.dispatch(action);

然后,Store自動調用Reducer,并且傳入兩個參數:當前State和收到的Action。Reducer會返回新的State。

let nextState = todoApp (previousState , action);

State一旦變化,Store就會調用監聽函數。

// 設置監聽函數

store.subscribe(listener);

lisrener 可以通過store.getState()得到當前狀態。如果使用的是React,這時可以觸發重新渲染View。

function listener () {

? ? let nextState=store.getState();

? ? component.setState( newState );

}

如果現在沒理解以上流程,不要急,看完以下API就差不多能動的Redux的核心機制啦。

四、Redux ?API

store

Store 就是保存數據的地方,你可以把它看成一個容器。整個應用只能有一個Store。

Redux提供createStore這個函數,用來生成Store。

下面代碼中,createStore函數接受另一個函數作為參數,返回新生成的Store對象。

import { createStore } from 'redux';

const store = createStore ( fn );

State

Store 對象包含所有的數據,如果想得到某個時點的數據,就要對Store生成快照。這種時點的數據集合,就叫做State。當前此刻的State,可以通過store.getState()拿到。

import { crateStore } from 'redux';

const store = createStore ( fn );

const state = ?store.getState();

Redux 規定,一個State對應一個View。只要State相同,View就相同,你知道State,就知道View是什么樣,反之亦然。

Action

State的變化,會導致VIew 的變化。但是,用戶接觸不到State,只能接觸到View。所以,State的變化必須是VIew導致的。

Action就是View發出的通知,表示state應該要發生變化啦。

Action是一個對象,其中的type屬性是必須的,表示Action的名稱。其他屬性可以自由設置。

const action = {

? ? type : 'ADD_TODO',

? ? payload ; 'learn Redux'

};

上述代碼中,Action的名稱是ADD_TODO,它攜帶的信息是字符串Learn Redux。

可以這樣理解,Action描述當前發生的事情。改變State的唯一辦法,就是使用Action,它會運送數據到Store。

Action Creator

view 要發送多少種信息,就會有多少種Action。如果都手寫,會很麻煩。可以定義一個函數來生成Action,這個函數就叫做ActionCreator。

const ADD_TODO = '添加TODO';

function addTodo (text) {

? ? return {

? ? ? ? type: ADD_TODO,

? ? ? ? text

? ? }

}

const action = addTodo(' Learn Redux');

Store.dispatch()

store.dispatch()是View發出Action的唯一方法。

import { createStore } from 'redux';

const ?store=createStore ( fn );

store.dispatch({

? ? type : 'ADD_TODO';

? ? payload : 'Learn Redux'

});

上面的代碼中,store.diapatch接受了一個Action對象作為參數,將它發出去,結合ActionCreator,這段代碼可以改寫如下。

import { createStore } from 'redux';

const? store=createStore ( fn );

store.dispatch(add Todo('Learn Rudux'));

const ADD_TODO = '添加TODO';

function addTodo (text) {

? ? return {

? ? ? ? type: ADD_TODO,

? ? ? ? text

? }

}

Reducer

Store 收到Action以后,必須給出一個新的State,這樣View才會發生變化。這種State的計算過程就叫做Reducer。Ruducer是一個函數,他接受Action和當前State作為參數,返回一個新的State,下面是一個實際的例子

const defaultState = 0;

const reducer = (state=defaultState,action)=>{

switch (action.type){

? ? case 'ADD';

? ? ? return state +action.payload;

? ? default:

? ? ? return state;

? ? }

};

const state = reducer (1, {

type : 'ADD',

payload: 2

});

上面代碼中,reducer函數收到名為ADD的Action以后,就返回一個新的State,作為加法的計算結果。其他運算的邏輯(比如剪發),也可以根據Action的不同來實現。

實際應用中,Reducer函數不用像上面那樣手動調用,store.dispatch方法會觸發Reudcer自動執行。為此,Store需要知道ReduCer函數,做法就是在生成Store的時候,將Reducer傳入createStore方法。

import {createStore } from 'redux';

const store = createStore(reducer);

store.dispatch(addTodo('Learn Redux'));

const ADD_TODO='添加Todo';

function addTodo(text){

? return {

? ? type:ADD_TODO,

? ? text

? }

}

上面代碼中,createStore接受Reducer作為參數,生成一個新的Store,以后每當store.dispatch發過來一個新的Action,就會自動調用Reducer,得到新的State。

Store.subscribe()

Store允許使用store.subscribe方法設置監聽函數,一旦State發生變化,就自動執行這個函數。

import { createStore } from 'redux';

const store = createStore (reducer);

store.dispatch(addTodo('Learn Redux'));

const ADD_TODO='添加Todo';

function addTodo(text){

? return {

? ? type:ADD_TODO,

? ? text

}

}

store.subscribe(listener);

顯然,只要把View的更新函數(對于React項目,就是組件的render方法和setState方法)放入listen,就會實現view的自動渲染。

store.subscribe方法返回一個函數,調用這個函數就可以解除監聽。

let unsubscribe = store.subscribe( () =>

? ? console.log(store.getState())

};

unsubscribe();

五、中間件與異步操作

一個關鍵的問題沒有解決:異步操作怎么辦?Action發出以后,Reducer立即算出State,這叫同步;Action發出以后,過一段時間再執行Reducer,這叫異步。

怎么才能Reducer在異步操作結束后自動執行呢?這就要用到新的工具:中間件(middleware)

middleware

為了理解中間件,然我們站在框架作者的角度思考問題:如果要添加功能,你會在那個環節添加?

(1)Reducer:純函數,只承擔計算State的功能,不適合承擔其他功能,也承擔不了,因為理 ? ? ? ? ?論上,純函數不能進行讀寫操作。

(2)View :與State一一對應,可以看作是State的視覺層,也不合適承擔其他功能。

(3)Action:存放數據的對象,即消息的載體,只能被別人操作,自己不能進行任何操作。

想來想去,只有發送Action的這個步驟,即store.dispatch()方法,可以添加功能

中間件的用法

這里只介紹如何使用中間件

import { applyMIddleware , crateStore } from 'redux';

import ?createLogger from 'redux-logger';

const logger= createLogger();


const store = createStore (

? ? reducer,

? ? applyMiddleware(logger)

);

上面代碼中,redux-logger提供了一個生成器createLogger,可以生出日志中間件logger。然后將它放在applyMiddleware方法之中,傳入createState方法,就完成了store.dispatch()的功能增強。

這里有兩點需要注意:

(1)createStore方法可以接受整個應用的初始狀態作為參數,那樣的話,applyMiddleware就 ? ? ? ? ? ?是第三個參數了。

import { applyMIddleware , crateStore } from 'redux';

import? createLogger from 'redux-logger';

const logger= createLogger();


const store = createStore(

? ? reducer,

? ? initial_state,

? ? applyMiddleware(logger)

);

(2)中間件的次序有講究

const store = createStore(

? ? reducer,

? ? applyMiddleware(thunk,promise,logger)

);

上面代碼中,applyMiddleware方法的三個參數,就是三個中間件。有的中間件有次序要求,使用前要查一下文檔。比如,logger就一定要放到最后,否則輸出結果會不正確。

六、異步操作的基本思路

同步操作只要發出一種Action即可,異步操作的差別是他要發出三種Action。

? ? 1)操作發起時的Action

? ? 2)操作成功時的Action

? ? 3)操作失敗時的Action

以向服務器取出數據為例,三種Action可以有兩種不同的寫法

// 寫法一:名稱相同,參數不同

{ ?type : 'FETCH_POSTS' }

{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }

{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

// 寫法二:名稱不同

{ type: 'FETCH_POSTS_REQUEST' }

{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }

{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

除了Action種類不同,同步操作的State也要進行改造,反映不同的操作狀態,下面是State的一個例子

let state = {

? ? isFetching : true,

? ? didInvalidate : true,

? ? lastUpdated: 'xxxx'

};

上面代碼中,State的屬性isFetching表示是否在抓取數據,didInvalidate表示數據是否過時,lastUpdated表示上一次更新的時間

現在,整個異步操作的思路清楚啦。

1)操作開始時,送出一個Action,觸發State更新為“正在操作”狀態,View重新渲染

2)操作結束后,再送出一個Action,觸發State更新為“操作結束”狀態,View再一次重新渲染

redux-thunk中間件

異步操作至少要送出兩個Action:用戶觸發第一個Action,這個跟同步一樣,沒有問題;如何才能在操作結束時,系統自動送出第二個Action呢?

奧妙就在Action Creator之中。

class AsyncApp extends Component {

? componentDidMount() {

? ? ? ?const { dispatch,selectedPost } =this.props

? ? ? ?dispatch (fetchPosts(selectedPost))

}

}

上面代碼是一個異步組件的例子,加載成功后(componentDidMouth方法),他送出了(dispatch方法)一個Action,向服務器要求數據fetchPosts(selectedSUbredddit).這里的fetchPosts就是Action Creator

const fetchPosts = postTitle => ( dispatch , getState) => {

? dispatch ( requestPosts( postTitle));

? return fetch ( '/some/API/${postTitle}.json')

? ? ? .then (response = > response.json() )

? ? ? .then( json=> dispatch(receivePosts(postTitle,json)));

};

};

// 使用方法一

store.dispatch(fetchPosts(reactjs'));

// 使用方法二

store.dispatch ( fetchPosts (reactjs')).then(()=>

? ? ? console.log(store.getState())

);

上面代碼中,fetchPosts是一個Action Creator(動作生成器),返回一個函數,這個函數執行后,先發出一個Action(requestPosts(postTitle)),然后進行操作。拿到結果后,先將結果轉換為JSON格式,然后再發出一個Action(recervePosts(postTitle,json)).

上面代碼中,有幾個地方需要注意。

(1)fetchPosts返回一個函數,而普通的Action Creator默認返回一個對象

(2)返回的函數的參數是disPatchgetState這兩個Redux方法,普通的ActionCreator的參數 ? ? ? ? ?是Action的內容。

(3)在返回的函數之中,先發出一個Action(requestPosts(postTitle)),表示操作開始

(4)異步操作結束之后,再發出一個Action(ReceivePosts(postTitle,json)),表示操作結束

這樣的處理,就解決了自動發送第二個Action的問題,但是又帶來一個新的問題,Action是由store.dispatch方法發送的。這時,就要使用中間件redux-thunk

import { createStore, applyMiddware } from 'redux';

import thunk form 'redux-thunk';

import reducer from './reducers';

const store = creareStore(

reducer,

applyMiddleware(thunk)

};

上面代碼使用redux-thunk中間件,改造store.dispatch,使的后者可以接受函數作為參數。因此,異步操作的第一種解決方案就是,寫一個返回函數的Action Creator,然后使用redux-thunk中間件改造store.dispatch.

*七、React-Redux的用法

為了方便使用,Redux的作者封裝了一個React專用的庫React-Redux本文主要介紹它

這個庫可以選用的。實際項目中,你應該權衡一下,是直接使用Redux,還是使用React-Redux。后者雖然提供了便利,但是需要掌握額外的Api,并且要遵循它的組件拆分規范。

React & Redux

React-Redux將所有的組件分為兩大類:UI組件(presentational component)和容器組件(container component)。

UI 組件

UI組件有一下幾個特征。

1)只負責UI的呈現,不帶有任何業務邏輯。

2)沒有狀態(即不使用this.state這個變量)

3)所有數據都由參數(this.props)提供

4)不使用任何Redux的API

下面就是一個UI組件的例子

const Title =

value = > <h1> { value} </h1>;

因為不含有狀態,UI組件又稱為“純組件”,即它純函數一樣,純粹由參數決定他的值

容器組件

容器組件的特征恰恰相反。

1)負責管理數據和業務邏輯,不負責Ui的呈現

2)帶有內部狀態

3)使用Redux的ApI

總之,只要記住一句話就可以了:UI組件負責Ui的呈現,容器組件負責管理數據和邏輯

你可能會問,如果一個組件既有UI又有業務邏輯,怎么辦? 答案是,將它拆分成下面的結構:外面是一個容器組件,里面包含了一個UI組件。前者負責與外部的通信,將數據傳給后者,由后者渲染出視圖。

React-Redux提供connect方法,用于從UI組件生成容器組件。connect的意思,就是將這兩種組件連起來。

connect方法的完整API如下。

import ?{ connect ?} ?from 'react-redux';

const VIsibleTodoList = connect(

mapStateToProps,

mapDisPatchToProps

) (TodoList)

上面代碼中,TodoList是UI組件,VisibleTodoList就是由React-Redux通過connect方法自動生成的容器組件。connect方法接受兩個參數:mapStateToPropsmapDispatchToProps。他們定義了UI組件的業務邏輯,前者負責輸入邏輯,即將state映射到UI組件的參數(props),后者負責輸出邏輯,即將用戶對UI組件的操作映射成Action。

mapStateToProps

mapStateToProps是一個函數,它的作用就像他的名字一樣,建立一個從(外部的)state對象到(UI組件的)props對象的映射關系。

作為函數,mapStateProps執行后應該返回一個對象,里面每個鍵值對就是一個映射,請看下例

const mapStateToProps = (state) =>{

? ? return {

? ? ?todos:getVisibleTodos(state.todos,state.visibilityFilter)

}

}

上面代碼中,mapStateToProps是一個函數,他接受state作為參數,返回一個對象。這個對象有一個todos屬性,代表UI組件的同名參數,后面的getVIsibleTodos也是一個函數,可以從state算出todos的值。

下面就是getVisibleTodos的一個例子,用來算出Todos。

const getVisibleTodos =(todos , ?filter) =>{

? switch (filter){

? ? case 'SHOW_ALL';

? ? ? ? return todos

? ? case 'SHOW_COMPLETED';

? ? ? ? return todos.filter( t => t.completed)

? ? ?case 'SHOW_ACTIVE' ;

? ? ? ? ?return todos.filter ( t=> ! t.completed)

? ? ? default:

? ? ? ? ? throw new Error ( ' Unknown filter: ' +filter)

? ?}

}

mapStateToProps會訂閱Store,每當state更新的時候,就會自動執行,重新計算UI組件的參數,從而觸發UI組件的重新渲染,

mapStateToProps的第一個參數總是state對象,還可以使用第二個參數,代表容器組件的props對象。

// 容器組件的代碼

// <FilterLink filter = "SHOW_ALL">

// ? ? ?ALL

// ?</FilterLink>

const mapStateToProps = (state, ownProps) =>{

return{

active : ownProps.filter===state.visibilityFilter

}

}

使用ownProps作為參數后,如果容器組件的參數發生變化,也會引發UI組件重新渲染。

connect方法可以省略mapStateToProps參數,那樣的話,UI組件就不會訂閱Store,就是說Store的更新不會引起UI組件的更新。

mapDispatchToProps()

mapDispatchToProps是connect函數的第二個參數,用來建立UI組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操作應該當做Action,傳給Store,他可以是一個函數,也可以是一個對象。

如果mapDispatchToProps是一個函數,會得到dispatch和ownProps(容器組件的props對象)兩個參數。

const mapDispatchToProps = {

? ? dispatch,

? ? ownprops

} = > {

? return {

? ? ?onClick: () =>{

? ? ?dispatch({

? ? ? ? ?type: ?'SET_VISIBILITY_FILTER',

? ? ? ? ?filter : ownProps.filter

? ? ? ? ? ?});

? ? ? ? }

? ? };

}

從上面代碼可以看出,mapDispatchToProps作為函數,應該返回一個對象,該對象的每一個鍵值對都是一個映射,定義了UI組件的參數怎么發出Action

如果mapDispatchToProps是一個對象,它的每個鍵名也是對應的UI組件的同名參數,鍵值應該是一個函數,會被當做Action Creator,返回的Action會由Redux自動發出。舉例來說,上面的mapDispatchToProps寫成對象就是下面這樣的

const mapDispatchToProps = ?{

? ?onClick : (filter) => {

? ? type:? 'SET_VISIBILITY_FILTER',

? ? filter : filter

};

}

<Provider>組件

connect 方法生成容器組件后,需要讓組件拿到state對象,才能生成UI組件的參數。React-Redux提供的Provider組件,可以讓容器組件拿到state

import ?{ Provider } from 'react-redux'

import { createStore ?} from 'redux'

import todoApp from './reducers'

import App form ' ./components/App'

let store = createStore(todoApp);

render(

? ? <Provider store={store}>

? ? ? ? <App/>

? ? ?</Provider>

? ? ?document.getElementById('root')

)

上面代碼中,Provider在根組件外面包了一層,這樣一來,App的所有子組件就默認都可以拿到state啦。

React-Router路由庫

使用React-Route的項目,與其他項目沒有不同之處,也是使用Provider在Router外面包了一層,畢竟Provider的唯一功能就是傳入store對象。

const Root =( { store } ) => (

? ? <Provider store ={store}>

? ? ? ? <Router>

? ? ? ? ? ? ?<Route path="/" component= { App } />

? ? ? ? </Router>

? ? </Provider>

);


后記:本文屬于筆記,所有內容出自是云隨風的博客,謝謝大神

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容