Redux、 React-Redux 、Redux/Toolkit 都是什么

学习了 React 就会提到 Redux / React Redux / Redux Toolkit 这些东西?它们到底是什么呢?又有什么关系呢?带着这些问题,我们一起开始学习吧。

PS: 本文尽量只使用核心代码解释 Redux、 React-Redux 、Redux/Toolkit 的用法,梳理核心用法和概念。而不是琳琅满目的概念和用法,让学习的人害怕。

不可变性

先看一段特别简单的 js 代码:

js 复制代码
const Bob = {
  firstName: "Bob",
  lastName: "Loblaw",
}
function giveAwesomePowers(person) {
  person.specialPower = "invisibility";
  return person;
}
giveAwesomePowers(Bob)
console.log(Bob.specialPower);

我们知道 giveAwesomePowers 函数会修改 Bob 的属性,一个变量被传给一个函数竟然修改了自己的属性,这会引起代码混乱,破坏了 纯函数 的概念,函数应该像数学公式一样,传入了一个参数,返回一个结果。不应该产生其他影响(副作用),不应该影响(修改了)全局变量 Bob。

这就是 不可变性,怎么优化上面的函数呢?如下:

js 复制代码
function giveAwesomePowers(person) {
  let newPerson = Object.assign({}, person, {
    specialPower: 'invisibility'
  })
  return newPerson;
}

这样就保持了 不可变性 ,一个函数的执行,并没有对其他变量造成"意料之外"的影响。为什么要讲不可变性呢?因为 Redux 的原则就是 不可变性,函数对传入的数据不直接进行修改,需要复制一份,然后修改复制的数据,并返回复制的数据。

# Immutability in React and Redux: The Complete Guide
# A Visual Guide to References in JavaScript

Redux

# 来自 Redux 官网的核心示例:

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <title>Redux basic example</title>
</head>
<body>
  <div>
    <p>
      Clicked: <span id="value">0</span> times
      <button id="increment">+</button>
      <button id="decrement">-</button>
      <button id="incrementIfOdd">Increment if odd</button>
      <button id="incrementAsync">Increment async</button>
    </p>
  </div>
  <script type="module">
    import { createStore } from "https://unpkg.com/redux@latest/dist/redux.browser.mjs";

    const initialState = {
      value: 0
    };

    function counterReducer(state = initialState, action) {
      switch (action.type) {
        case "counter/incremented":
          return { ...state, value: state.value + 1 };
        case "counter/decremented":
          return { ...state, value: state.value - 1 };
        default:
          return state;
      }
    }

    const store = createStore(counterReducer);

    const valueEl = document.getElementById("value");

    function render() {
      const state = store.getState();
      valueEl.innerHTML = state.value.toString();
    }

    render();

    store.subscribe(render);

    document
      .getElementById("increment")
      .addEventListener("click", function () {
        store.dispatch({ type: "counter/incremented" });
      });

    document
      .getElementById("decrement")
      .addEventListener("click", function () {
        store.dispatch({ type: "counter/decremented" });
      });

    document
      .getElementById("incrementIfOdd")
      .addEventListener("click", function () {
        if (store.getState().value % 2 !== 0) {
          store.dispatch({ type: "counter/incremented" });
        }
      });

    document
      .getElementById("incrementAsync")
      .addEventListener("click", function () {
        setTimeout(function () {
          store.dispatch({ type: "counter/incremented" });
        }, 1000);
      });
  </script>
</body>
</html>

可以看到 Redux 是一个独立的管理数据状态的库,并没有和 React 锁死的。最常见的用法是使用 React 和 React Native,但也有适用于 Angular、Angular 2、Vue、Mithril 等的绑定。Redux 只是提供了一种订阅机制,任何其他代码都可以使用它。

从上面基础用法我们可以看到 Redux 的一些使用的概念:

Store

当前 Redux 应用的 状态 存在于一个名为 store 的对象中。

