Redux = Reducer + Flux
Redux 是 React中的狀態(tài)管理容器抖僵,可以理解為存放 公用數(shù)據(jù)的地方,提供一種更加規(guī)范和安全的方式去定義組件之間的公用數(shù)據(jù)(想象一下兄弟組件傳值的過程)缘揪。對于初學者來說耍群,它的使用并不簡單。
三大原則
- 單一數(shù)據(jù)源
- State 是只讀的
- 使用純函數(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
工作流
體驗步驟
- 安裝依賴
- 創(chuàng)建store
- 創(chuàng)建reducer
- 將store數(shù)據(jù)映射到組件中
- 組件觸發(fā)事件 創(chuàng)建action
- 將action派發(fā)到store
- store自己調(diào)用reducer
初體驗
實現(xiàn)目標
- 可以發(fā)請求加載數(shù)據(jù)
- 點擊
+
-
組件 會修改數(shù)據(jù)
安裝依賴
redux 是核心庫 react-redux是負責將react組件連接redux
npm install redux react-redux --save
新建redux配套文件
在src/store/目錄下新建 以下文件
- index.js store核心文件
- 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)化的步驟
- 將state中的數(shù)據(jù)修改為對象形式敛苇,因為數(shù)據(jù)一般不會這么簡單妆绞。
- 將action的type類型提取成常量的形式,避免手寫字符串出錯
- 將action的創(chuàng)建由字面量改為 action生成器來創(chuàng)建枫攀,方便后期代碼的維護和測試
- 拆分和合并reducer括饶,有時候,會根據(jù)不同的數(shù)據(jù)使用不同的reducer
- 添加異步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的蹦掐,一個是操作水果的技羔。
- 編輯
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";
- 編輯
actionCreator/index.js
// 新增 添加蘋果 action
export const appleNumAdd = () => ({
type: APPLE_NUM_ADD,
value: 1
})
// 新增 減少蘋果 action
export const appleNumSubstra = () => ({
type: APPLE_NUM_SUBSTRA,
value: 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;
}
- 編輯
reducer/index.js
用來合并 兩個reducer
fruitReducer
和numReducer
// 1 引入 合并reducer的對象
import { combineReducers } from "redux";
import fruitReducer from "./fruitReducer";
import numReducer from "./numReducer";
// 2 對象的形式傳入 要合并的reducer
const rootReducer = combineReducers({ numReducer, fruitReducer });
export default rootReducer;
- 修改
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)
添加異步action redux-thunk
想象一下涩惑,我們對數(shù)據(jù)庫進行查詢碳柱,編輯和刪除,其實都是異步操作∪姓ィ現(xiàn)在社裆,讓我們的應用支持異步action操作拙绊。
- 安裝依賴
redux-thunk
npm install redux-thunk --save
- 修改
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));
- 修改
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);
}
}