Open li-jia-nan opened 1 year ago
在一般的React项目中,大多数情况会选择Redux或者React-Redux作为我们的状态管理工具,在使用Vuex的时候,我们可以在mutations里写同步操作,也可以在actions里写异步操作,然而Redux不同于Vuex,Redux本身是不支持异步的,如果需要处理异步操作,我们还要额外安装redux-thunk或者redux-saga这样的中间件,显得很繁琐。
Redux
React-Redux
Vuex
mutations
actions
redux-thunk
redux-saga
如果你的React项目中使用了react hook、redux、redux-thunk、或者redux-saga,那么可能你需要用redux-toolkit(以下简称RTK)来优化你的项目结构,它可以让你的代码看起来更清爽。
react hook
redux
redux-toolkit
RTK
# 优化前 /counter constants.ts actions.ts reducer.ts saga.ts index.tsx # 优化后 /counter slice.ts index.tsx
RTK旨在帮助解决关于Redux的几个问题:
简单讲,配置Redux的流程太过复杂,完整需要编写actionTypes、actions、reducer、store等一系列函数,最后通过connect隐射到props里面供组件使用。而使用RTK,只需一个reducer即可。
reducer
那么RTK到底是什么呢?
我们打开GitHub,找到RTK的源码,可以在 toolkit/package.json目录中看到关于RTK用到的一些依赖:
关于这些依赖都代表什么呢?
当然了,RTK需要它才能工作,这里引入了redux,意味着如果我们的项目安装了RTK,就不需要重复安装redux;除此之外,如果大家观察的足够仔细,就会发现RTK的依赖并不包含react-redux,这意味着,RTK是一个独立的库,它不只是给react项目使用,如果你愿意,你可以在任何环境中使用它,比如Vue或者Angular,甚至jQuery中,或者原生js中都可以使用。
react-redux
react
Vue
Angular
jQuery
js
RTK自带了redux-thunk来处理异步逻辑,thunk在RTK中是默认开启的(在开发过程中,你可以手动关闭,如果你愿意,也可以安装redux-saga等其它异步处理的中间件)。
reselect
这也是一个比较流行的redux插件,它可以帮助我们在视图渲染的时候记住当前的状态,防止组件在不需要的时候被无意识的渲染,功能有点类似于shouldComponentDidUpdate,但他们并不是一个东西,这里作为RTK入门笔记,对该插件不做深入讲解。
shouldComponentDidUpdate
immer
最后一个immer是个非常有意思的插件,它允许我们把state的immutable特性转化为mutable,也就是说,我们在reducer函数中,可以直接修改state中的数据(我在第一次听到这个思路的时候,理智告诉我,这是不对的,因为这样就违反了reducer函数式编程的理念,redux是单向数据流的状态管理工具,数据是不可变的,我们不可以直接修改store的状态,而是通过返回新state,替换旧state来完成状态更新,所以我一开始接触到这个概念的时候,还是比较抵触的)。总而言之,我们可以选择使用immer来修改state,让数据变成mutable状态,也可以选择不使用immer,让数据保持immutable状态,这完全取决于你自己,大家可以自行决定。(这不正是React的灵活之处吗)
immutable
mutable
单向数据流
不可变的
新state
旧state
使用immer
不使用immer
在上文中,我们对immer做了简单的介绍,这里再单独拿出来讨论一下
immer到底是什么?在上文中我们提到,它是一个很有意思的插件,它允许我们把state的immutable特性转化为mutable,实际上,immer在底层是的核心实现是利用了ES6的proxy,在我们对状态进行修改的时候,proxy对象进行拦截,并且proxy按顺序替换上层对象,相当于自动帮你返回的新对象(所以其实还是immutable的,只不过写法上看起来是可直接修改state)
proxy
下面的例子是用immer和不用immer的区别:
// 不使用 immer,返回新状态,替换旧状态 reducers: { fetchStart(state) { return { ...state, loading: true }; }, fetchEnd(state) { return { ...state, loading: false }; }, fetchSuccess(state, action: PayloadAction<FetchType>) { return { ...state, data: action.data }; }, fetchFailure(state, action: PayloadAction<ErrorType>) { return { ...state, error: action.message }; }, }, // 使用 immer,无需返回新状态,直接修改原状态 reducers: { fetchStart(state) { state.loading = true; }, fetchEnd(state) { state.loading = false; }, fetchSuccess(state, action: PayloadAction<FetchType>) { state.data = action.data; }, fetchFailure(state, action: PayloadAction<ErrorType>) { state.error = action.message }, },
于是有同学会问了,在上面的例子中,使用immer和不使用immer的代码行数是一样的,也没体现出代码的简化,所以它的优点体现在哪里呢?
再看下面的例子:
// 不使用 immer,返回新状态,替换旧状态 reducers: { someReducer(state, action: PayloadAction<SourceData>) { return { ...state, first: { ...state.first, second: { ...state.first.second, third: { ...state.first.second.third, value: action.someValue, }, }, }, }; }, }, // 使用 immer,无需返回新状态,直接修改原状态 reducers: { someReducer(state, action: PayloadAction<SourceData>) { state.first.second.third.value = action.someValue; }, },
这是一个很典型的例子,如果我们的状态嵌套了很多层,并且需要修改的数据在很深层,这时immer的便利性就体现出来了,主要有以下两个好处:
immutable update
configureStore
这是对标准Redux中createStore函数的封装,它为store添加了一些配置,以获得更好的开发体验,包裹createStore,并集成了redux-thunk、Redux DevTools,默认开启
createStore
Redux DevTools
getDefaultMiddleware
返回一个包含默认middleware的数组,默认情况下,configureStore会自动添加一些中间件到store设置中。如果你想自定义middleware列表,你可以将自己的middleware添加到getDefaultMiddleware返回的数组中。
middleware
createReducer
简化了标准Redux中的reducer函数。内置了immer(默认开启),通过在reducer中编写mutable代码,极大简化了immutable的更新逻辑(上文中有详细介绍过immer库),除此之外,在RTK中使用createReducer函数创建reducer的时候,有两中创建方式,一种回调函数的方式,一种映射对象的方式。(我更喜欢后者,因为映射对象的书写方式看起来更加直观、更加容易理解)
createAction
用于创造和定义标准Redux类型的函数。传入一个常量类型,它会返回一个携带payload的函数,和标准redux中的action基本类似。
常量类型
payload
action
createSlice
这个createSlice函数,在我看来是RTK中的核心api,官方文档中对它的描述是这样的:该函数接收一个初始化state对象,和一个reducer对象,它可以将store以slice的方式分割成为不同的部分,每个部分都会独立生成相对应的action和state对象。在99%的情况下,我们都不会直接使用createReducer和createAction,取而代之的就是createSlice。
state
slice
createAsyncThunk
用来处理异步操作的方法,对于使用RTK的项目来说,完成异步操作主要分三个步骤,createAsyncThunk方法主要用来创建异步函数,创建完毕之后在reduce中进行处理,最后在业务代码中用dispatch进行调用,基本流程和标准的Redux并无二致。(需要注意的是,在createSlice中,我们不可以用普通的reduce处理异步函数,必须使用 extraReducers来处理异步)
reduce
dispatch
extraReducers
# 使用 npm npm install @reduxjs/toolkit # 使用 yarn yarn add @reduxjs/toolkit
interface InitialState { count: number; } const initialState: InitialState = { count: 0, };
export const getData = createSlice({ name: "nameSpace", initialState, reducers: {}, extraReducers: {}, });
const fetchData = createAsyncThunk("nameSpace/fetchData", async () => await axios(someAPI));
const getData = createSlice({ name: "nameSpace", initialState, reducers: {}, extraReducers: { [fetchData.fulfilled.type]: (state: InitialState, action: PayloadAction<InitialState>) => { state.count = action.payload.count; }, }, });
const rootReducer = combineReducers({ data: getData.reducer });
const store = configureStore({ reducer: rootReducer, middleware: getDefaultMiddleware => [...getDefaultMiddleware()], devTools: true, }); export default store;
完整代码如下:
import { createSlice, PayloadAction, createAsyncThunk, combineReducers, configureStore } from "@reduxjs/toolkit"; import axios from "axios"; interface InitialState { count: number; } const initialState: InitialState = { count: 0, }; export const fetchData = createAsyncThunk("nameSpace/fetchData", async () => await axios(someAPI)); export const getData = createSlice({ name: "nameSpace", initialState, reducers: {}, extraReducers: { [fetchData.fulfilled.type]: (state: InitialState, action: PayloadAction<InitialState>) => { state.count = action.payload.count; }, }, }); const rootReducer = combineReducers({ data: getData.reducer }); const store = configureStore({ reducer: rootReducer, middleware: getDefaultMiddleware => [...getDefaultMiddleware()], devTools: true, }); export default store;
import React, { useEffect } from "react"; import { useDispatch } from "react-redux"; import { fetchData } from "../redux/slice"; import { useSelector } from "../redux/hooks"; const App: React.FC = () => { const dispatch = useDispatch(); const count = useSelector(({ data }) => data); useEffect(() => { dispatch(fetchData()); }, []); return <div>{count}</div>; }; export default App;
写的比较乱,当做学习笔记写的,后面有时间会持续进行优化和补充,如果有错误,感谢指正!
一、前言
在一般的React项目中,大多数情况会选择
Redux
或者React-Redux
作为我们的状态管理工具,在使用Vuex
的时候,我们可以在mutations
里写同步操作,也可以在actions
里写异步操作,然而Redux
不同于Vuex
,Redux
本身是不支持异步的,如果需要处理异步操作,我们还要额外安装redux-thunk
或者redux-saga
这样的中间件,显得很繁琐。如果你的React项目中使用了
react hook
、redux
、redux-thunk
、或者redux-saga
,那么可能你需要用redux-toolkit
(以下简称RTK
)来优化你的项目结构,它可以让你的代码看起来更清爽。二、简介
RTK旨在帮助解决关于Redux的几个问题:
简单讲,配置
Redux
的流程太过复杂,完整需要编写actionTypes、actions、reducer、store等一系列函数,最后通过connect隐射到props里面供组件使用。而使用RTK
,只需一个reducer
即可。那么
RTK
到底是什么呢?三、核心依赖
我们打开GitHub,找到
RTK
的源码,可以在 toolkit/package.json目录中看到关于RTK用到的一些依赖:关于这些依赖都代表什么呢?
第一个:
redux
当然了,RTK需要它才能工作,这里引入了redux,意味着如果我们的项目安装了RTK,就不需要重复安装redux;除此之外,如果大家观察的足够仔细,就会发现RTK的依赖并不包含
react-redux
,这意味着,RTK是一个独立的库,它不只是给react
项目使用,如果你愿意,你可以在任何环境中使用它,比如Vue
或者Angular
,甚至jQuery
中,或者原生js
中都可以使用。第二个:
redux-thunk
RTK自带了redux-thunk来处理异步逻辑,thunk在RTK中是默认开启的(在开发过程中,你可以手动关闭,如果你愿意,也可以安装
redux-saga
等其它异步处理的中间件)。第三个:
reselect
这也是一个比较流行的redux插件,它可以帮助我们在视图渲染的时候记住当前的状态,防止组件在不需要的时候被无意识的渲染,功能有点类似于
shouldComponentDidUpdate
,但他们并不是一个东西,这里作为RTK入门笔记,对该插件不做深入讲解。第四个:
immer
最后一个immer是个非常有意思的插件,它允许我们把state的
immutable
特性转化为mutable
,也就是说,我们在reducer函数中,可以直接修改state中的数据(我在第一次听到这个思路的时候,理智告诉我,这是不对的,因为这样就违反了reducer函数式编程的理念,redux是单向数据流
的状态管理工具,数据是不可变的
,我们不可以直接修改store的状态,而是通过返回新state
,替换旧state
来完成状态更新,所以我一开始接触到这个概念的时候,还是比较抵触的)。总而言之,我们可以选择使用immer
来修改state,让数据变成mutable
状态,也可以选择不使用immer
,让数据保持immutable
状态,这完全取决于你自己,大家可以自行决定。(这不正是React的灵活之处吗)四、关于immer库
在上文中,我们对
immer
做了简单的介绍,这里再单独拿出来讨论一下immer到底是什么?在上文中我们提到,它是一个很有意思的插件,它允许我们把state的
immutable
特性转化为mutable
,实际上,immer在底层是的核心实现是利用了ES6的proxy
,在我们对状态进行修改的时候,proxy对象进行拦截,并且proxy按顺序替换上层对象,相当于自动帮你返回的新对象(所以其实还是immutable的,只不过写法上看起来是可直接修改state)下面的例子是用immer和不用immer的区别:
于是有同学会问了,在上面的例子中,使用immer和不使用immer的代码行数是一样的,也没体现出代码的简化,所以它的优点体现在哪里呢?
再看下面的例子:
这是一个很典型的例子,如果我们的状态嵌套了很多层,并且需要修改的数据在很深层,这时immer的便利性就体现出来了,主要有以下两个好处:
immutable update
的逻辑是很困难的,并且用户在reducer中修改状态时,可能会因为粗心而犯错,启用immer
可以很好的规避这一点五、核心api
configureStore
这是对标准
Redux
中createStore
函数的封装,它为store添加了一些配置,以获得更好的开发体验,包裹createStore,并集成了redux-thunk
、Redux DevTools
,默认开启getDefaultMiddleware
返回一个包含默认
middleware
的数组,默认情况下,configureStore会自动添加一些中间件到store设置中。如果你想自定义middleware列表,你可以将自己的middleware添加到getDefaultMiddleware
返回的数组中。createReducer
简化了标准Redux中的
reducer
函数。内置了immer
(默认开启),通过在reducer中编写mutable
代码,极大简化了immutable
的更新逻辑(上文中有详细介绍过immer库),除此之外,在RTK中使用createReducer
函数创建reducer
的时候,有两中创建方式,一种回调函数的方式,一种映射对象的方式。(我更喜欢后者,因为映射对象的书写方式看起来更加直观、更加容易理解)createAction
用于创造和定义标准Redux类型的函数。传入一个
常量类型
,它会返回一个携带payload
的函数,和标准redux中的action
基本类似。createSlice
这个
createSlice
函数,在我看来是RTK中的核心api,官方文档中对它的描述是这样的:该函数接收一个初始化state
对象,和一个reducer
对象,它可以将store以slice
的方式分割成为不同的部分,每个部分都会独立生成相对应的action
和state
对象。在99%的情况下,我们都不会直接使用createReducer
和createAction
,取而代之的就是createSlice
。createAsyncThunk
用来处理异步操作的方法,对于使用RTK的项目来说,完成异步操作主要分三个步骤,
createAsyncThunk
方法主要用来创建异步函数,创建完毕之后在reduce
中进行处理,最后在业务代码中用dispatch
进行调用,基本流程和标准的Redux并无二致。(需要注意的是,在createSlice
中,我们不可以用普通的reduce
处理异步函数,必须使用extraReducers
来处理异步)六、如何使用
1. 安装
2. 初始化state
3. 创建slice
4. 创建异步函数
5. 传入异步函数,更新状态
6. 创建Reducer
7. 创建仓库
完整代码如下:
8. 在业务代码中使用:
七、最后
写的比较乱,当做学习笔记写的,后面有时间会持续进行优化和补充,如果有错误,感谢指正!