store 是通过传入一个 reducer 来创建的,并且有一个名为 getState 的方法,它返回当前状态值:

js 复制代码
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}

State

通常将 Store 中存的数据状态成为 State, 可以通过 Store 的 getState 方法获取到。

Actions

action 是一个具有 type 字段的普通 JavaScript 对象。你可以将 action 视为描述应用程序中发生了什么的事件.

上面例子中的 { type: "counter/incremented" } 就是对应的 action 的概念。对 Store 里状态会进行不同的操作,对这些操作进行统一的命名和管理。

Reducers

reducer 是一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态。函数签名是:(state, action) => newState你可以将 reducer 视为一个事件监听器,它根据接收到的 action(事件)类型处理事件。

对应示例代码中 function counterReducer(state = initialState, action) {} 函数,根据 action 类型对 Store 中的状态数据 state 进行处理,并返回一个 newState。

Dispatch

Redux store 有一个方法叫 dispatch更新 state 的唯一方法是调用 store.dispatch() 并传入一个 action 对象 。 store 将执行所有 reducer 函数并计算出更新后的 state,调用 getState() 可以获取新 state。

# Redux 深入浅出,第二节:概念与数据流

React-Redux

初始化项目

上面是一个独立使用 Redux 的例子,例子中数据更新逻辑在 render() 函数中,以及按钮的操作都是我们自己写的,在 React 中,可以利用 React-Redux 库来帮我们完成数据更新的逻辑,以及和 React 和结合绑定。使用如下:

lua 复制代码
npx create-react-app count-demo
npm install react-redux --save
npm install redux --save

action/reducer/store 代码都是使用的 Redux 的方法,没有什么好说的,我们看下组件 CountNum 中是怎么获取 Store 中的 state,以及组件 CountButton 怎么出发 action 修改 state。

CountNum

jsx 复制代码
// CountNum
import React from 'react';
import { connect } from 'react-redux';
function CountNum(props) {
  return (
    <div>{props.count}</div>
  )
}
const mapStateToProps = state => {
  return state
}
export default connect(mapStateToProps)(CountNum)

CountButton

jsx 复制代码
// CountButton.jsx
import React from 'react';
import { connect } from 'react-redux';
import { addAction, reduceAction } from '../react-redux/actions';
function CountButton(props) {
  const addTen = ()=> {
    props.sendAdd(10)
  }
  const reduceTwo =()=> {
    props.sendReduce(2)
  }
  return (
    <>
      <button onClick={addTen}> +10</button>
      <button onClick={reduceTwo}> -2 </button>
    </>
  )
}
const mapDispatchToProps = dispatch=> {
  return {
    sendAdd: (num)=> {
      dispatch(addAction(num))
    },
    sendReduce: (num)=> {
      dispatch(reduceAction(num))
    }
  }
}

export default connect(null, mapDispatchToProps)(CountButton)

connect

连接 React 组件和 Redux store 的关键函数是 connect,语法如下:

jsx 复制代码
import { connect } from 'react-redux';
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(YourComponent);
  • mapStateToProps(state, ownProps?): 这是一个函数,它接收整个store的状态state作为第一个参数,可选地接收组件自身的propsownProps作为第二个参数,然后返回一个对象,该对象中的每个键值对都会被合并到组件的props中。这样,你的组件就可以访问到store中的数据了。

  • mapDispatchToProps(dispatch, ownProps?): 这也是一个函数,但通常你会看到它被省略或以另一种形式定义。它的目的是将action creators绑定到dispatch方法,使得组件可以直接调用这些action creator函数来触发state的更新。你也可以直接传递一个对象,其中的函数会被自动dispatch。

不可变性

Redux 重要一点是 不可变性,在 reducer 中,并没有直接操作 state,而是重新返回了一个新的对象:

