如何理解Redux

中文文檔
英文文檔

Redux = Reducer + Flux

Redux 是 React中的狀態(tài)管理容器抖僵,可以理解為存放 公用數(shù)據(jù)的地方,提供一種更加規(guī)范和安全的方式去定義組件之間的公用數(shù)據(jù)(想象一下兄弟組件傳值的過程)缘揪。對于初學者來說耍群,它的使用并不簡單。


Redux的理解.png

三大原則

  1. 單一數(shù)據(jù)源
  2. State 是只讀的
  3. 使用純函數(shù)來執(zhí)行修改

單一數(shù)據(jù)源

整個應用的 state 被儲存在一棵 object tree 中找筝,并且這個 object tree 只存在于唯一一個 store 中

State 是只讀的

唯一改變 state 的方法就是觸發(fā) action蹈垢,action 是一個用于描述已發(fā)生事件的普通對象。
這里的state是值 redux中的數(shù)據(jù)袖裕,不是組件的state

使用純函數(shù)來執(zhí)行修改

為了描述 action 如何改變 state tree 曹抬,你需要編寫 reducers。

redux概念

Action

行為

Action 是把數(shù)據(jù)從應用陆赋。傳到 store 的有效載荷沐祷。我們的數(shù)據(jù)只能通過 Action來觸發(fā)嚷闭,修改攒岛。

Reducer

Reducers 負責接收 action,然后根據(jù)action去處理store
不能直接修改原來的state胞锰。

Store

store其實是一個倉庫灾锯,redux應用只有一個store,當需要拆分數(shù)據(jù)時嗅榕,不能拆分store顺饮,但可以拆分reducer

工作流

Redux工作流.png

體驗步驟

  1. 安裝依賴
  2. 創(chuàng)建store
  3. 創(chuàng)建reducer
  4. 將store數(shù)據(jù)映射到組件中
  5. 組件觸發(fā)事件 創(chuàng)建action
  6. 將action派發(fā)到store
  7. store自己調(diào)用reducer

初體驗

實現(xiàn)目標

實現(xiàn)一下以上效果.gif
  1. 可以發(fā)請求加載數(shù)據(jù)
  2. 點擊 + - 組件 會修改數(shù)據(jù)

安裝依賴

redux 是核心庫 react-redux是負責將react組件連接redux

npm install redux react-redux --save

新建redux配套文件

在src/store/目錄下新建 以下文件

  1. index.js store核心文件
  2. reducer/index.js 負責記錄操作的reducer文件

reducer.js

// 1 定義默認數(shù)據(jù),后期可以從接口中獲取
const defaultState = {
  num: -1
};

// 2 創(chuàng)建和對外暴露一個函數(shù) 返回state
export default (state = defaultState, action) => {
  return state;
}

store/index.js

// 1 引入 store生成器
import { createStore } from "redux";
// 2 引入reducer
import reducer from "./reducer";
// 3 創(chuàng)建和對外暴露store
export default createStore(reducer);

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// 1 引入 react-redux  負責將store和組件連接起來
import { Provider } from "react-redux";
// 1 引入 store
import store from "./store";

// 2 將App用 Provider 標簽包裹起來
// 2 將store通過屬性的方式傳遞到App組件上
ReactDOM.render(<Provider store={store} ><App /></Provider>, document.getElementById('root'));

App.js

import React, { Component } from 'react';
// 1 引入 react-redux 中 鏈接 組件和store的對象 connect
import { connect } from "react-redux";

class App extends Component {
  render() {
    return (
      // 4 使用store中的數(shù)據(jù) 
      <div className="App">
        {this.props.num}
        <hr />
        <button> + </button>
        <button> - </button>
      </div>
    );
  }
}

// 2 將 store中的數(shù)據(jù)傳遞到 App的props上
const mapStateToProps = (state) => {
  return {
    num: state.num
  }
}


// 3 用 connect 將store中的數(shù)據(jù)通過props的方式傳遞到App上
export default connect(mapStateToProps, null)(App)

思考

以上代碼凌那,“action去了哪里了呢兼雄? ”
答:沒有操作數(shù)據(jù)的行為,當然么有action了

抽離組件帽蝶,綁定事件

編輯App.js文件
將 兩個按鈕 抽離出來變成兩個組件赦肋,這才滿足組件共享數(shù)據(jù)的設計理念

// 加
class AddBtn extends Component {
  render() {
    return <button onClick={this.props.numAdd} >+</button>
  }
}
 
