前端應用的狀態管理日益複雜。隨着大前端時代的到來,前端越來越注重處理邏輯,而不僅是專一 UI 層面的改進,而以 React 爲表明的前端框架的出現,大大簡化了咱們編寫 UI 界面的複雜度。雖然 React 提供了 State 機制實現狀態管理,也有諸如「狀態提高」等開發約定,可是這些方案只適用於小型應用,當你的前端應用有多達 10 個以上頁面時,如何讓應用狀態可控、讓協做開發高效成爲了亟待解決的問題,而 Redux 的出現正是爲了解決這些問題而生的!Redux 提出的「數據的惟一真相來源」、單向數據流、「純函數 Reducers」 大大簡化了前端邏輯,使得咱們可以以高效、便於協做的方式編寫任意複雜的前端應用。本篇教程致力於用簡短的文字講透 Redux,在實戰中掌握 Redux 的概念和精髓。前端
此教程屬於React 前端工程師學習路線的一部分,歡迎來 Star 一波,鼓勵咱們繼續創做出更好的教程,持續更新中~。node
Redux 官方文檔對 Redux 的定義是:一個可預測的 JavaScript 應用狀態管理容器。react
這就意味着,Redux 是沒法單獨運做的,它須要與一個具體的 View 層的前端框架相結合才能發揮出它的威力,這裏的 View 層包括但不限於 React、Vue 或者 Angular 等。這裏咱們將使用 React 做爲綁定視圖層,由於 Redux 最初誕生於 React 社區,爲解決 React 的狀態管理問題而設計和開發的一個庫。這篇教程將讓你直觀地感覺 React 的「狀態危機」,以及 Redux 是如何解決這一危機的,從而可以更好地學習 Redux,並理解它的源起,以及它將走向什麼樣的遠方。git
近來 React Hooks 確實很火,展示出驚人的潛力,甚至有人聲稱能夠拋棄 Redux 了。其實筆者以爲這種說法不徹底正確,Redux 的成功其實不只僅是由於這個框架自己,還由於圍繞其構建起來的生態系統,好比 Enhancers、Middlewares、還有諸如 redux-form,redux-immutable 等,甚至還有基於 Redux 的上層框架,如 Dva 還有 Rematch,這些都爲 Redux 鞏固了在 React 社區的王者地位。github
而有趣的是,咱們注意到 Redux 的 React 綁定庫 react-redux 如今正在用 React Hooks 重構,以求讓代碼更加精煉和高效,因此筆者以爲 React Hooks 首先還處於萌芽階段,一小部分嚐鮮者在視圖使用它來構建更好的 React 項目或者框架,React Hooks 可讓以前的這些項目和框架變得更好,以更好的輔助 Redux 生態的繼續繁榮,因此咱們有理由相信,React Hooks 的出現,會讓 React 社區變得更加高效和專業,也會幫助 Redux 工具鏈變得更加輕量,最終做爲 React 的一個優秀的特性將 React 和其生態帶向更好的遠方。數據庫
本篇教程是關於 Redux 的快速入門教程,並致力於講解與 React 綁定時的使用,而瞭解和掌握 Redux 對於一個 React 開發者來講屬於較爲進階的內容,因此咱們假設在閱讀本篇教程以前,你須要擁有如下的知識儲備:npm
const
、對象解構、函數默認參數等概念有良好的瞭解,固然若是你瞭解過函數式編程,如純函數、不變性等就更好了在本篇教程中,咱們將首先給出了一個使用 React 實現的待辦事項小應用(比上篇教程中完成的版本多了篩選的功能),它將是咱們學習 Redux 的起點,當你熟悉了這份初始代碼,並瞭解了它的功能以後,你就能夠關閉它,而後開始咱們教程的學習啦!編程
咱們將基於這個純 React 寫成的模板,分析 React 在處理狀態時存在的問題,以及用 Redux 重構帶來的優點。接着咱們將經過實戰的方式學習如何將一個純 React 應用一步步地重構成一個 Redux 應用,最終實現一個升級版的待辦事項小應用。redux
本教程所實現的源代碼都託管在 Github 上:後端
你能夠經過 CodeSandbox 查看代碼最終的效果:
無論外界把 Redux 吹得如何天花亂墜,實際上它能夠用一張圖來歸納,這張圖也有利於幫助你思考前端的本質是什麼:
咱們先來詳解一下這張圖,而且在教程以後的內容中,你會屢次看到這張圖以不一樣的形式出現。咱們但願學完本篇教程以後,每當你想起 Redux 時,腦海裏就是上面這張圖。
首先咱們來看 View ,在前端開發中,咱們稱這個爲視圖層,就是展現給最終用戶的效果,在本篇教程的學習中,咱們的 View 就是 React。
隨着前端應用要完成的工做愈來愈豐富,咱們對前端也提出了要保持 「狀態」 的要求。在 React 中,這個 「狀態」 將保存在 this.state
。在 Redux 中,這個狀態將保存在 Store。
這個 Store 從抽象意義上來講能夠看作一個前端的 「數據庫」,它保存着前端的狀態(state),而且分發這些狀態給 View,使得 View 根據這些狀態渲染不一樣的內容。
注意到,Redux 是一個可預測的 JavaScript 應用狀態管理容器,這個狀態容器就是這裏的 Store。
咱們平常生活中看到的網頁,它不是一成不變的,而是會響應用戶的 「動做」,不管是頁面跳轉仍是登錄註冊,這些動做會改變當前應用的狀態。
在 Redux 框架中,Reducers 的做用就是響應不一樣的動做。更精確地說,Reducers 是負責更新 Store 中狀態的 JavaScript 函數。
當咱們對這三個核心概念有了粗略的認知以後,就能夠開始 Redux 的學習了。
將初始 React 代碼模板 Clone 到本地,進入倉庫,並切換到 initial-code 分支(初始代碼模板):
git clone https://github.com/pftom/redux-quickstart-tutorial.git
cd redux-quickstart-tutorial
git checkout initial-code
複製代碼
安裝項目依賴,並打開開發服務器:
npm install
npm start
複製代碼
接着 React 開發服務器會打開瀏覽器,若是你看到下面的效果,而且能夠進行操做,那麼表明代碼準備完成:
提示
因爲咱們使用 Create React App 腳手架,它使用 Webpack Development Server(WDS)做爲開發服務器,所以在後面編輯代碼的時候只需保存文件,咱們的 React 應用就會自動刷新,很是方便。
咱們完成的這個待辦事項小應用比上篇教程中實現的要高級一點,以下面這個動圖所示:
咱們但願展現一個 todo 列表,當一個 todo 被點擊時,它將被加上刪除線表示此 todo 已經完成,咱們還加上了一個輸入框,使得用戶能夠增長新的 todo。在底部,咱們展現了三個按鈕,能夠切換展現 todo 的類型。
整份 React 代碼組件設計以下(首先是組件,而後是組件所擁有的屬性):
TodoList
用來展現 todo 列表:
todos: Array
是一個 todo 數組,它其中的每一個元素的樣子相似 { id, text, completed }
。toggleTodo(id: number)
是當一個 todo 被點擊時會調用的回調函數。Todo
是單一 todo 組件:
text: string
是這個 todo 將顯示的內容。completed: boolean
用來表示是否完成,若是完成,那麼樣式上就會給這個元素劃上刪除線。onClick()
是當這個 todo 被點擊時將調用的回調函數。Link
是一個展現過濾的按鈕:
active: boolean
表明此時被選中,那麼此按鈕將不能被點擊onClick()
表示這個 link 被點擊時將調用的回調函數。children: ReactComponent
展現子組件Footer
用於展現三個過濾按鈕:
filter: string
表明此時的被選中的過濾器字符串,它是 [SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE]
其中之一。setVisibilityFilter()
表明 Link 被點擊時將設置對應被點擊的 filter
的回調函數。App
是 React 根組件,最終組合其餘組件並使用 ReactDOM 對其進行編譯渲染,咱們在它的 state
上定義了上面的幾個組件會用到的屬性,同時定義了其餘組件會用到的方法,還有 nextTodoId
,VisibilityFilters
,getVisibleTodos
等一些輔助函數。咱們知道 Redux 能夠與多種視圖層開發框架如 React,Vue 和 Angular 等搭配使用,而 Redux 只是一個狀態管理容器,因此爲了在 React 中使用 Redux,咱們還須要安裝一下對應的依賴。
npm install redux
npm install react-redux
複製代碼
作得好!如今一切已經準備就緒,相信你已經火燒眉毛的想要編寫一點 Redux 相關的代碼了,別擔憂,在下一節中,咱們將引出 Redux Store 的詳細概念,而且經過代碼講解它將替換 React 的哪一個部分。
咱們前面提到了 Store 在 Redux 中的做用是用來保存狀態的,至關於咱們在前端創建了一個簡單的 」數據庫「。在目前的富狀態前端應用中,若是每一次狀態的修改(例如點擊一個按鈕)都須要與後端通訊,那麼整個網站的平均響應時間將變得難以接受,用戶體驗將糟糕透頂。
根據不徹底統計:」一個網站能留住一名用戶的時間只有 8S,若是你在 8S 內不能吸引住用戶,或者網站出現了問題,那麼你將完全地丟失這名用戶!」
因此爲了適應用戶的訪問需求,聰明的前端拓荒者們開始將後端的 「數據庫」 理念引入到前端中,這樣大多數的前端狀態能夠直接在前端搞定,徹底不須要後端的介入。
在 React 中,咱們將狀態存在每一個組件的 this.state
中,每一個組件的 state
爲組件所私有,若是要在一個組件中操做另一個組件,實現起來是至關繁瑣的。
咱們將用下面這張圖來演示一下爲何繁瑣:
組件 A 是組件 B 和 C 的父組件。若是組件 B 想要操做組件 C,那麼它首先須要調用父組件 A 傳給它的 handleClick
方法,而後經過這個方法修改父組件A的 state
,進而經過 React 的自動從新渲染機制,觸發組件 C 的變化。
如今組件 B 和組件 C 是處於平級的,你可能還感受不到這種跨組件改變有什麼問題,讓咱們再來看一張圖:
咱們看到上面這張圖,組件 B 和組件 C 相差了不少級,圖中的 n 可能爲 10,也可能更多。這個時候若是再想在組件 B 中修改組件 C,那就要把這個 handleClick
方法一層一層地往下傳。每次要修改的時候,都要進行調用,這已經至關繁瑣了。
若是組件 C 離組件 A 還有很深的層級,狀況就更復雜了:
這時候,不只要把 handleClick
方法經過很深的層級傳給組件 B,當組件 B 調用 handleClick
方法時,修改組件 A 的 state
,再反過來傳遞給組件 C 時,組件 A 到組件 C 之間的全部組件都會觸發從新渲染,這帶來了鉅額的渲染開銷,當咱們的應用愈來愈複雜,這種開銷顯然是承受不起的。
React 誕生的初衷就是爲了更好、更高效率地編寫用戶界面 ,它不該該也不須要來承擔狀態管理的職責。
因而備受折磨的前端拓荒者們構想出了偉大的 Store。咱們徹底不須要讓每一個組件單獨保持狀態,直接抽離全部組件的狀態,類比 React 組件樹,構造一個中心化的狀態樹,這棵狀態樹與 React 組件樹一一對應,至關於對 React 組件樹進行了狀態化建模:
能夠看到,咱們將組件的 state 去掉,取而代之的是一棵狀態樹,它是一個普通的 JavaScript 對象。經過對象的嵌套來類比組件的嵌套組合,這棵由 JavaScript 對象建模的狀態樹就是 Redux 中的 Store。
當咱們將組件的狀態抽離出去以後,咱們在使用組件 B 操做組件 C 就變得至關簡單且高效。
咱們在組件 B 中發起一個更新狀態 C 的動做,此動做對應的更新函數更新 Store 狀態樹,以後將更新後的狀態 C 傳遞給組件 C,觸發組件 C 的從新渲染。
能夠看到,當咱們引入這種機制以後,組件 B 與組件 C 之間的交互就可以單獨進行,不會影響 React 組件樹中的其餘組件,也不須要傳遞很深層級的 handleClick
函數了,不再須要把更新後的 state
一層一層地傳給組件 C,性能有了質的飛躍。
有了 Redux Store 以後,全部 React 應用中的狀態修改都是對這棵 JavaScript 對象樹的修改,全部狀態的獲取都是從這棵 JavaScript 對象樹獲取,這棵 JavaScript 對象表明的狀態樹成了整個應用的 「數據的惟一真相來源」。
瞭解了 Redux Store 之於 React 的做用以後,咱們立刻在 React 中應用 Redux ,看看神奇的 Store 是如何介入併產生如此大的變化的。
咱們修改初始代碼模板中的 src/index.js
,修改後的代碼以下:
import React from "react";
import ReactDOM from "react-dom";
import App, { VisibilityFilters } from "./components/App";
import { createStore } from "redux";
import { Provider } from "react-redux";
const initialState = {
todos: [
{
id: 1,
text: "你好, 圖雀",
completed: false
},
{
id: 2,
text: "我是一隻小小小小圖雀",
completed: false
},
{
id: 3,
text: "小若燕雀,亦可一展宏圖!",
completed: false
}
],
filter: VisibilityFilters.SHOW_ALL
};
const rootReducer = (state, action) => {
return state;
};
const store = createStore(rootReducer, initialState);
ReactDOM.render(
<Provider store={store}> <App /> </Provider>,
document.getElementById("root")
);
複製代碼
能夠看到,上面的代碼作了下面幾項工做:
redux
中導出了 createStore
,從 react-redux
導出了 Provider
,從 src/components/App.js
中導出了 VisibilityFilters
。initialState
對象,這將做爲咱們以後建立 Store 的初始狀態數據,也是咱們以前提到的那棵 JavaScript 對象樹的初始值。rootReducer
函數,它是一個箭頭函數,接收 state
和 action
而後返回 state
,這個函數目前尚未完成任何工做,可是它是建立 Store 所必須的參數之一,咱們將在以後的 Reducers 中詳細講解它。createStore
函數,傳入定義的 rootReducer
和 initialState
,生成了咱們本節的主角:store!App
組件的最外層使用 Provider
包裹,並接收咱們上一步建立的 store
做爲參數,這確保以後咱們能夠在子組件中訪問到 store 中的狀態。Provider
是 react-redux
提供的 API,是 Redux 在 React 使用的綁定庫,它搭建起 Redux 和 React 交流的橋樑。如今咱們已經建立了 Store,並使用了 React 與 Redux 的綁定庫 react-redux
提供的 Provider
組件將 Store 與 React 組件組合在了一塊兒。咱們立刻來看一下整合 Store 與 React 以後的效果。
打開 src/components/App.js
,修改代碼以下:
import React from "react";
import AddTodo from "./AddTodo";
import TodoList from "./TodoList";
import Footer from "./Footer";
import { connect } from "react-redux";
// 省略了 VisibilityFilters 和 getVisibleTodos 函數...
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTodo = this.toggleTodo.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.setVisibilityFilter = this.setVisibilityFilter.bind(this);
}
// 省略中間其餘方法...
render() {
const { todos, filter } = this.props;
return (
<div>
<AddTodo onSubmit={this.onSubmit} />
<TodoList
todos={getVisibleTodos(todos, filter)}
toggleTodo={this.toggleTodo}
/>
<Footer
filter={filter}
setVisibilityFilter={this.setVisibilityFilter}
/>
</div>
);
}
}
const mapStateToProps = (state, props) => ({
todos: state.todos,
filter: state.filter
});
export default connect(mapStateToProps)(App);
複製代碼
能夠看到,上面的代碼作了這幾項工做:
react-redux
綁定庫裏面導出了 connect
函數。mapStateToProps
箭頭函數,它接收 state
和 props
,這個 state
就是咱們那棵 Store 裏面保存的 JavaScript 對象狀態樹,目前就是咱們在上一個文件中定義的 initialState
內容;這個 props
就是咱們熟悉的原 React 組件的 props
,它對於 mapStateToProps
是一個可選參數。 mapStateToProps
函數就是能夠同時操做組件的原 props
和 Store 的狀態,而後合併成最終的組件 props,(固然這裏咱們並無使用原組件 props 內容)並經過 connect
函數傳遞給 App
組件。connect
函數接收 mapStateProps
函數,獲取 mapStateProps
返回的最終組合後的狀態,而後將其注入到 App
組件中,返回一個新的組件,而後交給 export default
導出。App
組件中就能夠取到經過 mapStateToProps
返回的 { todos, filter }
內容了,咱們經過對象解構,從 this.props
拿到 todos
和 filter
屬性。constructor
中的 this.state
內容。注意
connect
實際上是一個高階函數,高階函數就是指能夠接收參數調用並返回另一個函數的函數。這裏connect
經過接收mapStateToProps
而後調用返回一個新函數,接着這個新函數再接收App
組件做爲參數,經過mapStateToProps
注入todos
和filter
屬性,最後返回注入後的App
組件。
提示
這裏之因此咱們能在
App
組件中經過mapStateToProps
拿到 Store 中保存的 JavaScript 對象狀態樹,是由於咱們在以前經過Provider
包裹了App
組件,並將store
做爲屬性傳遞給了Provider
。
如今再來看一看咱們在第一步驟中提到的環形圖,咱們如今處於這個流程的第一步,即將 Store 裏面的狀態傳遞到 View 中,具體咱們是經過 React 的 Redux 綁定庫 react-redux
中的 connect
實現的。
保存改變的內容,若是你的 React 開發服務器打開着,那麼你應該能夠在瀏覽器中看到以下內容:
恭喜你!你已經成功編寫了 Redux 的 Store,完成將 Redux 整合進 React 工做的 1/3。 經過在 React 中接入 Store,你成功的將 Redux 和 React 之間的數據打通,並刪除了 this.state
,使用 Store 的狀態來取代 this.state
。
可是!當你此時點擊 Add Todo
按鈕,你的瀏覽器應該會顯示出紅色的錯誤,由於咱們已經刪除了 this.state
的內容,因此在 onSubmit
方法中讀取 this.state.todos
就會報錯。別擔憂,咱們將在下一節中: Action
中講解如何解決這些錯誤。
歡迎來到 Redux Action 環節,讓咱們再一次引用上一節提到的圖:
在上一節中,咱們就在組件 B 中完成某種動做來修改組件 C 中的內容,詳細剖析了徹底基於 React 實現的弊端,並經過引出 Redux Store 的概念,講解了咱們只須要建一個全局 JavaScript 對象狀態樹,而後全部的狀態的改變都是經過修改這一狀態樹,進而將修改後的新狀態傳給相應的組件並觸發從新渲染來完成咱們的目的。而且咱們講解了如何將 Store 裏面的狀態傳給 React 組件使用。
這一節咱們就來說一講,如何修改 Redux Store 中保存的狀態。讓咱們再拋出熟悉的 Redux 狀態環形圖:
修改 Store 中保存的狀態就是上面這張圖的第二個部分,即咱們已經建立好了 Store,並在裏面存儲了一棵 JavaScript 對象狀態樹,咱們經過 「發起更新動做」 來修改 Store 中保存的狀態。
在 Redux 的概念術語中,更新 Store 的狀態有且僅有一種方式:那就是調用 dispatch
函數,傳遞一個 action
給這個函數 。
一個 Action 就是一個簡單的 JavaScript 對象:
{ type: 'ADD_TODO', text: '我是一隻小小小圖雀' }
複製代碼
咱們能夠看到一個 action
包含動做的類型,以及更新狀態須要的數據,其中 type
是必須的,其它內容都是可選的,這裏咱們除了 type
,還額外添加了一個 text
,表明咱們發起 type
爲 ADD_TODO
的動做是,額外傳遞了一個 text
內容。
因此若是咱們須要更新 Store 狀態,那麼就須要相似下面的函數調用:
dispatch({ type: 'ADD_TODO', text: '我是一隻小小小圖雀' })
複製代碼
由於咱們在建立 Action 的時候,有時候有些內容是固定了,好比咱們的待辦事項添加教程的 Action,有三個字段,分別是 type
,text
,id
,咱們可能會要在多個地方能夠 dispatch
這個 Action,那麼咱們每次都須要寫下面長長的一串 :
{ type: 'ADD_TODO', text: '我是一隻小小小圖雀' , id: 0}
{ type: 'ADD_TODO', text: '小若燕雀,亦可一展宏圖' , id: 1}
...
{ type: 'ADD_TODO', text: '歡迎你加入圖雀社區!' , id: 10}
複製代碼
對 JavaScript 函數比較熟悉的同窗可能就知道該如何解決這種問題。是的,咱們只須要定義一個函數,而後傳入須要變化的參數就能夠了。
let nextTodoId = 0;
const addTodo = text => ({
type: "ADD_TODO",
id: nextTodoId++,
text
});
複製代碼
這種接收一些須要修改的參數,返回一個 Action 的函數在 Redux 中被稱爲 Action Creators(動做建立器)。
當咱們使用 Action Creators 來建立 Action 以後,咱們再想要修改 Store 的狀態就變成了下面這樣:
dispatch(addTodo('我是一隻小小小圖雀'))
複製代碼
能夠看到,咱們的邏輯大大簡化了,每次發起一個新的 "ADD_TODO"
action,都只須要傳入對應的 text。
瞭解了 Action 的基礎概念以後,咱們立刻來嘗試一下如何在 React 中發起更新動做。
首先,咱們在 src
文件夾下面建立 actions
文件夾,而後在 actions
文件夾下建立 index.js
文件,並在裏面添加下面的 Action Creators:
let nextTodoId = 0;
export const addTodo = text => ({
type: "ADD_TODO",
id: nextTodoId++,
text
});
複製代碼
由於在使用 Redux 的 React 應用中,咱們將須要建立大量的 Action 或者 Action Creators,因此 Redux 社區的最佳實踐推薦咱們建立一個獨立的 actions
文件夾,並在這個文件夾裏面編寫特定的 Action 邏輯。
能夠看到,咱們加入了一個 addTodo
Action Creator,它接收 text
參數,並每次自增一個 id
,而後返回帶有 id
和 text
,而且類型爲 "ADD_TODO"
的 Action。
接着咱們修改 src/components/AddTodo.js
文件,將以前的 onSubmit
替換成以 dispatch(action)
的形式來修改 Store 的狀態:
import React from "react";
import { connect } from "react-redux";
import { addTodo } from "../actions";
const AddTodo = ({ dispatch }) => {
let input;
return (
<div> <form onSubmit={e => { e.preventDefault(); if (!input.value.trim()) { return; } dispatch(addTodo(input.value)); input.value = ""; }} > <input ref={node => (input = node)} /> <button type="submit">Add Todo</button> </form> </div> ); }; export default connect()(AddTodo); 複製代碼
能夠看到,上面的代碼作了這幾項改變:
react-redux
中導出了 connect
函數,它負責將 Store 中的狀態注入組件的同時,還給組件傳遞了一個額外的方法:dispatch
,這樣咱們就能夠在組件的 props
中獲取這個方法。注意到咱們在 AddTodo 函數式組件中使用了對象解構來獲取 dispatch
方法。addTodo
Action Creators。addTodo
接收 input.value
輸入值,建立一個類型爲 "ADD_TODO"
的 Action,並使用 dispatch
函數將這個 Action 發送給 Redux,請求更新 Store 的內容,更新 Store 的狀態須要 Reducers 來進行操做,咱們將在 Reducer 中詳細講解它。由於咱們已經將直接修改 this.state
的 onSubmit
換成了 dispatch
一個 Action,因此咱們刪除 src/components/App.js
相應的代碼,由於咱們如今已經不須要它們了:
import React from "react";
import AddTodo from "./AddTodo";
import TodoList from "./TodoList";
import Footer from "./Footer";
import { connect } from "react-redux";
// 省略 VisibilityFilters 和 getVisibleTodos ...
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTodo = this.toggleTodo.bind(this);
this.setVisibilityFilter = this.setVisibilityFilter.bind(this);
}
toggleTodo(id) {
const { todos } = this.state;
this.setState({
todos: todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
});
}
setVisibilityFilter(filter) {
this.setState({
filter: filter
});
}
render() {
const { todos, filter } = this.props;
return (
<div>
<AddTodo />
<TodoList
todos={getVisibleTodos(todos, filter)}
toggleTodo={this.toggleTodo}
/>
<Footer
filter={filter}
setVisibilityFilter={this.setVisibilityFilter}
/>
</div>
);
}
}
// 後面沒有變化 ...
複製代碼
能夠看到咱們刪除了 nextTodoId
,由於咱們已經在 src/actions/index.js
中從新定義了它;接着咱們刪除了 onSubmit
方法;最後咱們刪除了傳遞給 AddTodo
組件的 onSubmit
方法。
保存修改的內容,咱們在待辦事項小應用的輸入框裏面輸入點內容,而後點擊 Add Todo
按鈕,咱們發現,以前的錯誤沒有再次出現。
在這一節中,咱們完成了 Redux 狀態環形圖的第二個部分,即發起更新動做,咱們首先講解了什麼是 Action 和 Action Creators,而後經過 dispatch(action)
的方式來發起一個更新 Store 中狀態的動做。
當咱們使用了 dispatch(action)
以後,傳遞給子組件,用來修改父組件 State 的方法就不須要了,因此咱們在代碼中刪除了它們。在咱們的 AddTodo
中,這個方法就是 onSubmit
。
可是有一點遺憾就是,咱們雖然刪除了 onSubmit
方法,可是咱們這一節中講到和實現的 dispatch(action)
還只能完成以前 onSubmit
方法的一半功能,即發起修改動做,可是咱們目前還沒法修改 Store 中的狀態。爲了修改 Store 中的 State,咱們須要定義 Reducers,用於響應咱們 dispatch
的 Action,並根據 Action 的要求修改 Store 中對應的數據。
在這一節中,咱們立刻來告終上一節中留下的遺憾,即咱們好像放了一聲空炮,dispatch
了一個 Action,可是沒有收穫任何效果。
首先祭出咱們萬能的 Redux 狀態循環圖:
咱們已經完成了前兩步了,離 Redux 整合進 React 只剩下最後一個步驟,即響應從組件中 dispatch
出來 Action,並更新 Store 中的狀態,這在 Redux 的概念中被稱之爲 Reducers。
reducer
是一個普通的 JavaScript 函數,它接收兩個參數:state
和 action
,前者爲 Store 中存儲的那棵 JavaScript 對象狀態樹,後者即爲咱們在組件中 dispatch
的那個 Action。
reducer(state, action) {
// 對 state 進行操做
return newState;
}
複製代碼
reducer 根據 action 的指示,對 state 進行對應的操做,而後返回操做後的 state,Redux Store 會自動保存這份新的 state。
注意
Redux 官方社區對 reducer 的約定是一個純函數,即咱們不能直接修改
state
,而是可使用{...}
等對象解構手段返回一個被修改後的新state
。好比咱們對
state = { a: 1, b: 2 }
進行修改,將a
替換成 3,咱們應該這麼作:newState = { ...state, a: 3 }
,而不該該state.a = 3
。 這種不直接修改原對象,而是返回一個新對象的修改,咱們稱之爲 「純化」 的修改。
當了解了 Reducer 的概念以後,咱們立刻在應用中響應咱們以前 dispatch
的 Action,來彌補咱們在上一節中留下的遺憾。
打開 src/index.js
,對 rootReducer
做出以下修改:
// ...
const rootReducer = (state, action) => {
switch (action.type) {
case "ADD_TODO": {
const { todos } = state;
return {
...state,
todos: [
...todos,
{
id: action.id,
text: action.text,
completed: false
}
]
};
}
default:
return state;
}
};
// ...
複製代碼
上面的代碼作了這麼幾項工做:
rootReducer
進行改進,從單純地返回原來的 state
,變成了一個 switch
語句,在 switch
語句中對 action 的 type
進行判斷,而後作出對應的處理。action.type
的類型爲 "ADD_TODO"
時,咱們從 state
中取出了 todos
,而後使用 {...}
語法給 todos
添加一個新的元素對象,並設置 completed
屬性爲 false
表明此 todo 未完成,最後再經過一層 {...}
語法將新的 todos
合併進老的 state
中,返回這個新的 state
。action.type
沒有匹配 switch
的任何條件時,咱們返回默認的 state
,表示 state
沒有任何更新。當咱們對 rootReducer
函數作了上述的改動以後,Redux 經過 Reducer 函數就能夠響應從組件中 dispatch
出來的 action
了,目前咱們還只能夠響應 action.type
爲 "ADD_TODO"
的 action,它表示新增一個 todo。
保存修改的代碼,打開瀏覽器,在輸入框裏面輸入點內容,而後點擊 Add Todo
按鈕,如今網頁應該能夠正確響應你的操做了,咱們又能夠愉快地添加新的待辦事項了。
在這一小節中,咱們實現了第一個能夠響應組件 dispatch
出來的 Action 的 Reducer,它判斷 action.type
的類型,並根據這些類型對 state
進行 「純化」 的修改,當 action.type
沒有匹配 Reducer 中任何類型時,咱們返回原來的 state
。
當了解了 Redux 三大概念:Store,Action,Reducers 以後,咱們再來看一張圖:
這張圖咱們以前看過相似的,只不過這一次咱們在這張圖上加了點東西,分別標出了 dispatch
、 reducers
和 connect
所完成的工做。
dispatch(action)
用來在 React 組件中發出修改 Store 中保存狀態的指令。在咱們須要新加一個待辦事項時,它取代了以前定義在組件中的 onSubmit
方法。reducer(state, action)
用來根據這一指令修改 Store 中保存狀態對應的部分。在咱們須要新加一個待辦事項時,它取代了以前定義在組件中的 this.setState
操做。connect(mapStateToProps)
用來將更新好的數據傳給組件,而後觸發 React 從新渲染,顯示最新的狀態。它架設起 Redux 和 React 之間的數據通訊橋樑。如今,Redux 的核心概念你已經所有學完了,而且咱們的應用已經徹底整合了 Redux。可是,咱們還有一點工做沒有完成,那就是將整個應用徹底使用 Redux 重構。在下一篇教程中,咱們將使用咱們在上面三節學到的知識,一步一步將咱們的待辦事項應用的其餘部分重構成 Redux,敬請期待~
想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。