js 复制代码
// reducer/index.js
const initialState = {
  count: 0
}
export const reducer = (state = initialState, action)=> {
  switch (action.type) {
    case 'ADD_NUM':
      return {
        ...state,
        count: state.count + action.payload.num
      }
    case 'REDUCE_NUM':
      return {
        ...state,
        count: state.count - action.payload.num
      }
    default:
      return state
  }
}

# git 源码
# 最容易理解的react-redux 入门实战讲解
# React-Redux 入门讲解实战

Redux/Toolkit

Redux/Toolkit 最初发布于2019年。它是Redux团队官方推荐的一套工具集,旨在简化Redux的开发过程,减少样板代码,并提供最佳实践。随着Redux的发展,Redux/Toolkit逐渐成为了创建Redux应用的首选方法,特别是在Redux 4.2.0版本之后,其中的createStore方法被标记为已弃用,进一步推动了向Redux/Toolkit及其configureStore方法的过渡。来,我们看看 Redux/Toolkit 做了哪些简化,以及怎么使用的呢。

初始化项目

lua 复制代码
# Redux + Plain JS template
npx create-react-app my-app --template redux

# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript

Redux/Toolkit 模板里面把逻辑都写好了,我们直接看关键代码,在入口文件 index.js 中,还是通过 Provider 和 store 通过根节点注入到项目中:

js 复制代码
// src\index.js
import { store } from './app/store';

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Store

我们看下 Store 是怎么初始化的:

js 复制代码
// src\app\store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

@reduxjs/toolkit 中通过 configureStore 初始化 Store 的。

Slice

slice 是一个对象,有 actionsreducer属性:

jsx 复制代码
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { fetchCount } from './counterAPI';

const initialState = {
  value: 0,
  status: 'idle',
};

// 异步
export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount) => {
    const response = await fetchCount(amount);
    return response.data;
  }
);

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      // Redux Toolkit  内部集成了 Immer,可以直接修改参数 state
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export const selectCount = (state) => state.counter.value;

export default counterSlice.reducer;
  • 初始化 Store 时,可以传入 counterSlice.reducer
  • 通过 selectCount 获取 Store 中的数据
  • 通过 counterSlice.actions 返回 action
  • 对于异步请求,可以使用 createAsyncThunk

更多使用细节# Redux Toolkit: 概览

不可变性

Redux/Toolkit 内部使用了一个名为 Immer 的库。Immer 会跟踪所有更改,然后使用更改列表返回一个安全不可变更新的值,看起来就像手动编写了所有不可变更新逻辑一样。 所以不需要这样编写:

js 复制代码
function handwrittenReducer(state, action) {
  return {
    ...state,
    first: {
      ...state.first,
      second: {
        ...state.first.second,
        [action.someId]: {
          ...state.first.second[action.someId],
          fourth: action.someValue
        }
      }
    }
  }
}

可以这样写:

js 复制代码
function reducerWithImmer(state, action) {
  state.first.second[action.someId].fourth = action.someValue
}

# Redux 深入浅出,第 8 节:使用 Redux Toolkit 的现代 Redux

相关推荐
彩虹下面1 天前
手把手带你阅读vue2源码
前端·javascript·vue.js
华洛1 天前
经验贴:Agent实战落地踩坑六大经验教训,保姆教程。
前端·javascript·产品
luckyzlb1 天前
03-node.js & webpack
前端·webpack·node.js
左耳咚1 天前
如何解析 zip 文件
前端·javascript·面试
程序员小寒1 天前
前端高频面试题之Vue(初、中级篇)
前端·javascript·vue.js
陈辛chenxin1 天前
软件测试大赛Web测试赛道工程化ai提示词大全
前端·可用性测试·测试覆盖率
沿着路走到底1 天前
python 判断与循环
java·前端·python
Code知行合壹1 天前
AJAX和Promise
前端·ajax
大菠萝学姐1 天前
基于springboot的旅游攻略网站设计与实现
前端·javascript·vue.js·spring boot·后端·spring·旅游
心随雨下1 天前
TypeScript中extends与implements的区别
前端·javascript·typescript