// 減
class SubstraBtn extends Component {
  render() {
    return <button onClick={this.props.numSubStra}>-</button>
  }
}

class App extends Component {
  render() {
    return (
      <div className="App">
        {this.props.num}
        <hr />
        {/* 使用組件 傳遞props */}
        <AddBtn {...this.props}></AddBtn>
        {/* 使用組件 傳遞props*/}
        <SubstraBtn {...this.props}></SubstraBtn>
      </div>
    );
  }
}

此時,我們發(fā)現(xiàn) 兩個組件上都綁定了點擊事件

<button onClick={this.props.numAdd} >+</button>
<button onClick={this.props.numSubStra}>-</button>

所以励稳,現(xiàn)在我們需要另外定義 兩個事件佃乘,在redux中就叫做行為 action
和 mapStateToProps同層級,創(chuàng)建 行為合集 mapDispatchToProps驹尼,并且把它傳入 connect的第二個參數(shù)內(nèi)趣避。

// 2 將行為action 鏈接到store和組件上
const mapDispatchToProps = (dispatch) => {
  return {
    // 點擊事件中的加
    numAdd: () => {
      // 創(chuàng)建一個action,負責將行為類型和數(shù)據(jù)交給reducer
      const action = {
        // type是一個自定義的字符串
        type: "NUM_ADD",
        value: 1
      };
      // 派發(fā)行為- 會將action 派發(fā)到  reducer中
      dispatch(action);
    },
    // 點擊事件中的減
    numSubStra: () => {
      const action = {
        type: "NUM_SUBSTRA",
        value: 1
      };
      dispatch(action);
     }
  }
}

// 3 用 connect 將store中的數(shù)據(jù)通過props的方式傳遞到App上
export default connect(mapStateToProps, mapDispatchToProps)(App)

編輯 reducer邏輯

// 1 定義默認數(shù)據(jù)新翎,后期可以從接口中獲取
const defaultState = {
  num: -1
};

// 2 創(chuàng)建和對外暴露一個函數(shù) 返回state
export default (state = defaultState, action) => {
  // 當 action被派發(fā)時(dispatch)程帕,會觸發(fā)
  if (action.type === "NUM_ADD") {
    // 復制一份舊的state
    let newState = Object.assign({}, state);
    newState.num += action.value;
    // 將新的state返回住练,即可觸發(fā)store的更新
    return newState;
  }
  if (action.type === "NUM_SUBSTRA") {
    // 復制一份舊的state
    let newState = Object.assign({}, state);
    newState.num -= action.value;
    return newState;
  }

  return state;
}

優(yōu)化手段

通過以上步驟,可以把redux的使用流程走完愁拭,但是在公司中澎羞,還會對以上的代碼進行優(yōu)化,存在以下優(yōu)化的步驟

  1. 將state中的數(shù)據(jù)修改為對象形式敛苇,因為數(shù)據(jù)一般不會這么簡單妆绞。
  2. 將action的type類型提取成常量的形式,避免手寫字符串出錯
  3. 將action的創(chuàng)建由字面量改為 action生成器來創(chuàng)建枫攀,方便后期代碼的維護和測試
  4. 拆分和合并reducer括饶,有時候,會根據(jù)不同的數(shù)據(jù)使用不同的reducer
  5. 添加異步action来涨,因為有時候我們的數(shù)據(jù)是從異步中獲取的不是同步的方式图焰。

將state中的數(shù)據(jù)修改為對象形式

編輯 reducer/index.js

const defaultState = {
  // 修改為對象形式
  payload: {
    num: -1
  }
};

export default (state = defaultState, action) => {
  if (action.type === "NUM_ADD") {
    let newState = Object.assign({}, state);
    // 修改為對象形式
    newState.payload.num += action.value;
    return newState;
  }
  if (action.type === "NUM_SUBSTRA") {
    let newState = Object.assign({}, state);
    // 修改為對象形式
    newState.payload.num -= action.value;
    return newState;
  }

  return state;
}

修改App.js中使用的state的代碼

const mapStateToProps = (state) => {
  return {
     {/* 修改為對象的形式 */}
    num: state.payload.num
  }
}

將action的type類型提取成常量的形式

新建文件 src/store/actionType/index.js

export const NUM_ADD = "NUM_ADD";
export const NUM_SUBSTRA = "NUM_SUBSTRA";

修改 使用到了 NUM_ADD的文件
編輯 src/store/reducer/index.js

