上一篇介绍了redux的基本知识点,今天一起来学习下@reduxjs/toolkit。
其他几篇:
react数据集中式管理第四篇--@rematch/core
什么是@reduxjs/toolkit
@reduxjs/toolkit 就是redux的一个工具包,它想编写Redux逻辑的标准方式,想解决配置Redux存储太复杂的问题;
先放上我的项目部分目录结构:
\
下面一起学习@reduxjs/toolkit中的createAction、counterReducer、createSlice
主角之一:createAction:
之前我们要写一个action时,要自己写 const action = {type: xxx, payload: xxx}; 如果action一多起来每次都要写什么type,payload就会很繁琐;这时候createAction就发挥它的作用;
createAction:返回给定action type 字符串的 action creator 函数,该函数本身已定义了 toString() ,因此可以代替常量类型使用;
注意:createAction返回的是一个函数,那么这个函数的参数就是payload的值,上代码:console.log("getAgentUserAction", getAgentUserAction({})); // {type: "posts/update", payload: {}}
// store/modules/action.js 存放所有的createAction
import { createAction } from "@reduxjs/toolkit";
export const getAgentUserAction = createAction("posts/update");
// 返回给定 action type 字符串的 action creator 函数,该函数本身已定义了 toString(),因此可以代替常量类型使用
console.log("getAgentUserAction", getAgentUserAction({})); // {type: "posts/update", payload: {}}
export const increment = createAction("counter/increment");
export const decrement = createAction("counter/decrement");
export const incrementByAmount = createAction("counter/incrementByAmount");
export const sagaAddUser = createAction("saga/addUser");
export const sagaTake = createAction("saga/take");
export const sagaUpdateUser = createAction("saga/updateUser");
主角之二:createReducer
如果像之前那样要写switch case去匹配action.type其实是比较繁琐的,createReducer可以解决这个问题
createReducer函数接收两个参数,第一个参数为模块初始值state,第二个参数可以使用builder方式处理case reducer
比如下方的count模块的例子:
将reducer需要处理的action对应的函数用builder.addCase的方式挂载来监听;
// store/modules/createReducer.js
import { createReducer } from "@reduxjs/toolkit";
import { increment, decrement, incrementByAmount } from "./action";
// state初始值
const initialState = { value: 0 };
// createAction 运用toString()
const reducers = {
// 下面两种都行
[incrementByAmount]: (state, action) => {
console.log("incrementByAmount", action);
state.value += action.payload;
},
/* [incrementByAmount](state, action) {
console.log("incrementByAmount", action);
state.value += action.payload;
}, */
};
// createReducer的第一个参数为第一次调用reducer时的初始状态
export const counterReducer = createReducer(initialState, (builder) => {
builder
.addCase(increment, (state, action) => { // increment 这里换成 "counter/increment" 是一样的
console.log("increment", action); // 若是dispatch(increment({})) 则这里打印:{type: "counter/increment", payload: {}}
state.value++; // 看到这个是否有疑问,之前不是说不能直接修改state值吗,只能用return的方式;其实这里是工具包里面做了处理;
})
.addCase(decrement, (state, action) => {
console.log("decrement", action);
state.value--;
})
/* .addCase("counter/incrementByAmount", (state, action) => {
console.log("incrementByAmount", action);
state.value += action.payload;
}) */
.addCase(incrementByAmount, reducers[incrementByAmount]);
});
// 这里addCase第一个参数可以使用createAction生成的toString(),也可以使用字符串:"xxxx"
然后将该模块挂载到store之中:
这里为啥是挂载到redux里面的reducers之中,其实是rematch用的init定义的关系,这个之后我们再介绍,总之你先可以理解为这样就挂载上store了;
使用counter模块的state和修改state:
// Counter.js
import { useSelector, useDispatch } from "react-redux";
import { increment, incrementByAmount } from "../store/modules/action";
const Counter = () => {
// 获取counter模块的state
const data = useSelector((state) => state.counter);
console.log(data, "data"); // { value: 0 }
const dispatch = useDispatch();
const add = () => {
// dispatch.counter 为undefined 是获取不到的;
console.log(dispatch.counter); //undefined
dispatch(increment({})); // 以下为大概步骤解析
// 首先:increment({}) =》 {type: "counter/increment", payload: {}}
// 接着:dispatch({type: "counter/increment", payload: {}})
// 然后会直接调用counterReducer模块中定义的increment函数
};
const decrement = () => {
// counterReducer模块中定义了decrement及其函数,故以下dispatch也是可以直接调用定义的decrement函数
dispatch({ type: "counter/decrement", payload: {} });
};
const add2 = () => {
dispatch(incrementByAmount(2));
};
return (
<div>
<section className="posts-list">
<h2>Counter</h2>
<div>value: {data.value}</div>
</section>
<section>
........
</section>
</div>
);
};
export default Counter;
是不是看起来很简单,那继续看下一个。
主角之三:createSlice:
我们每次dispatch(action)时,比如上面的例子,都要先定义action是怎样的,那有没有什么方式可以不用自己直接去定义呢;
createSlice:可以创建一个模块store去使用,可以让你不用定义action,它内部已经帮你生成action type
首先,我们先创建一个store模块posts:
当我们在reducers创建方法addPost时,createSlice内部会自动生成一个同名的 “action creator” 函数,即type: "posts/addPost";
那么为什么是 posts/addPost 不是 另外什么 xxx/addPost呢,其实这个跟该createSlice定义时的name有关;name: "posts",
// store/modules/postsSlice.js
import { createSlice } from "@reduxjs/toolkit";
import { getAgentUserAction } from "./action";
// 定义state的初始值
const initialState = {
list: [
{ id: "1", title: "First Post!", content: "Hello!" },
{ id: "2", title: "Second Post", content: "More text" },
],
status: "wait",
};
const postsSlice = createSlice({
name: "posts", // 内部生成action.type的前缀
initialState, // 相当于postsSlice模块的state初始值
reducers: { // 定义方法
addPost(state, action) {
console.log(action); // {type: 'posts/addPost', payload: xxx}
state.push(action.payload);
},
},
});
// 将定义的方法暴露出去
export const { addPost } = postsSlice.actions;
// 当我们编写 addPost reducer 函数时,createSlice 会自动生成一个同名的 “action creator” 函数,即type: "posts/addPost"。
// 这里就可以看出如果在其他文件引入addPost将其调用,则:addPost({}) =》 {type: "posts/addPost", payload: {}}
console.log(postsSlice.actions.addPost({})); // {type: "posts/addPost", payload: {}}
export const selectAllPosts = (state) => state.posts;
export default postsSlice.reducer; // 这里就可以将该模块挂载到store之中;
然后将posts模块挂载到store之中:
这里为啥是挂载到redux里面的reducers之中,其实是rematch用的init定义的关系,这个之后我们再介绍,总之你先可以理解为这样就挂载上store了;
那么,我们该如何使用posts的state和更新state呢:
上代码:里面有注释说明
// PostsList.js
import { useSelector, useDispatch } from "react-redux";
import { nanoid } from "@reduxjs/toolkit";
import { addPost, selectAllPosts } from "../store/modules/postsSlice";
import { getAgentUserAction } from "../store/modules/action";
const PostsList = () => {
const posts = useSelector((state) => state.posts);
const dispatch = useDispatch();
console.log(posts, "posts"); // 这里就可以取得posts模块的所有state了
const save = () => {
const params = { id: nanoid(), title, content };
console.log(addPost, "addPost"); // ƒ posts/addPost "addPost"
// addPost(params) => {type: "posts/addPost", payload: params}
// 可以看出addPost跟createAction创建出来的是一样的,只不过是内部做了处理;const addPost = createAction('posts/addPost')
dispatch(addPost(params));
// 首先 addPost(params) => {type: "posts/addPost", payload: params} => 就是相当于返回一个action
// 然后 dispatch({type: "posts/addPost", payload: params})
// 相当于调用posts中的addPost方法:addPost(state, action); action等于 {type: "posts/addPost", payload: params}
// 另外:dispatch.posts 为undefined 是获取不到的;
// 其实你直接dispatch(addPost(params)); 换成 dispatch({type: "posts/addPost", payload: params}) 也是没问题可以触发的
console.log(dispatch.posts, "dispatch.posts");
};
return (
<div>
<section className="posts-list">
<h2>Posts</h2>
</section>
........
</div>
);
};
export default PostsList;
这个时候你会发现我们定义的方法是为同步的,那要是我想处理异步逻辑该怎么办呢,其实有个字段extraReducers可以使用
详情可以参考这里→ cn.redux.js.org/tutorials/e… (主要是自己没有去动手操作只是看了下😅)
在这里穿插介绍一个功能的使用:bindActionCreators
import { bindActionCreators } from "redux";
import { sagaAddUser } from "../store/modules/action";
bindActionCreators(sagaAddUser, dispatch)("张三"); // 其实就是=》 dispatch(sagaAddUser("张三"));
// bindActionCreators(sagaAddUser, dispatch) // 返回的是一个包裹dispach的函数
// let fn = bindActionCreators(sagaAddUser, dispatch) 就是等于 fn = (id) => dispatch(sagaAddUser(id))
// 所有fn("张三") 就是等于 fn = ("张三") => dispatch(sagaAddUser("张三"))
这一篇的分享就到这里结束了,那有没有其他方法去处理异步逻辑呢,这个时候redux-saga中间件就可以派上用场了,若有兴趣了解redux-saga可看下一篇哦。