跳到主要内容

Redux简单记录

· 阅读需 6 分钟
Jason Rong
前端糕手

Redux使用有感

Redux简述

Redux是一个全局状态管理工具,与pinia不同,它并不依托与某个框架绑定使用。当然很多基于React搭建的项目都使用Redux作为全局状态管理工具。本文涉及的示例也是基于React的。 Redux的特点:

  • 不可变更新,即更新数据的方式永远是重新赋一个全新的值,而不是直接修改旧值。
  • 通过所谓的action函数去dispatch我们配置的全局storestore里的reducer函数去更新数据(actionreducer存在绑定关系 ---Toolkit)。
  • 纯函数,即更新数据的函数必须是纯函数,即函数的返回值只依赖于函数的输入值,而不依赖于函数的外部状态。
  • 单向数据流,即数据的流向是单向的,只能从action流向reducer

本文主要讲述Toolkit的实现模板,这也是Redux官方目前所推崇的配置方式

Tips⭐⭐: Redux Toolkitreducer函数中使用immer库去让你可以通过直接修改的方式更新数据,而不是重新返回一个新值。但需要注意的是immer只是通过proxy去帮我们去做返回新值的操作,本质上reducer函数永远接受一个新值,而不是直接修改旧值。

Redux的构成

通过示例去理解Redux的构成,帮助我们去理解Redux的工作原理以及所提到的名词。

首先从根文件出发

import { configureStore } from '@reduxjs/toolkit';
import postsReducer from './features/postSlice';
import usersReducer from './features/usersSlice';
import notificationsReducer from './features/notificationsSlice';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';

const store = configureStore({
reducer: {
posts: postsReducer,
users: usersReducer,
notifications: notificationsReducer,
},
});
export default store;
export type RootState = ReturnType<typeof store.getState>;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch: () => typeof store.dispatch = useDispatch;
  • configureStore帮助我们去创建了一个store对象,这是全局且唯一的根对象。

  • configureStore接受一个对象参数配置去创建store对象,其中reducer这个属性可以传入我们创建的Reducer片段。或者换个理解方式,假设项目的全局状态分为三大板块,分别是postsusernotice,我们最好对他们分开进行处理。这样我们就分别给它们创建一个Reducer片段,各自的Reducer片段去处理各自的状态。(Reducer后续会提到的,稍安勿躁)。

  • 默认导出store,这个store对象就是我们全局唯一的根对象。后续我们所有的全局数据操作都会在这个store对象上进行。

后面的还有三行代码是自定义我们的SelectorDispatch触发器并提供类型推导,帮助我们在项目中处理全局状态时提供类型提示和约束---(JS可无视)

Reducer片段

const notificationsSlice = createSlice({
name: 'notifications',
initialState: [] ,
reducers: {
allNotificationsRead(state) {
state.forEach((notification) => {
notification.read = true;
});
},
},
});
export default notificationsSlice.reducer;
export const { allNotificationsRead } = notificationsSlice.actions;
export const selectAllNotifications = (state: RootState) => state.notifications;
  • createSlice创建了一个Reducer片段,它接受一个对象参数,其中name属性是我们的Reducer片段的名称,initialState属性是我们的Reducer片段的初始状态。

  • reducers属性则是我们的Reducer片段的reducer函数列表,它接受一个对象参数,其中的键值就是我们的reducer函数,键名就是我们的reducer函数的名称。(这里我使用ES6的简写形式)createSlice会自动生成与与reducer同名的action构造函数,所以下面我们可以具名导出与之同名的action构造函数

  • 默认导出我们的Reducer片段,这个Reducer片段将传入我们的根store并通过state.notifications访问。

  • 具名导出我们Recducer片段的action构造函数(allNotificationsRead),这些action构造函数接受参数返回一个action对象从而通过dispatch去触发数据操作。

  • 同时我们导出了一个selector函数(selectAllNotifications),通过useSelector触发并返回我们Reducer片段的当前state(当前片段state位于store.notifications)。

使用示例(片段)

import { useAppSelector, useAppDispatch } from '@/store';
import { selectAllUsers } from '@/store/features/usersSlice';
import { selectAllNotifications, allNotificationsRead } from '@/store/features/notificationsSlice';
import { useLayoutEffect } from 'react';
export default function NotificationsList() {
const notifications = useAppSelector(selectAllNotifications);
const users = useAppSelector(selectAllUsers);
const dispatch = useAppDispatch();
useLayoutEffect(()=>{
dispatch(allNotificationsRead())
})
}

useAppSelector是我自定义的useSelector仅提供类型推导支持--可看作useSelector useAppDispatch是我自定义的useDispatch仅提供类型推导支持--可看作useDispatch

  • useAppSelector触发我们的selector函数,并返回当前state的数据。通过我们具名导出的selector (selectAllNotificationsselectAllUsers)函数去获取我们的notificationsuser的当前state

  • useAppDispatch返回store.dispatch,并传入具名导出的action构造函数(allNotificationsRead)所返回的action对象去触发Reducer

...关于涉及异步操作的部分,后续再补充。