// 1 導入 type常量
import { NUM_ADD, NUM_SUBSTRA } from "../actionType";

export default (state = defaultState, action) => {
  // 2 修改為常量的方式
  if (action.type === NUM_ADD) {
    ......
  }

  return state;
}

編輯 src/App.js

// 1 導入 type 常量
import { NUM_ADD, NUM_SUBSTRA } from "./store/actionType";

const mapDispatchToProps = (dispatch) => {
  return {
    numAdd: () => {
      const action = {
        // 2  使用 type常量
        type: NUM_ADD,
        value: 1
      };
      dispatch(action);
    }
}

使用action生成器來創(chuàng)建action

新建文件 src/store/actionCreator/index.js

import {  NUM_ADD,NUM_SUBSTRA} from "../actionType";
export const numAdd = () => ({
  type: NUM_ADD,
  value: 1
})
export const numSubstra = () => ({
  type: NUM_SUBSTRA,
  value: 1
})

修改 App.js

// 1 導入action
import { numAdd, numSubstra } from "./store/actionCreator";

const mapDispatchToProps = (dispatch) => {
  return {
    numAdd: () => {
      // 2 修改為 生成器生成的action
      dispatch(numAdd());
    }
  }
}

拆分和合并reducer

當需要共享的數(shù)據(jù)足夠多時,一般會拆分多個reducer方便管理
如 拆分成兩個 reducer 一個是操作 nums的蹦掐,一個是操作水果的技羔。

  1. 編輯 actionType/index.js
export const NUM_ADD = "NUM_ADD";
export const NUM_SUBSTRA = "NUM_SUBSTRA";
// 新增 增加 蘋果action type
export const APPLE_NUM_ADD = "APPLE_NUM_ADD";
// 新增 減少 蘋果action type
export const APPLE_NUM_SUBSTRA = "APPLE_NUM_SUBSTRA";
  1. 編輯 actionCreator/index.js
// 新增 添加蘋果 action 
export const appleNumAdd = () => ({
  type: APPLE_NUM_ADD,
  value: 1
})
// 新增 減少蘋果 action
export const appleNumSubstra = () => ({
  type: APPLE_NUM_SUBSTRA,
  value: 1
})
  1. 新建文件 reducer/numReducer.js

將 以前 reducer/index.js 全部復制過去即可

import { NUM_ADD, NUM_SUBSTRA } from "../actionType";
const defaultState = {
  payload: {
    num: -1
  }
};
export default (state = defaultState, action) => {
  if (action.type === NUM_ADD) {
    let newState = Object.assign({}, state);
    newState.payload.num += action.value;
    return newState;
  }
  if (action.type === NUM_SUBSTRA) {
    let newState = Object.assign({}, state);
    newState.payload.num -= action.value;
    return newState;
  }
  return state;
}

4.新建文件 reducer/fruitReducer.js

import { APPLE_NUM_ADD, APPLE_NUM_SUBSTRA } from "../actionType";
const defaultState = {
  payload: {
    appleNum: 110
  }
};
export default (state = defaultState, action) => {
  if (action.type === APPLE_NUM_ADD) {
    let newState = Object.assign({}, state);
    newState.payload.appleNum += action.value;
    return newState;
  }
  if (action.type === APPLE_NUM_SUBSTRA) {
    let newState = Object.assign({}, state);
    newState.payload.appleNum -= action.value;
    return newState;
  }
  return state;
}
  1. 編輯 reducer/index.js 用來合并 兩個reducer fruitReducernumReducer
// 1 引入 合并reducer的對象
import { combineReducers } from "redux";
import fruitReducer from "./fruitReducer";
import numReducer from "./numReducer";
// 2 對象的形式傳入 要合并的reducer
const rootReducer = combineReducers({ numReducer, fruitReducer });
export default rootReducer;
  1. 修改App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
// 1 多導入兩個action appleNumAdd 和 appleNumSubstra
import { numAdd, numSubstra, appleNumAdd, appleNumSubstra } from "./store/actionCreator";
class AddNumBtn extends Component {
  render() {
    return <button onClick={this.props.numAdd} >+</button>
  }
}

class SubstraNumBtn extends Component {
  render() {
    return <button onClick={this.props.numSubStra}>-</button>
  }
}
// 2 新增的組件
class AddFruitBtn extends Component {
  render() {
    // 2.1 新綁定的事件
    return <button onClick={this.props.appleNumAdd} >+</button>
  }
}
// 2 新增的組件
class SubstraFruitBtn extends Component {
  render() {
    // 2.1 新綁定的事件
    return <button onClick={this.props.appleNumSubStra}>-</button>
  }
}

class App extends Component {
  render() {
    // 3 修改過的頁面代碼
    return (
      <div className="App">
      數(shù)量  {this.props.num}
        <hr />
        <AddNumBtn {...this.props}></AddNumBtn>
        <SubstraNumBtn {...this.props}></SubstraNumBtn>
        <hr />
      水果數(shù)量  {this.props.appleNum}
        <br />
        {/* 3.1 引入新組件 */}
        <AddFruitBtn {...this.props}></AddFruitBtn>
        {/* 3.1 引入新組件 */}
        <SubstraFruitBtn {...this.props}></SubstraFruitBtn>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  // 4 修改數(shù)據(jù)的獲取方法
  return {
    num: state.numReducer.payload.num,
    appleNum: state.fruitReducer.payload.appleNum
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    numAdd: () => {
      dispatch(numAdd());
    },
    numSubStra: () => {
      dispatch(numSubstra());
    },
    // 5 新增的action
    appleNumAdd: () => {
      dispatch(appleNumAdd());
    },
    // 5 新增的action
    appleNumSubStra: () => {
      dispatch(appleNumSubstra());
    },
  }
}

// 3 用 connect 將store中的數(shù)據(jù)通過props的方式傳遞到App上
export default connect(mapStateToProps, mapDispatchToProps)(App)
圖示.png

添加異步action redux-thunk

想象一下涩惑,我們對數(shù)據(jù)庫進行查詢碳柱,編輯和刪除,其實都是異步操作∪姓ィ現(xiàn)在社裆,讓我們的應用支持異步action操作拙绊。

  1. 安裝依賴 redux-thunk
npm install redux-thunk --save
  1. 修改store/index.js
// 1 引入 redux的中間件連接器
import { createStore, applyMiddleware } from "redux";
import reducer from "./reducer";
// 1 引入 redux-thunk
import reduxThunk from "redux-thunk";
// 2 使用中間件連接器將redux-thunk傳入 store構(gòu)造器
export default createStore(reducer, applyMiddleware(reduxThunk));
  1. 修改 actionCreator/index.js
// 1 修改 減少蘋果的action 為異步的形式
export const appleNumSubstra = () => {
   // 2 返回一個函數(shù)
  return (dispatch) => {
    // 3 開啟異步  后期將 setTimeout 替換成異步的方式即可
    setTimeout(() => {
      const action = {
        type: APPLE_NUM_SUBSTRA,
        value: 1
      };
        // 4 開啟派發(fā)
      dispatch(action);
    }, 2000);
  }
}

React的生命周期
React路由
閉包,原型鏈泳秀,繼承标沪,AJAX請求步驟等javaScript基礎

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市嗜傅,隨后出現(xiàn)的幾起案子金句,更是在濱河造成了極大的恐慌,老刑警劉巖吕嘀,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件违寞,死亡現(xiàn)場離奇詭異,居然都是意外死亡币他,警方通過查閱死者的電腦和手機坞靶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝴悉,“玉大人,你說我怎么就攤上這事拍冠∧蛘猓” “怎么了簇抵?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵叨橱,是天一觀的道長伙菜。 經(jīng)常有香客問我轩缤,道長,這世上最難降的妖魔是什么贩绕? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任火的,我火速辦了婚禮,結(jié)果婚禮上淑倾,老公的妹妹穿的比我還像新娘馏鹤。我一直安慰自己,他們只是感情好踊淳,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布假瞬。 她就那樣靜靜地躺著,像睡著了一般迂尝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剪芥,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天垄开,我揣著相機與錄音,去河邊找鬼税肪。 笑死溉躲,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的益兄。 我是一名探鬼主播锻梳,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼净捅!你這毒婦竟也來了疑枯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蛔六,失蹤者是張志新(化名)和其女友劉穎荆永,沒想到半個月后废亭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡具钥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年豆村,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骂删。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡掌动,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宁玫,到底是詐尸還是另有隱情坏匪,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布撬统,位于F島的核電站适滓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏恋追。R本人自食惡果不足惜凭迹,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苦囱。 院中可真熱鬧嗅绸,春花似錦、人聲如沸撕彤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羹铅。三九已至蚀狰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間职员,已是汗流浹背麻蹋。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留焊切,地道東北人扮授。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像专肪,于是被迫代替她去往敵國和親刹勃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351