Redux + React-Router 的入門教學和配置教程,看這一篇就夠了

前言

React

是單向資料流,資料透過

props

從父節點傳遞到子節點。如果頂層的某個

props

改變了,

React

會重新渲染所有的子節點。注意:

props

是隻讀的(即不可以使用

this.props

直接修改

props

),它是用於在整個元件樹中傳遞資料和配置。

每個元件都有屬於自己的

state

state

props

的區別在於

state

只存在於元件內部。注意 ⚠️:只能從當前元件呼叫

this.setState

方法修改

state

值(不可以直接修改

this.state

)。

可見,更新子元件有兩種方式,一種是改變子元件自身的

state

值,另一種則是更新子元件從父元件接收到的

this.props

值從而達到更新。

React

專案開發過程中,我們大多時候需要讓元件共享某些資料。一般來說,我們可以透過在元件間傳遞資料(透過

props

)的方式實現資料共享,然而,當資料需要在非父子關係的元件間傳遞時操作起來則變得十分麻煩,而且容易讓程式碼的可讀性降低,這時候我們就需要使用

state

(狀態)管理工具。

常見的狀態管理工具有

Redux

Mobx

。由於

Redux

提供了狀態管理的整個架構,並有著清晰的約束規則,適合在大型多人開發的應用中使用。本文介紹的是如何在

React

專案中使用

Redux

進行狀態管理。

進入正題

本節主要介紹

Redux

React-Router

相關的基礎知識和相關配置教程。

Redux

-

基本概念

Redux

適用於多互動、多資料來源的場景。從元件角度看,如果我們的應用有以下場景,則可以考慮在專案中使用

Redux

某個元件的狀態,需要共享

某個狀態需要在任何地方都可以拿到

一個元件需要改變全域性狀態

一個元件需要改變另一個元件的狀態

當我們的應用符合以上提到的場景時,若不使用

Redux

或者其他狀態管理工具,不按照一定規律處理狀態的讀寫,專案程式碼的可讀性將大大降低,不利於團隊開發效率的提升。

Redux + React-Router 的入門教學和配置教程,看這一篇就夠了

如上圖所示,

Redux

透過將所有的

state

集中到元件頂部,能夠靈活的將所有

state

各取所需地分發給所有的元件。

Redux

的三大原則:

整個應用的

state

都被儲存在一棵

object tree

中,並且

object tree

只存在於唯一的

store

中(這並不意味使用

Redux

就需要將所有的

state

存到

redux

上,元件還是可以維護自身的

state

)。

state

是隻讀的。

state

的變化,會導致檢視(

view

)的變化。使用者接觸不到

state

,只能接觸到檢視,唯一改變

state

的方式則是在檢視中觸發

action

action

是一個用於描述已發生事件的普通物件。

使用

reducers

來執行

state

的更新。

reducers

是一個純函式,它接受

action

和當前

state

作為引數,透過計算返回一個新的

state

,從而實現檢視的更新。

Redux + React-Router 的入門教學和配置教程,看這一篇就夠了

如上圖所示,redux 的工作流程大致如下:

首先,使用者在檢視中透過

store.dispatch

方法發出

action

然後,

store

自動呼叫

reducers

,並且傳入兩個引數:當前

state

和收到的

action

reducers

會返回新的

state

最後,當

store

監聽到

state

的變化,就會呼叫監聽函式,觸發檢視的重新渲染。

放一張圖加深理解:

Redux + React-Router 的入門教學和配置教程,看這一篇就夠了

下面我們來介紹一下

Redux 的

API

相關內容。

Store

Store

就是儲存資料的地方,整個應用只能有一個

Store

Redux

提供

createStore

這個函式,用來建立一個

Store

以存放整個應用的

state

import { createStore } from ‘redux’;const store = createStore(reducer, [preloadedState], enhancer);

可以看到,

createStore

接受

reducer

、初始

state

(可選)和增強器作為引數,返回一個新的

store

物件。

State

store

物件包含所有資料。如果想得到某個時點的資料,就要對

store

生成快照。這種時點的資料集合,就叫做

state

如果要獲取當前時刻的 state,可以透過 store。getState() 方法拿到:

import { createStore } from ‘redux’;const store = createStore(reducer, [preloadedState], enhancer);const state = store。getState();

Action

state 的變化,會導致檢視的變化。但是,使用者接觸不到

state

,只能接觸到檢視。所以,

state

的變化必須是由檢視發起的。

action

就是檢視發出的通知,通知

store

此時的

state

應該要發生變化了。

action

是一個物件。其中的

type

屬性是必須的,表示

action

的名稱。其他屬性可以自由設定,社群有一個規範可以參考:

