這是一篇詳盡的 React Redux 掃盲文。前端
對 React Redux 已經比較熟悉的同窗能夠直接看 《React Redux 與胖虎他媽》。react
React Redux 是 Redux 的 React 版,Redux 自己獨立於其餘框架而存在,又能夠結合其餘視圖框架使用,好比此處的 React。android
按我的理解,Redux 是應用的狀態管理框架,以事件流的形式來發送事件、處理事件、操做狀態和反饋狀態。webpack
這麼說仍是太抽象了,舉個簡單的例子。好比有個 A 組件,它要改變它本身的一個 div 裏面的文字,假設這個文字內容由 this.props.content
決定,那麼它能夠發送一個事件,這個事件通過一系列的處理,最終會改變 this.props.content
。git
龜龜,這也太秀了吧,改個文字都得這麼複雜?沒錯,若是是這種狀況去用 React Redux,那簡直就是多此一舉,沒事找事。這裏有一篇文章 You Might Not Need Redux,能夠考慮本身編寫的應用,是否真的須要 React Redux。github
回到上面的例子,假若 A 組件要去改變同級的一個 B 組件裏面的文字呢?按照咱們以前的作法,咱們會在 A B 組件的上一層套上一個 Parent 組件,將修改 B 組件文字的方法傳給 A 組件,A 組件調用後改變 Parent 組件的 state,進而改變 B 組件的文字。web
那麼咱們的代碼大約是這個樣子:redux
//Parent 組件
render() {
return (
<div>
<A onClick={(newContent) => this.setState({content: newContent})}/>
<B content={this.state.content}/>
</div>
)
}
//A 組件
render() {
return (
<div onClick={() => this.props.onClick('This is new content for B')}>Change B's content</div>
)
}
//B 組件
render() {
return (
<div>{this.props.content}</div>
)
}
複製代碼
有點費勁呢...但是咱們總算實現了功能~設計模式
什麼?多加了個同級的 C 組件,也要 A 組件來改變裏面的文字...bash
什麼?有個深度爲 100 的組件,要它來改變 B 組件的文字...
我胖虎出去抽根菸,回來要看到這兩個功能實現,否則錘死在座各位。
爲了實現這兩個功能,回調函數滿天飛,特別是第二個功能,你得把回調函數往下傳 100 層...
差很少這個時候,你就該考慮 React Redux 了。
像第二個功能,只須要從深層的組件發送一個事件出來,這個事件最終就會改變 B 組件的文字。
嗬,聽起來不錯。
講了這麼多,是時候一睹 React Redux 的真容。
其中 Action、Dispatch 和 Reducer 都是 React Redux 的東西,View 則是表明咱們的視圖層 React。
先理清幾個概念:Store,Action 和 Reducer(Dispatch 是 Store 的一個方法)
type
字段從上面的圖看到 View 層能夠經過兩種方式來更新:
上面不管哪種方式,都是遵循單向數據流的規則,即:發送 Action -> Reducer 根據 Action 返回數據 -> Store 被更新 -> View 被更新。
空談誤國,實幹興邦。仍是邊寫邊介紹爲好。
這裏實現一個小 demo,就一個按鈕和一個數字,點擊按鈕數字加 1,即計數器。
先說一點,React Redux 將組建區分爲 容器組件 和 UI 組件,前者會處理邏輯,後者只負責顯示和交互,內部不處理邏輯,狀態徹底由外部掌控。
「老子有的組件又負責顯示又負責處理邏輯,你怕不是在爲難我胖虎」
是的,不少狀況都是這樣,因此通常作法是在外面封裝一層,將邏輯和 UI 剝離,外層寫邏輯,內層純粹寫 UI。
因此對於計數器這個組件,咱們須要多封裝一層,使用的是 react-redux
的 connect
函數。這個函數顧名思義就是鏈接用的,鏈接 UI 組件,生成新的含有邏輯的組件。
connect
是一個高階函數,能夠傳入兩個函數:
import { connect } from 'react-redux';
import Counter from './Counter';
function mapStateToProps(state) {
return {
count: state.count
}
}
function mapDispatchToProps(dispatch) {
return {
onAdd: () => dispatch({type: 'ADD_COUNT'})
}
}
const newComponent = connect(mapStateToProps, mapDispatchToProps)(Counter);
複製代碼
connect
函數能夠傳入兩個函數:
此函數接收 state 參數(後面會講到,這個 state 是從 reducer 那裏過來的),定義從 state 轉換成 UI 組件 props 的規則。該函數返回 props 對象,好比咱們取 state 的 count
字段生成新的 props 返回。
此函數還能夠接收 ownProps 參數,表明直接在 UI 組件上聲明的 props:
function mapStateToProps(state, ownProps) {
console.log(ownProps); //{content: 'hello', color='white'}
return {};
}
//好比咱們是這麼使用 Counter 組件的
<Counter content='hello', color='white' />
複製代碼
此函數接收 dispatch 參數(其實是 Store 的 dispatch 方法),定義一系列發送事件的方法,並返回 props 對象。好比上面咱們定義發送 ADD_COUNT
事件的方法 onAdd
,其中 {type: 'ADD_COUNT'}
就是一個簡單的 Action 了。
等等,胖虎有話要說。
你說 mapStateToProps
返回 UI 組件的 props,mapDispatchToProps
也返回 UI 組件的 props,同時 UI 組件本身還定義了 props,那他孃的最後 UI 組件的 props 是什麼啊?
答案是,這 3 個 props 合在一塊。也就是說,照上面的例子,在 Counter 組件內部能夠調用到這些:
this.props.content;
this.props.color;
this.props.count;
this.props.onAdd();
export default class Counter extends React.Component {
render() {
return (
<div> <p>{this.props.count}</p> <button onClick={this.props.onAdd}>Add</button> </div>
)
}
}
複製代碼
恩,用 connect
就把外層的容器組件構造好了,咱們把剛剛那個含有 connect
函數的文件命名爲 index.jsx
。
咱們剛剛寫的那個 Counter,其實還不能用,由於咱們發送事件出去以後,並無對事件進行處理。
Reducer 就是用來處理 Action 的,其實是一個函數,好比咱們處理上面提到的 ADD_COUNT
事件:
//counter-reducer.js
export default function reducer(state={count: 0}, action) {
switch(action.type) {
case 'ADD_COUNT':
return {
count: state.count + 1
};
default:
return state;
}
}
複製代碼
像這裏,若是咱們判斷事件 type 是 ADD_COUNT
時,將 state 裏面的 count
字段屬性值 +1 而且返回新的 state 對象,這個對象會傳到 mapStateToProps
中。
Reducer 函數裏面有 2 點值得注意:
{count: 1}
,隨後返回 {count: 2}
,再下次進來就是 {count: 2}
了。state 能夠傳入初始化值,好比我們這裏初始值爲 0有人又好奇了,那這個 state 究竟是存在於哪裏的?目前咱們討論到的 Reducer 和 mapStateToProps
函數,它們都是接受 state,自己並不持有 state。
實際上,state 存在於 Store 中。後面還會講到,多個 Reducer 的狀況下,一個 Reducer 對應 Store 中的一個 state。
那麼,Store 又是怎麼做用到咱們的 DOM 樹上的?
React Redux 是經過 Provider 組件將 Store 這一個全局狀態容器綁定到 DOM 樹上,Provider 通常做爲 React Redux 應用最頂層的組件(Provider 並不真實存在於 DOM 樹上):
import { createStore, Provider } from 'react-redux';
import React from 'react';
import reducer from './counter-reducer.js';
import Counter from './components/Counter';
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}> <Counter/> </Provider>, document.getElementById('root'));
複製代碼
能夠看到咱們從 react-redux
這個庫引入了 createStore
Provider
,並使用 createStore
傳入上面的 Reducer 建立出 Store,再將 Store 傳到 Provider 組件,從而做用於整個 DOM 結構。
此時的項目結構爲(固然還有其餘一些 webpack 配置文件什麼的,就不列舉了):
Project
- components
- Counter
- index.jsx
- Counter.jsx
- index.jsx
複製代碼
到此爲止,這個計數器已經能正常地運做了。
咱們也稍微理解了 React Redux 的工做原理和方式了,再總結一番:
技術上的問題,歡迎討論。
我的博客:mindjet.github.io
最近在 Github 上維護的項目:
歡迎 star/fork/follow 提 issue 和 PR。