# hello
傳統(tǒng)的狀態(tài)管理
在jquery時(shí)代槽奕,往往把全局變量定義在代碼的最前面,使每一個(gè)頁(yè)面都能使用他
<body>
<div>
<button id="add">+ 1</button>
<span id="number"></span>
<button id="minus">- 1</button>
</div>
<script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
<script>
$(function () {
// 變量 注釋1
const myState = { number: 1 }
// 增加
$("#add").click(() => {
myState.number++; // 注釋2
render();
})
// 減少
$("#minus").click(() => {
myState.number--; // 注釋2
render();
})
// 渲染DOM
const render = () => $("#number").html(myState.number);
render();
});
</script>
</body>
在 React 中撒轮,我們把變量(注釋1)稱(chēng)作狀態(tài)(state)
而通過(guò) React 基礎(chǔ)的學(xué)習(xí)我們發(fā)現(xiàn), React 的狀態(tài)都是在組件內(nèi)部(局部),當(dāng)組件嵌套復(fù)雜時(shí)昙篙,很難管理狀態(tài),也就是說(shuō)诱咏,很難實(shí)現(xiàn)全局變量
這個(gè)時(shí)候苔可,市場(chǎng)上出現(xiàn)了很多優(yōu)秀的解決方案,比如Flux袋狞,Redux焚辅,React-Redux删咱,useContext+useReduce酣溃,dva等
Redux 是基于 Flux 的改良版,不僅僅服務(wù)于React昵时,任何前端框架都可以使用
Redux 思路
把狀態(tài)(上述代碼注釋1)早处,操作(上述代碼注釋2)融合成一個(gè)容器湾蔓,通過(guò)視圖分發(fā)指令去更新?tīng)顟B(tài),并通過(guò)訂閱更新視圖
狀態(tài):State
操作:Reduce
容器:Store
分發(fā):dispatch
指令:action
訂閱:subscribe
hello Redux
Redux 通過(guò) createStore 創(chuàng)建一個(gè)容器砌梆,createStore 需要參數(shù) Reduce 默责, Reduce 有兩個(gè)參數(shù),狀態(tài)和指令
Reduce 是一個(gè)純函數(shù)咸包,只能回返新?tīng)顟B(tài)桃序,不更新原狀態(tài)
dispatch 分發(fā)的必須是一個(gè)對(duì)象
分發(fā)后,會(huì)調(diào)用 Reduce烂瘫,而且會(huì)把 dispatch 的參數(shù)會(huì)作為 Reduce 的第二個(gè)參數(shù)
<body>
<div>
<button id="add">+ 1</button>
<span id="number"></span>
<button id="minus">- 1</button>
</div>
<script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
<script src="./redux.js"></script>
<script>
$(function () {
// 狀態(tài)
const myState = { number: 1 }
// 操作
const Reduce = (state = myState, action) => {
if (action.type == "增加") return { ...state, number: state.number + 1 }
else if (action.type == "減少") return { ...state, number: state.number - 1 }
else return state;
}
// 容器
const Store = Redux.createStore(Reduce);
// 訂閱
Store.subscribe(() => render());
// 增加
$("#add").click(() => Store.dispatch({ type: "增加" }));
// 減少
$("#minus").click(() => Store.dispatch({ type: "減少" }));
// 渲染DOM
const render = () => $("#number").html(Store.getState().number);
render();
});
</script>
</body>
指令重寫(xiě)
指令可以帶一個(gè)參數(shù)媒熊,約定俗稱(chēng)payload,而且type我們把它改成英文名顯得更專(zhuān)業(yè)些
// 操作
const Reduce = (state = myState, action) => {
if (action.type == "add") return { ...state, number: action.payload }
else if (action.type == "minus") return { ...state, number: action.payload }
else return state;
}
// 增加
$("#add").click(() => Store.dispatch({ type: "add", payload: Store.getState().number + 1 }));
// 減少
$("#minus").click(() => Store.dispatch({ type: "minus", payload: Store.getState().number - 1 }));
可以所有的指令用一個(gè)變量存儲(chǔ)(大寫(xiě)表示常量),那么再使用的過(guò)程中芦鳍,不容易出錯(cuò)嚷往,還可以復(fù)用
// 所有指令
const Actions = {
ADD: "add",
MINUS: "minus"
}
// 操作
const Reduce = (state = myState, action) => {
if (action.type == Actions.ADD) return { ...state, number: action.payload }
else if (action.type == Actions.MINUS) return { ...state, number: action.payload }
else return state;
}
// 增加
$("#add").click(() => Store.dispatch({ type: Actions.ADD, payload: Store.getState().number + 1 }));
// 減少
$("#minus").click(() => Store.dispatch({ type: Actions.MINUS, payload: Store.getState().number - 1 }));
為了更加貼合管網(wǎng)(深層裝X),我們可以在 dispatch 一個(gè)由 createAction 生成的指令
// 所有指令
const Actions = {
ADD: "add",
MINUS: "minus"
}
// 生成指令
const createAction = {
ADD(payload) {
return { type: Actions.ADD, payload }
},
MINUS(payload) {
return { type: Actions.MINUS, payload }
}
}
// 操作
const Reduce = (state = myState, action) => {
if (action.type == Actions.ADD) return { ...state, number: action.payload }
else if (action.type == Actions.MINUS) return { ...state, number: action.payload }
else return state;
}
// 增加
$("#add").click(() => Store.dispatch(createAction.ADD(Store.getState().number + 1)));
// 減少
$("#minus").click(() => Store.dispatch(createAction.ADD(Store.getState().number - 1)));
再把 Reduce 的if判斷改為更為高大上的 switch
OK 怜校,全部改完后的代碼如下
<body>
<div>
<button id="add">+ 1</button>
<span id="number"></span>
<button id="minus">- 1</button>
</div>
<script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
<script src="./redux.js"></script>
<script>
$(function () {
// 狀態(tài)
const myState = { number: 1 }
// 所有指令
const Actions = {
ADD: "add",
MINUS: "minus"
}
// 生成指令
const createAction = {
ADD(payload) {
return { type: Actions.ADD, payload }
},
MINUS(payload) {
return { type: Actions.MINUS, payload }
}
}
// 操作
const Reduce = (state = myState, action) => {
switch (action.type) {
case Actions.ADD:
return { ...state, number: action.payload };
case Actions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
// 容器
const Store = Redux.createStore(Reduce);
// 訂閱
Store.subscribe(() => render());
// 增加
$("#add").click(() => Store.dispatch(createAction.ADD(Store.getState().number + 1)));
// 減少
$("#minus").click(() => Store.dispatch(createAction.ADD(Store.getState().number - 1)));
// 渲染DOM
const render = () => $("#number").html(Store.getState().number);
render();
});
</script>
</body>
再讀 Redux
Redux 是 JavaScript 狀態(tài)容器间影,提供可預(yù)測(cè)化的狀態(tài)管理
我們把這句話分解一下:
狀態(tài)(State)
容器(Store)
可預(yù)測(cè)(Action)
狀態(tài)管理(Reduce)
# React 配合 Redux
安裝Redux
yarn add Redux
容器
import { createStore } from "redux"
// 狀態(tài)
const myState = { number: 1 }
// 所有指令
const Actions = {
ADD: "add",
MINUS: "minus"
}
// 生成指令
const createAction = {
ADD(payload) {
return { type: Actions.ADD, payload }
},
MINUS(payload) {
return { type: Actions.MINUS, payload }
}
}
// 操作
const Reduce = (state = myState, action) => {
switch (action.type) {
case Actions.ADD:
return { ...state, number: action.payload };
case Actions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
// 容器
const Store = createStore(Reduce);
export {
createAction, Store
}
App組件
import React, { useState } from 'react';
import { Store, createAction } from "./Store/index"
function App() {
const [number, setNumber] = useState(Store.getState().number);
// 訂閱狀態(tài)更新
Store.subscribe(() => setNumber(Store.getState().number));
const changeNumber = (props) => {
// 分發(fā)指令
if (props == `add`) Store.dispatch(createAction.ADD(number + 1));
else if (props == `minus`) Store.dispatch(createAction.MINUS(number - 1));
}
return (
<div>
<button onClick={() => changeNumber(`add`)}>+ 1</button>
<span>{number}</span>
<button onClick={() => changeNumber(`minus`)}>- 1</button>
</div>
)
}
export default App;
# 圖解 React + Redux
圖一
React 某個(gè)組件分發(fā)(Dispatch)一個(gè)指令更新Store
容器訂閱(Subscribe)狀態(tài)(State)更新后,重新渲染組件
圖二
視圖(View)把指令(Actions)分發(fā)(Dispatch)給容器(Store)
容器根據(jù)分發(fā)的指令可預(yù)測(cè)的管理(Redux)狀態(tài)(State)
容器偵聽(tīng)(Subscibe)了狀態(tài)(State)的變化茄茁,更新了視圖(View)
# 取消訂閱
每次在組件銷(xiāo)毀的時(shí)候魂贬,必須取消訂閱避免內(nèi)存泄露
其實(shí)在組件銷(xiāo)毀時(shí),不僅僅要取消訂閱裙顽,還要清空定時(shí)器付燥,DOM偵聽(tīng)等副作用
import React, { useState, useEffect } from 'react';
import { Store, createAction } from "./Store/index"
function App() {
const [number, setNumber] = useState(Store.getState().number);
// 訂閱狀態(tài)更新
const sub = Store.subscribe(() => setNumber(Store.getState().number));
useEffect(() => {
return () => {
sub();
}
});
const changeNumber = (props) => {
// 分發(fā)指令
if (props == `add`) Store.dispatch(createAction.ADD(number + 1));
else if (props == `minus`) Store.dispatch(createAction.MINUS(number - 1));
}
return (
<div>
<button onClick={() => changeNumber(`add`)}>+ 1</button>
<span>{number}</span>
<button onClick={() => changeNumber(`minus`)}>- 1</button>
</div>
)
}
export default App;
# reduce
介紹
當(dāng)頁(yè)面非常龐大的時(shí)候,如果狀態(tài)管理都寫(xiě)在一起非常不好維護(hù)愈犹,redux 有一個(gè) combineReducers 可以把狀態(tài)管理拆成小塊键科,從而使開(kāi)發(fā)維護(hù)更加方便
快速 demo
Store.js
import { createStore, combineReducers } from "redux"
// 狀態(tài)
const NumberState = { number: 1 }
const UserState = [{ name: "張三", age: 12 }]
// 所有指令
const NumberActions = {
ADD: "add",
MINUS: "minus"
}
const UserActions = {
INIT: "init",
ADD: "add",
UPDATE: "update"
}
// 生成指令
const createNumberAction = {
ADD(payload) {
return { type: NumberActions.ADD, payload }
},
MINUS(payload) {
return { type: NumberActions.MINUS, payload }
}
}
const createUserAction = {
INIT(payload) {
return { type: UserActions.INIT, payload }
},
ADD(payload) {
return { type: UserActions.ADD, payload }
},
UPDATE(payload) {
return { type: UserActions.UPDATE, payload }
}
}
// 操作
const NumberReduce = (state = NumberState, action) => {
switch (action.type) {
case NumberActions.ADD:
return { ...state, number: action.payload };
case NumberActions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
const UserReduce = (state = UserState, action) => {
switch (action.type) {
case NumberActions.ADD:
return { ...state, number: action.payload };
case NumberActions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
const Store = createStore(reduce);
export {
createNumberAction, Store
}
action , createAction漩怎, Reduce 都要寫(xiě)兩份勋颖,最后合成一份Store,并在 combineReducers 時(shí)勋锤,給不同的 redux 加上名稱(chēng)饭玲,方便 Store 調(diào)用
APP.js
import React, { useState, useEffect } from 'react';
import { Store, createNumberAction } from "./Store/index"
function App() {
const [number, setNumber] = useState(Store.getState().Num.number);
// 訂閱狀態(tài)更新
const sub = Store.subscribe(() => setNumber(Store.getState().Num.number));
useEffect(() => {
return () => {
sub();
}
});
const changeNumber = (props) => {
// 分發(fā)指令
if (props == `add`) Store.dispatch(createNumberAction.ADD(number + 1));
else if (props == `minus`) Store.dispatch(createNumberAction.MINUS(number - 1));
}
return (
<div>
<button onClick={() => changeNumber(`add`)}>+ 1</button>
<span>{number}</span>
<button onClick={() => changeNumber(`minus`)}>- 1</button>
</div>
)
}
export default App;
調(diào)用的時(shí)候,需要用 Store 調(diào)用 combineReducers 合并時(shí)取的別名
Redux 文件拆分
可以把不同的Redux拆分文件單獨(dú)提出
├── Store # 狀態(tài)管理容器
│ ├── numberReduce.js # 數(shù)字reduce
│ ├── userReduce.js # 用戶(hù)reduce
│ └── index.js
├── App.js # 根組件
./Store/numberReduce.js
// 狀態(tài)
const NumberState = { number: 1 }
// 所有指令
const NumberActions = {
ADD: "add",
MINUS: "minus"
}
// 生成指令
const createNumberAction = {
ADD(payload) {
return { type: NumberActions.ADD, payload }
},
MINUS(payload) {
return { type: NumberActions.MINUS, payload }
}
}
// 操作
const NumberReduce = (state = NumberState, action) => {
switch (action.type) {
case NumberActions.ADD:
return { ...state, number: action.payload };
case NumberActions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
export { createNumberAction, NumberReduce }
export default NumberReduce;
./Store/userReduce.js
// 狀態(tài)
const UserState = [{ name: "張三", age: 12 }]
// 所有指令
const UserActions = {
INIT: "init",
ADD: "add",
UPDATE: "update"
}
// 生成指令
const createUserAction = {
INIT(payload) {
return { type: UserActions.INIT, payload }
},
ADD(payload) {
return { type: UserActions.ADD, payload }
},
UPDATE(payload) {
return { type: UserActions.UPDATE, payload }
}
}
// 操作
const UserReduce = (state = UserState, action) => {
return UserState;
}
export { createUserAction, UserReduce }
export default UserReduce;
./Store/index.js
import { createStore, combineReducers } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
// 合并
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
export default createStore(reduce);
./App.js
import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import { createNumberAction } from "./Store/numberReduce"
function App() {
const [number, setNumber] = useState(Store.getState().Num.number);
// 訂閱狀態(tài)更新
const sub = Store.subscribe(() => setNumber(Store.getState().Num.number));
useEffect(() => {
return () => {
sub();
}
});
const changeNumber = (props) => {
// 分發(fā)指令
if (props == `add`) Store.dispatch(createNumberAction.ADD(number + 1));
else if (props == `minus`) Store.dispatch(createNumberAction.MINUS(number - 1));
}
return (
<div>
<button onClick={() => changeNumber(`add`)}>+ 1</button>
<span>{number}</span>
<button onClick={() => changeNumber(`minus`)}>- 1</button>
</div>
)
}
export default App;
這是以功能模塊拆分叁执,還可以以 Store 拆分
├── Store # 狀態(tài)管理容器
│ ├── action
│ │ ├── numberAction
│ │ ├── userAction
│ ├── reduce
│ │ ├── numberReduce
│ │ ├── userReduce
│ ├── state
│ │ ├── numberState
│ │ ├── userState
│ └── index.js
├── App.js # 根組件
# 配合請(qǐng)求更新?tīng)顟B(tài)
用 json-server 模擬數(shù)據(jù)
安裝
$ npm install -g json-server
數(shù)據(jù)模擬
{
"User": [
{
"id": "001",
"name": "Sherry",
"age": 24
},
{
"id": "002",
"name": "Addy",
"age": 24
},
{
"id": "003",
"name": "Jack",
"age": 24
},
{
"id": "004",
"name": "Rebeca",
"age": 24
}
]
}
啟動(dòng)服務(wù)
$ json-server --watch --port 3001 db.json
請(qǐng)求數(shù)據(jù)更新?tīng)顟B(tài)
為了方便測(cè)試茄厘,把 numberReducer 的功能去掉
App.js
import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import axios from "axios"
import { createUserAction } from "./Store/userReduce"
function App() {
const [user, setUser] = useState(Store.getState().User);
const sub = Store.subscribe(() => setUser(Store.getState().User));
useEffect(() => {
axios.get(`http://localhost:3001/User`).then(res => {
Store.dispatch(createUserAction.INIT(res.data));
})
return () => {
sub();
}
}, []);
return (
<>
{user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
</>
)
}
export default App;
userReduce 簡(jiǎn)化成一個(gè)指令
// 所有指令
const UserActions = {
INIT: "init"
}
// 生成指令
const createUserAction = {
INIT(payload) {
return { type: UserActions.INIT, payload }
}
}
// 操作
const UserReduce = (state = [], action) => {
if (action.type == UserActions.INIT) {
return action.payload;
}
return state;
}
export { createUserAction, UserReduce }
export default UserReduce;
此時(shí)就能配合請(qǐng)求更新?tīng)顟B(tài)了。如果需要指令異步執(zhí)行谈宛,可以使用redux中間件
中間件
redux 中間件是在 action 和 reduce環(huán)節(jié)中次哈,添加的功能
使用例子
import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import thunk from "redux-thunk"
// 合并
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
export default createStore(reduce, applyMiddleware(thunk));
# redux-thunk
介紹
redux-thunk 屬于 redux 的中間件,作用是可以讓 dispatch 返回的是一個(gè)函數(shù)吆录,那么就可以把異步的寫(xiě)法搬到 action 中
App.js
import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import axios from "axios"
import { createUserAction } from "./Store/userReduce"
function App() {
const [user, setUser] = useState(Store.getState().User);
const sub = Store.subscribe(() => setUser(Store.getState().User));
useEffect(() => {
Store.dispatch(createUserAction.ASYNCINIT());
return () => {
sub();
}
}, []);
return (
<>
{user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
</>
)
}
export default App;
Reduce.js
import axios from "axios"
// 所有指令
const UserActions = {
ASYNCINIT: "init"
}
// 生成指令
const createUserAction = {
ASYNCINIT() {
return (dispatch, getState) => {
axios.get(`http://localhost:3001/User`).then(res => {
dispatch({ type: UserActions.ASYNCINIT, payload: res.data });
})
}
}
}
// 操作
const UserReduce = (state = [], action) => {
if (action.type == UserActions.ASYNCINIT) {
return action.payload;
}
return state;
}
export { createUserAction, UserReduce }
export default UserReduce;
index.js
import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import thunk from "redux-thunk"
// 合并
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
export default createStore(reduce, applyMiddleware(thunk));
# redux-saga
介紹
redux-saga 和 redux-thunk 一樣窑滞,都是 redux 中,名氣非常高的中間件
dva 還是基于 redux-saga 的一個(gè)數(shù)據(jù)流方案
思路
容器分發(fā)一個(gè)指令恢筝,用 redux-saga 去偵聽(tīng)(其實(shí)reduce也偵聽(tīng)到了哀卫,只不過(guò)不處理事件),偵聽(tīng)到后滋恬,做一系列的異步或同步操作聊训,再用put發(fā)送一個(gè)指令抱究,再由reduce偵聽(tīng)并更新?tīng)顟B(tài)
注意:redux-saga 發(fā)送的指令不能和偵聽(tīng)的指令相同恢氯,否則會(huì)一直執(zhí)行
App.js
import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import { createUserAction } from "./Store/userReduce"
function App() {
const [user, setUser] = useState(Store.getState().User);
const sub = Store.subscribe(() => setUser(Store.getState().User));
useEffect(() => {
Store.dispatch(createUserAction.ASYNCINIT);
return () => {
sub();
}
}, []);
return (
<>
{user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
</>
)
}
export default App;
userReduce
// 所有指令
const UserActions = {
ASYNCINIT: "asyncinit",
INIT: "init"
}
// 生成指令
const createUserAction = {
ASYNCINIT: {
type: UserActions.ASYNCINIT
},
INIT(payload) {
return {
type: UserActions.INIT,
payload: payload
}
}
}
// 操作
const UserReduce = (state = [], action) => {
console.log(action);
if (action.type == UserActions.INIT) {
return action.payload || state;
}
return state;
}
export { createUserAction, UserReduce, UserActions }
export default UserReduce;
Store
import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import reduxSaga from "redux-saga"
import sagas from "./sagas"
const sage = reduxSaga();
// 合并
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
export default createStore(reduce, applyMiddleware(sage));
sage.run(sagas);
sagas
import { takeEvery, put } from "redux-saga/effects"
import { UserActions, createUserAction } from "./userReduce"
import axios from "axios"
function* mySaga() {
yield takeEvery(UserActions.ASYNCINIT, getList);
}
function* getList() {
const res = yield axios.get("http://localhost:3001/User");
yield put(createUserAction.INIT(res.data));
}
export default mySaga;
# redux-devtools-extension 調(diào)試工具
chrome 插件
地址: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
配置
$ npm install --save-dev redux-devtools-extension
配置
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(reducer, composeWithDevTools(
applyMiddleware(...middleware),
// other store enhancers if any
));
上一篇:React Hook篇