const action = { type: ‘ADD_TODO’, payload: ‘Learn Redux’ // 可選屬性};

上面程式碼定義了一個名稱為

ADD_TODO

action

,它攜帶的資料資訊是

Learn Redux

Action Creator

view

要傳送多少種訊息,就會有多少種

action

,如果都手寫,會很麻煩。

可以定義一個函式來生成

action

,這個函式就稱作

Action Creator

,如下面程式碼中的

addTodo

函式:

const ADD_TODO = ‘新增 TODO’;function addTodo(text) { return { type: ADD_TODO, text }}const action = addTodo(‘Learn Redux’);

redux-actions

是一個實用的庫,讓編寫

redux

狀態管理變得簡單起來。該庫提供了

createAction

方法用於建立動作建立器:

import { createAction } from “redux-actions”export const INCREMENT = ‘INCREMENT’export const increment = createAction(INCREMENT)

上邊程式碼定義一個動作

INCREMENT

, 然後透過

createAction

建立了對應

Action Creator

: 呼叫

increment()

時就會返回

{ type: 'INCREMENT' }

呼叫

increment(10)

返回

{ type: 'INCREMENT', payload: 10 }

store.dispatch()

store.dispatch()

是檢視發出

action

的唯一方法,該方法接受一個

action

物件作為引數:

import { createStore } from ‘redux’;const store = createStore(reducer, [preloadedState], enhancer);store。dispatch({ type: ‘ADD_TODO’, payload: ‘Learn Redux’});

結合

Action Creator

,這段程式碼可以改寫如下:

import { createStore } from ‘redux’;import { createAction } from “redux-actions”const store = createStore(reducer, [preloadedState], enhancer);const ADD_TODO = ‘ADD_TODO’;const add_todo = createAction(‘ADD_TODO’); // 建立 Action Creatorstore。dispatch(add_todo(‘Learn Redux’));

reducer

store

收到

action

以後,必須給出一個新的

state

,這樣檢視才會進行更新。

state

的計算(更新)過程則是透過

reducer

實現。

reducer

是一個函式,它接受

action

和當前

state

作為引數,返回一個新的

state

const reducer = function (state, action) { // 。。。 return new_state;};

為了實現呼叫

store.dispatch

方法時自動執行

reducer

函式,需要在建立

store

時將將

reducer

傳入

createStore

方法:

import { createStore } from ‘redux’;const reducer = function (state, action) { // 。。。 return new_state;};const store = createStore(reducer);

上面程式碼中,

createStore

方法接受

reducer

作為引數,生成一個新的

store

。以後每當檢視使用

store.dispatch

傳送給

store

一個新的

action

,就會自動呼叫

reducer

函式,得到更新的

state

redux-actions

提供了

handleActions

方法用於處理多個

action

// 使用方法:// handleActions(reducerMap, defaultState)import { handleActions } from ‘redux-actions’;const initialState = { counter: 0 };const reducer = handleActions( { INCREMENT: (state, action) => ({ counter: state。counter + action。payload }), DECREMENT: (state, action) => ({ counter: state。counter - action。payload }) }, initialState,);

拆分、合併 reducer

前面提到,在一個

React

應用中只能有一個

store

用於存放應用的

state

。元件透過呼叫

action

函式,傳遞資料到

reducer

reducer

根據資料更新對應的

state

對於大型應用來說,應用的

state

必然十分龐大,導致

reducer

的複雜度也隨著變大。

關於拆分

在這個時候,就可以考慮將

reducer

拆分成多個單獨的函式,讓每個函式負責獨立管理

state

的一部分。

關於合併

redux

提供了

combineReducers

輔助函式,可將獨立分散的

reducer

合併成一個最終的

reducer

函式,然後在建立

store

時作為

createStore

的引數傳入。

我們可以根據業務需要,把所有子

reducer

放在不同的目錄下,然後在在一個檔案裡面統一引入,最後將合併後的

reducer

匯出:

// src/model/reducers。tsimport { combineReducers } from ‘redux’;import UI from ‘。/UI/reducers’;import user from ‘。/user/reducers’;import content from ‘。/content/reducers’;const rootReducer = combineReducers({ UI, user, content,});export default rootReducer;

中介軟體及非同步操作

redux

而言,同步指的是當檢視發出

action

以後,

reducer

立即算出

state

(原始的

redux

工作流程),而非同步指的是在

action

發出以後,過一段時間再執行

reducer

同步通常發生在原生

redux

的工作流程中,而在大多數實際場景中,更多的是需要非同步操作:

action

發出以後,在進入

reducer

之前需要先完成一個非同步任務,比如傳送

ajax

請求後拿到資料後,再進入

reducer

執行計算並對

state

進行更新。

顯然原生的

redux

是不支援非同步操作的,這就要用到新的工具——中介軟體(

middleware

)來處理這種業務場景。從本質上來講中介軟體是對

store.dispatch

方法進行了拓展。

中介軟體提供位於

action

發起之後,到達

reducer

之前的擴充套件點:即透過

store.dispatch

方法發出的

action

會依次經過各個中介軟體,最終到達

reducer

Redux + React-Router 的入門教學和配置教程,看這一篇就夠了

我們可以利用中介軟體來進行日誌記錄(

redux-logger

)、建立崩潰報告(自己寫

crashReporter

)、呼叫非同步介面(

redux-saga

)或者路由(

connected-react-router

)等操作。

redux

提供了一個原生的

applyMiddleware

方法,它的作用是將所有中介軟體組成一個數組,依次執行。假如要使用

redux-logger

來實現日誌記錄功能,用法如下:

import { applyMiddleware, createStore } from ‘redux’;import createLogger from ‘redux-logger’;const logger = createLogger();const store = createStore( reducer, applyMiddleware(logger));

如果有多箇中間件,則將中介軟體依次作為引數傳入

applyMiddleware

方法中:

import { applyMiddleware, createStore } from ‘redux’;import createLogger from ‘redux-logger’;import createSagaMiddleware from ‘redux-saga’;const logger = createLogger(); // 日誌記錄const sagaMiddleware = createSagaMiddleware(); // 呼叫非同步介面let middleware = [sagaMiddleware];middleware。push(logger);const store = createStore( reducer, // 可傳initial_state applyMiddleware(。。。middleware));

需要注意的是:

createStore

方法可以接受整個應用的初始狀態作為引數(可選),若傳入初始狀態,

applyMiddleware

則需要作為第三個引數。 有的中介軟體有次序要求,使用前要查一下文件(如

redux-logger

一定要放在最後,否則輸出結果會不正確)。

React-Redux 概念介紹

前面小節介紹的

Redux

本身是一個可以結合

React

Vue

Angular

甚至是原生

JavaScript

應用使用的狀態庫。

為了讓

Redux

幫我們管理

React

應用的狀態,需要把

Redux

React

連線,官方提供了

React-Redux

庫(這個庫是可以選用的,也可以只用

Redux

)。

React-Redux

將所有元件分成

UI

元件和容器元件兩大類:

UI

元件只負責

UI

的呈現,不含有狀態(

this.state

),所有資料都由

this.props

提供,且不使用任何

Redux

API

。容器元件負責管理資料和業務邏輯,含有狀態(

this.state

),可使用

Redux

API

簡而言之,容器元件作為

UI

元件的父元件,負責與外部進行通訊,將資料透過

props

傳給

UI

元件渲染出檢視。

React-Redux

規定,所有的

UI

元件都由使用者提供,容器元件則是由

React-Redux

自動生成。也就是說,使用者負責視覺層,狀態管理則是全部交給

React-Redux

下面我們來介紹一下

React-Redux API

相關的內容

connect

方法

React-Redux

提供了

connect

方法,用於將

UI

元件生成容器元件:

import { connect } from ‘react-redux’class Dashboard extends React。Component { 。。。 // 元件內部可以獲取 this。props。loading 的值}const mapStateToProps = (state) => { return { loading: state。loading, }}// 將透過 connect 方法自動生成的容器元件匯出export default connect( mapStateToProps, // 可選 // mapDispatchToProps, // 可選)(Dashboard)

從上面程式碼可以看到,

connect

方法接受兩個可選引數用於定義容器元件的業務邏輯:

mapStateToProps

負責輸入邏輯,即將

state

對映成傳入

UI

元件的引數(

props

mapDispatchToProps

負責輸出邏輯,即將使用者對

UI

元件的操作對映成

action

注意:當

connect

方法不傳入任何引數時,生成的容器元件只可以看作是對

UI

元件做了一個單純的包裝,不含有任何的業務邏輯: 省略

mapStateToProps

引數,

UI

元件就不會訂閱

store

,即

store

的更新不會引起

UI

元件的更新。 省略

mapDispatchToProps

引數,

UI

元件就不會將使用者的操作當作

action

傳送資料給

store

,需要在元件中手動呼叫

store.dispatch

方法。

mapStateToProps

mapStateToProps

是一個函式,它的作用就是建立一個從

state

物件(外部)到 UI 元件

props

物件的對映關係。該函式會訂閱 整個應用的

store

,每當

state

更新的時候,就會自動執行,重新計算 UI 元件的引數,從而觸發 UI 元件的重新渲染。

mapStateToProps

的第一個引數總是

state

物件,還可以使用第二個引數(可選),代表容器元件的

props

物件:

// 容器元件的程式碼// // All// const mapStateToProps = (state, ownProps) => { return { active: ownProps。showType === “SHOW_ALL”, loading: state。loading, }}

使用

ownProps

作為引數後,如果容器元件的引數發生變化,也會引發 UI 元件重新渲染。

mapDispatchToProps

mapDispatchToProps

connect

函式的第二個引數,用來建立 UI 元件的引數到

store.dispatch

方法的對映。

由於在專案中大多使用

mapDispatchToProps

比較少,這裡不進行細講。關於

mapStateToProps

mapDispatchToProps

connect

的更詳細用法說明可以檢視文件。

Provider 元件

使用

connect

方法生成容器元件以後,需要讓容器元件拿到

state

物件,才能生成 UI 元件 的引數。

react-redux

提供了

Provider

元件,可以讓容器元件拿到

state

,具體用法是需要用

Provider

元件包裹專案的根元件(如

App

),使得根元件所有的子元件都可以預設獲取到

state

物件:

import * as React from ‘react’;import * as ReactDOM from ‘react-dom’;import { Provider } from ‘react-redux’;import App from ‘。/App’;import { store } from ‘。/store/configureStore’;ReactDOM。render( , document。getElementById(‘root’),);

react-router

react-router

是完整的

react

的路由解決方案,它保持

UI

URL

的同步。在專案中我們使用的是最新的

v4

版。

需要注意的是,在開發中不應該直接安裝

react-router

,因為:在

v4

版中

react-router

被劃分為三個包:

react-router

react-router-dom

react-router-native

,它們的區別如下:

react-router

:提供核心的路由元件和函式。

react-router-dom

:提供瀏覽器使用的路由元件和函式。

react-router-native

:提供

react-native

對應平臺使用的路由元件和函式。

當我們的

react

應用同時使用了

react-router

redux

,則可以將兩者進行更深度的整合,實現:將

router

的資料與

store

進行同步,並且可以從

store

訪問

router

資料,可使用

this.props.dispatch

方法傳送

action

。透過

dispatch actions

導航,個人理解是可使用

store.dispatch(push('routerName'))

切換路由。在

redux devtools

中支援路由改變的時間旅行除錯。

想要實現以上的目標,則可以透過

connected-react-router

history

兩個庫進行實現,步驟如下:在建立

store

的檔案新增配置,包括建立

history

物件、使用

connected-react-router

提供的

connectRouter

方法和

history

物件建立

root reducer

、使用

connected-react-router

提供的

routerMiddleware

中介軟體和

history

物件實現

dispatch actions

導航。

import { connectRouter, routerMiddleware } from ‘connected-react-router’;import createHistory from ‘history/createBrowserHistory’;import { createStore, applyMiddleware } from ‘redux’;import { createLogger } from ‘redux-logger’;import createSagaMiddleware from ‘redux-saga’;import reducer from ‘。。/model/reducers’;export const history = createHistory();const sagaMiddleware = createSagaMiddleware(); // 呼叫非同步介面let middleware = [sagaMiddleware, routerMiddleware(history)];const logger = createLogger(); // 日誌記錄middleware。push(logger);const initialState = {};const store = createStore( connectRouter(history)(reducer), initialState, applyMiddleware(。。。middleware));

在專案入口檔案

index.js

中為根元件中新增配置,包括使用

connected-react-router

提供的

ConnectedRouter

元件包裹路由,將

ConnectedRouter

元件作為

Provider

的子組,並且將在

store

中建立的

history

物件引入,將其作為

props

屬性 傳入

ConnectedRouter

元件

import * as React from ‘react’;import * as ReactDOM from ‘react-dom’;import { Provider } from ‘react-redux’import { ConnectedRouter } from ‘connected-react-router’import App from ‘。/App’import rootSaga from ‘。/model/sagas’;import { store, history } from ‘。/store/configureStore’;ReactDOM。render( , document。getElementById(‘root’),);

以上則完成了 react-router 和 redux的深度整合 ✌️。

總結

本文介紹的是如何在

React

專案中使用

redux

進行狀態管理,並對相關基礎知識進行介紹和展示了完整的程式碼。

在進行業務程式碼開發前通常會對專案進行的一些特殊配置,有利於後期的工程開發,具體內容可參考 :react + typescript 專案的定製化過程。

版權

作者:前端小黑

連結:https://juejin。im/post/5dcaaa276fb9a04a965e2c9b

著作權歸作者所有。