如果你在用 React,那你也应该使用 Redux。
如果你在用 Redux,那你也应该使用 RTK。
RTK 全拼 Redux-Toolkit,是 Redux 的官方工具包,致力于让 Redux 更易于使用。
这篇文章将分成 10 个步骤教你快速掌握 RTK。
在阅读之前,你最好知道一些 Redux 的知识。
1. 创建 store
使用 configureStore
创建 store。
store 中保存所有的数据以及更新数据的操作(reducer)。
使用 Provide
组件包裹你的所有组件。
import { configureStore } from "@reduxjs/toolkit" import { Provider } from "react-redux" // 也可以配置 middleware 和 enhancer const store = configureStore({ reducer: { // 改变数据的函数 } }) const Container = () => ( // Provider 组件可以将 store 以上下文的方式注入到根组件中,这样所有的组件都可以使用上下文 <Provider store={store}> <App /> </Provider> )
2. 创建命名切片(slice)
slice 包含一部分状态和可以更新这部分状态的函数。
我们通常以模块来划分 Slice,比如商城系统可以设计成 GoodcartSlice、ShopSlice、OrderSlice 等。
这样做的好处是将业务高内聚,不再为了修改一个 action 而去修改 N 个文件。
import { createSlice } from "@reduxjs/toolkit" export const counterSlice = createSlice({ name: 'counter',// 每个 slice 必须有一个名字,而且需要是唯一的。 // 设置初始状态 initialState: { count: 0 }, // reducers 可以自动创建 actions reducers: { } })
3. 将 slice 中的 reducer 添加到 store 中
我们在 createSlice 中定义的 reducers(复数)会在 counterSlice 对象上自动创建 reducer(单数)属性。
将 slice.reducer 添加到 store 中。
const store = configureStore({ reducer: { // reducer 的 key 需要和 slice 的 name 保持一致 counter: counterSlice.reducer// reducer 会根据配置对象的 reducers 自动创建 } })
4. 在 slice 的 reducers 中编写逻辑
reducer 可以获取当前的状态和正在执行的 action,并更新 Slice 中的 state。
RTK 内部自动使用 immer,所以这些 state 的变化都是不可变的。
import { createSlice } from "@reduxjs/toolkit" export const counterSlice = createSlice({ name: 'counter', initialState: { count: 0 }, reducers: { // reducers 的每个方法都可以接收到当前的状态 // 方法的第二个参数可以自动接收 actions,这个例子中没有用到 up: state => { state.count += 1// 这种写法像是在直接改变原数据,但 RTK 会使用 immer 让它变成不可变的操作 }, down: state => { state.count -= 1 } } })
5. 使用 useSelector 从 store 中取值
useSelector 函数可以访问 store 中所有的数据,并且可以值返回当前组件所需要的数据。
它是一个 hook,我们可以在任意一个被 Provider 包裹的组件中调用它。
import { useSelector } from "react-redux" cosnt App = () => { // state 参数是 store 的所有数据 // state.counter 是我们的命名 slice // 最后在 slice 中获取需要的值 const count = useSelector(state => state.counter.count) return ( <div> Count: {count} </div>) }
6. 更改 store 的数据时,从 slice 中导出 actions
slice 会根据 reducers 自动生成一个 actions 属性,它包含了一组方法,但是我们不能直接调用,为它们需要当前的 state 和参数。
cosnt App = () => { const actions = counterSlice.actions // 此时 actions 身上包含两个方法:actions.up 和 actions.down }
7. 使用 dispatch 调用 actions
我们需要使用 useDispatch hook 来访问 dispatch 函数,然后使用这个函数来调用 actions。
actions 更新 state 后,所有使用 useSelector 访问数据的组件都会自动更新。
cosnt App = () => { const count = useSelector(state => state.counter.count) const actions = counterSlice.actions // dispatch 是个方法,它可以调用 action const dispatch = useDispatch() // actions.up 会创建一个 action 对象 const constUp = () => dispatch(actions.up()) const constDown = () => dispatch(actions.down()) return ( <div> Count: {count} <button onClick={countUp}>+</button> <button onClick={countDown}>-</button> </div>) }
8. 为 action 添加参数
action 也可以附带参数,作为 reducer 的第二个参数传入。
传递的数据 RTK 会自动帮我们挂载到 action 的 payload 属性上。
import { createSlice } from "@reduxjs/toolkit" export const counterSlice = createSlice({ name: 'counter', initialState: { count: 0 }, reducers: { up: (state, action) => { state.count += action.payload || 1 }, down: (state, action) => { state.count -= action.payload || 1 } } })
在组件中传递参数。
cosnt App = () => { const count = useSelector(state => state.counter.count) const actions = counterSlice.actions const dispatch = useDispatch() const constUp = () => dispatch(actions.up()) const constDown = () => dispatch(actions.down()) const plusFive = () => dispatch(actions.up(5)) const minusFive = () => dispatch(actions.down(5)) return ( <div> Count: {count} <button onClick={countUp}>+</button> <button onClick={plusFive}>+ 5</button> <button onClick={countDown}>-</button> <button onClick={minusFive}>- 5</button> </div>) }
9. 使用 redux-thunk 处理异步操作
因为异步操作非常常见,所以 RTK 内置了 redux-thunk。
处理异步的方式就是创建一个 thunk 函数,它返回一个异步函数,然后在 thunk 函数中使用 dispatch 调用 action。
// 返回的异步函数默认接受 dispatch 作为参数 const fetchLength = () => async dispatch => { // 模拟 ajax 获取数据 const result = await fetch("http://localhost:3000/api/length") const text = await result.text() const actions = counterSlice.actions // 当数据准备完毕后,使用 dispatch 调用 action dispatch(actions.up(text.length)) }
在组件中进行异步操作。
cosnt App = () => { const count = useSelector(state => state.counter.count) const actions = counterSlice.actions const dispatch = useDispatch() const constUp = () => dispatch(actions.up()) const constDown = () => dispatch(actions.down()) const plusFive = () => dispatch(actions.up(5)) const minusFive = () => dispatch(actions.down(5)) // 将 thunk 函数像 action 一样传递给 dispatch const countAsync = () => dispatch(fetchLength()) return ( <div> Count: {count} <button onClick={countUp}>+</button> <button onClick={plusFive}>+ 5</button> <button onClick={countDown}>-</button> <button onClick={minusFive}>- 5</button> <button onClick={countAsync}> async fetch </button> </div>) }
10. Context 可以替代 Redux 吗?
通过上面的学习 ,我们发现即使是一个很简单的计数器应用,也要写大量的代码和使用繁琐的 API。
既然 ReactRedux 是使用 React 的 Context 来实现的,那我们只使用 Context 会怎么样呢?
对于小型项目,我更倾向于使用 Context。
但是对于大型项目和多人团队协作来说,Redux 有它不可替代的一些优势:
- 更清晰的代码组织结构。
- 更易于测试。
- 提供了功能强大的 devtools。
- 强大的生态系统。