redux篇

# 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)稱作狀態(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ù),約定俗稱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

圖一

download.png

React 某個(gè)組件分發(fā)(Dispatch)一個(gè)指令更新Store

容器訂閱(Subscribe)狀態(tài)(State)更新后,重新渲染組件

圖二

download (1).png

視圖(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 加上名稱,方便 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       # 用戶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篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容