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

相关推荐
理想不理想v6 分钟前
vue经典前端面试题
前端·javascript·vue.js
不收藏找不到我7 分钟前
浏览器交互事件汇总
前端·交互
YBN娜21 分钟前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=21 分钟前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
minDuck25 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。1 小时前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼1 小时前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端