如果你在用 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。
- 强大的生态系统。