九、Redux介绍


  • 什么是状态管理
  • Redux 的核心思想
  • React-redux 介绍

什么是状态管理

所谓状态管理,指的是把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测

组件之间通常会有一些共享的状态,在 Vue 或者 React 中我们一般会将这部分状态提升至公共父组件的 props 中,由父组件来统一管理共享的状态,状态的改变也是由父组件执行并向下传递。这样会导致两个问题:

  • 需要将共享的状态提升至公共的父组件,若无公共的父组件,往往需要自行构造
  • 状态由父组件自上而下逐层传递,若组件层级过多,数据传递会变得很冗杂

此时,我们就需要一个统一的仓库来对组件状态进行管理,如下图:

Redux 的核心思想

早期的时候,React 官方提供了 FluxFlux 的特点如下:

  • 单向数据流。视图事件或者外部测试用例发出 Action ,经由 Dispatcher 派发给 StoreStore 会触发相应的方法更新数据、更新视图
  • Store 可以有多个
  • Store 不仅存放数据,还封装了处理数据的方法

2015 年的时候,Dan Abramov 推出的 Redux 席卷了整个 React 社区,Redux 本质就是在 Flux 上做了一些更新:

  • 单向数据流View 发出 Action (store.dispatch(action)),Store 调用 Reducer 计算出新的 state ,若 state 产生变化,则调用监听函数重新渲染 View (store.subscribe(render)
  • 单一数据源,只有一个 Store
  • state 是只读的,每次状态更新之后只能返回一个新的 state
  • 没有 Dispatcher ,而是在 Store 中集成了 dispatch 方法,store.dispatch() View 发出 Action 的唯一途径
  • 支持使用中间件(Middleware )管理异步数据流

Redux 官网:https://redux.js.org/

Redux 数据流:


Redux 示例:ToDoList

在浏览器中使用 redux 扩展工具,首先需要打开谷歌浏览器的扩展应用商店,下载 redux devtools


接下来需要在创建 store 的地方,添加如下的代码:

export const store = createStore(
  todoReducer,
  + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

详细可以参阅:https://github.com/zalmoxisus/redux-devtools-extension#usage

React-redux 介绍

Redux 是一个独立的第三方库,之后 React 官方在 Redux 的基础上推出了 _React-redux_:https://react-redux.js.org/

最新版的 React-redux ,已经全面拥抱了 Hooks ,内置了诸如:

  • useSelector
  • useDispatch
  • useStore

一类的 Hook ,我们只要掌握这一类 Hook ,就可以轻松上手。

另外,Redux 官方还推出了 Redux Toolkit ,来简化整个 Redux 的使用。官方文档:https://redux-toolkit.js.org/

因此现在在 React 应用中,状态管理库的使用一般都是 React-redux + Redux Toolkit

React-redux 示例:ToDoList

和后端进行交互

一般来讲,当数据发生变化时,不仅仅是前端的状态库要更新数据,服务器端也要对应的对数据进行更新,此时的更新流程如下:


和后端交互示例:学生管理系统

基本使用流程:

1、安装redux

npm i redux

2、创建一个redux文件夹


3、store.js:引入仓库,引用reducers,启用仓库

// 创建仓库
import { createStore } from "redux";
// 引入 reducer
import { todoReducer } from "./reducers"
// 需要你传入一个 reducer(纯函数,用于计算最新的状态)
export const store = createStore(
  todoReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

4、引入的reducers.js里面定义了仓库的状态也就是数据,以及处理方法todoReducer,更新状态,todoReducer是纯函数

// reducer,用于计算最新的状态

import { ADD,DEL,CHANGE } from "./actionType";

// 仓库一开始默认的数据
let defaultState = {
  list: [
    {
      content: "学习 React",
      status: false,
    },
    {
      content: "复习 Vue",
      status: false,
    },
    {
      content: "玩游戏",
      status: false,
    },
    {
      content: "听歌",
      status: false,
    },
  ],
};

/**
 * 通过这个纯函数我们会计算出最新的状态
 * state : 仓库数据,每次会传入上一次的仓库数据
 * action : 描述对象 {type : ADD, data : "学习 redux"}
 * 描述对象描述了我要做什么,以及带来的额外数据
 */
export function todoReducer(state = defaultState, action) {
  // 有了描述对象后,我就可以根据旧状态计算出新的状态并返回
  switch (action.type) {
    case ADD: {
      // 新增的操作
      const arr = [...state.list];
      arr.push({
        content: action.data,
        status: false,
      });
      return { list: arr };
    }
    case DEL : {
       const arr = [...state.list];
       arr.splice(action.data, 1);
       return { list: arr };
    }
    case CHANGE : {
        const arr = [...state.list];
        arr[action.data].status = !arr[action.data].status;
        return { list: arr };
    }
    default: return state;
  }
}

5、actionType定义了描述action对象的类型,这个不重要

// 描述了 action 对象的类型

export const ADD = "ADD"; // 新增
export const DEL = "DEL"; // 删除
export const CHANGE = "CHANGE"; // 修改状态

6、action.js定义的是每一个action对象,每一个action的返回值,就是作为我们在todoReducer函数调用时的第二个参数

// 生产 action 对象的函数,我们一般称之为 actionCreator

import { ADD, DEL, CHANGE } from "./actionType";

export const addListAction = newItem => ({
    type: ADD,
    data : newItem
})

export const delListAction = index => ({
    type: DEL,
    data : index
})

export const changeAction = index => ({
    type: CHANGE,
    data : index
})

7、在index.js中引入并挂在,注意store.subscribe(render)这句话,所谓的订阅就是当仓库发生改变时渲染整个页面(个人觉得这里有渲染问题,性能不高)

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

// 引入仓库
import { store } from "./redux/store";

const root = ReactDOM.createRoot(document.getElementById('root'));

function render() {
  root.render(
    <App store={store} />
  );
}
render();


// 让 redux 和 react 建立关联
// subscribe 叫做订阅,在仓库的状态发生改变的时候
store.subscribe(render);

8、作为props传入到app.js,同时传入到子组件

import Input from "./components/Input";
import List from "./components/List";

import "./css/App.css"

function App(props) {

  return (
    // 最外层容器
    <div className="container">
      <h1 className="lead" style={{
        marginBottom : "30px"
      }}>待办事项</h1>
      <Input store={props.store}/>
      <List store={props.store}/>
    </div>
  )

}

export default App;

9、在input.jsx中使用,引入action.js中定义的action,然后通过props.store.dispatch触发,触发后会被派发到reducer里面,执行todoReducer方法,从而更新state的状态

import { useState } from "react";
import { addListAction } from "../redux/actions";

function Input(props) {
  const [value, setValue] = useState("");

  function clickHandle() {
    // 将用户填写的内容提交到仓库
    // dispatch 方法会派发一个 action 对象到 reducer 里面
    // addListAction(value) ===> { type : ADD, data : value}
    // 这个就是我们的 action 描述对象,该对象会被 dispatch(派发)到 reducer 里面
    props.store.dispatch(addListAction(value));
    setValue("");
  }

  return (
    <div className="form-inline">
      <input
        type="text"
        className="form-control"
        placeholder="请输入待办事项"
        style={{
          marginRight: 10,
        }}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <button className="btn btn-primary" onClick={clickHandle}>
        提交
      </button>
    </div>
  );
}

export default Input;

10、list.jsx一样的东西,需要注意的是,在 redux,通过 store.getState() 来获取仓库数据

import React from "react";
import { delListAction, changeAction } from "../redux/actions";

function List(props) {
  // 在 redux,通过 store.getState() 来获取仓库数据
  const lis = props.store.getState().list.map((item, index) => {
    return (
      <li key={index} className="text-primary">
        <span
          onClick={() => props.store.dispatch(changeAction(index))}
          className={["item", item.status ? "completed" : ""].join(" ")}
        >
          {item.content}
        </span>
        <button
          type="button"
          className="close"
          onClick={() => props.store.dispatch(delListAction(index))}
        >
          &times;
        </button>
      </li>
    );
  });

  return (
    <div>
      <ul style={{ marginTop: 20 }}>{lis}</ul>
    </div>
  );
}

export default List;

文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
  目录