10 个步骤轻松掌握 ReduxToolkit

620 阅读4分钟

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