菜单

createSlice 和 Reducers

相关源文件

本文档解释了如何使用 Redux Toolkit 的 createSlice API,通过集成 Immer 来简化 reducer 和 action 的创建。createSlice 是 Redux Toolkit 中的一个核心函数,它通过自动生成 action creators 和 action types,并借助 Immer 实现更简单的 reducer 逻辑,从而帮助消除 Redux 样板代码。

有关 Redux 异步操作的信息,请参阅使用 createAsyncThunk 的异步逻辑。有关 store 配置,请参阅configureStore

什么是 createSlice?

createSlice 是一个高阶函数,它接受一个初始状态、一个 reducer 函数对象和一个 slice 名称,并自动生成与 reducer 和状态对应的 action creators 和 action types。

Sources: docs/tutorials/essentials/part-2-app-structure.md235-283

createSlice 显著减少了您需要编写的 Redux 样板代码。您无需手动单独定义 action types、action creators 和 reducers,createSlice 会为您处理所有这些。

createSlice 如何转换 Redux 代码

下图展示了 createSlice 如何将传统的 Redux 模式转换为更简单的代码

Sources: docs/tutorials/essentials/part-2-app-structure.md238-297 docs/tutorials/essentials/part-4-using-data.md372-422

createSlice 的基本用法

使用 createSlice 的基本模式是

这是一个如何定义 slice reducer 的简单示例

// Define slice with createSlice
const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: state => state + 1,
    decrement: state => state - 1,
    incrementByAmount: (state, action) => state + action.payload
  }
})

// Extract generated action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions

// Export the reducer function for the store
export default counterSlice.reducer

Sources: docs/tutorials/essentials/part-2-app-structure.md240-284 docs/tutorials/essentials/part-3-data-flow.md273-299

Immer 集成实现不可变更新

createSlice 最强大的功能之一是它与 Immer 库的集成,这使您能够编写看起来“改变”状态但实际上产生不可变更新的 reducers。

使用 Immer 之前(手动不可变更新)

function handwrittenReducer(state, action) {
  return {
    ...state,
    first: {
      ...state.first,
      second: {
        ...state.first.second,
        [action.someId]: {
          ...state.first.second[action.someId],
          fourth: action.someValue
        }
      }
    }
  }
}

使用 createSlice 和 Immer

const sliceWithImmer = createSlice({
  name: 'test',
  initialState: { first: { second: { someId: { fourth: 'value' } } } },
  reducers: {
    someAction(state, action) {
      // This looks like mutation, but Immer makes it safe!
      state.first.second[action.payload.someId].fourth = action.payload.someValue
    }
  }
})

Immer 的工作原理是利用 JavaScript 的 Proxy 特性来包装您的状态并跟踪所有尝试修改它的操作。当 reducer 函数完成时,Immer 会获取所有检测到的更改,并生成一个反映这些更改的新的不可变状态对象。

Sources: docs/tutorials/essentials/part-2-app-structure.md329-383 docs/tutorials/essentials/part-4-using-data.md345-372

createSlice 的 Reducer 模式

基本 Reducer

reducers 对象中,每个键都成为一个 action type 字符串,每个函数都是处理该 action 的 reducer。

const postsSlice = createSlice({
  name: 'posts',
  initialState: [],
  reducers: {
    // Basic reducer: just one argument (state)
    allPostsLoaded(state) {
      // do something with state
    },
    // Reducer with payload: (state, action)
    postAdded(state, action) {
      state.push(action.payload)
    }
  }
})

Sources: docs/tutorials/essentials/part-3-data-flow.md273-299 docs/tutorials/essentials/part-4-using-data.md216-247

使用 prepare 回调

有时,您需要在 action payload 到达 reducer 之前进行额外的准备工作。prepare 回调允许您自定义 action creator

示例

const postsSlice = createSlice({
  name: 'posts',
  initialState: [],
  reducers: {
    // Object notation with reducer and prepare
    postAdded: {
      reducer(state, action) {
        state.push(action.payload)
      },
      prepare(title, content, userId) {
        // Do work to prepare the payload
        return {
          payload: {
            id: nanoid(),
            date: new Date().toISOString(),
            title,
            content,
            user: userId
          }
        }
      }
    }
  }
})

Sources: docs/tutorials/essentials/part-4-using-data.md370-442

使用 extraReducers

虽然 reducers 处理此 slice 创建的 action,但 extraReducers 允许此 slice 响应其他 action,包括由其他 slice 或异步 thunk 派发的 action。

有两种方式定义 extraReducers

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    // ...reducer methods
  },
  extraReducers: builder => {
    builder
      .addCase(fetchPosts.pending, (state, action) => {
        state.status = 'loading'
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.status = 'succeeded'
        // Add fetched posts to the array
        state.posts.push(...action.payload)
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.status = 'failed'
        state.error = action.error.message
      })
  }
})

推荐使用 builder callback 模式,因为它提供了完整的 TypeScript 类型安全。

Sources: docs/tutorials/essentials/part-5-async-logic.md478-516 docs/tutorials/essentials/part-6-performance-normalization.md600-675

TypeScript 支持

createSlice 对 TypeScript 有很好的支持。以下是如何将其与 TypeScript 一起使用

// Define a TypeScript interface for your state
interface CounterState {
  value: number
  status: 'idle' | 'loading' | 'failed'
}

// Use the interface with initialState
const initialState: CounterState = {
  value: 0,
  status: 'idle'
}

// createSlice with typed state
const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    // TypeScript will infer the argument types and return type
    increment(state) {
      state.value += 1
    },
    // Use PayloadAction for actions with a payload
    incrementByAmount(state, action: PayloadAction<number>) {
      state.value += action.payload
    }
  }
})

Sources: docs/tutorials/essentials/part-2-app-structure.md237-284 docs/usage/UsageWithTypescript.md129-143

createSlice 的高级模式

在 createSlice 中定义选择器

某些版本的 Redux Toolkit 支持直接在 createSlice 中定义选择器

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    // reducer functions
  },
  // Optional selectors field
  selectors: {
    // Selectors receive the slice state
    selectAllPosts: postsState => postsState,
    selectPostById: (postsState, postId) => {
      return postsState.find(post => post.id === postId)
    }
  }
})

// Can export selectors from the slice
export const { selectAllPosts, selectPostById } = postsSlice.selectors

Sources: docs/tutorials/essentials/part-4-using-data.md539-589

在 createSlice 中定义 Thunk

高级用法允许使用 reducers 的回调语法在 createSlice 中定义 thunk

const createAppSlice = buildCreateSlice({
  creators: { asyncThunk: asyncThunkCreator }
})

const postsSlice = createAppSlice({
  name: 'posts',
  initialState,
  reducers: create => {
    return {
      // Define async thunk within the slice
      fetchPosts: create.asyncThunk(
        async () => {
          const response = await client.get('/posts')
          return response.data
        },
        {
          pending: (state) => {
            state.status = 'loading'
          },
          fulfilled: (state, action) => {
            state.status = 'succeeded'
            state.posts.push(...action.payload)
          },
          rejected: (state, action) => {
            state.status = 'failed'
            state.error = action.error.message
          }
        }
      )
    }
  }
})

Sources: docs/tutorials/essentials/part-5-async-logic.md736-834

最佳实践

  1. 每个特性一个 Slice:为应用程序的每个特性或领域区域组织 Redux 状态,每个特性一个 slice。

  2. Slice 文件结构:将 slice 的所有逻辑放入单个文件中

    • 状态类型/接口
    • 初始状态
    • 带有 reducers 的 createSlice 调用
    • 导出 action 和 reducer
    • 访问状态的选择器
  3. 使用 Immer 进行不可变更新:利用 Immer 的“类修改”语法来编写更简洁的代码。

  4. 使用 Prepare 回调:对于复杂的 action payload,使用 prepare 回调模式来封装 action 创建逻辑。

  5. extraReducers 使用 Builder Callback:为了更好的 TypeScript 类型安全,extraReducers 请使用 builder callback 模式。

  6. 导出类型化的 Hooks:创建并使用包含您的 Redux store 类型的预类型化 Hooks。

// app/hooks.ts
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()

Sources: examples/counter-ts/src/app/hooks.ts1-7 docs/usage/UsageWithTypescript.md76-117

总结

createSlice 是一个强大的 Redux Toolkit 函数,它通过以下方式简化了 Redux 开发:

  1. 从您的 reducer 函数自动生成 action creators 和 action types
  2. 通过 Immer 集成实现“可变”状态更新
  3. 提供用于定义 reducers、action creators 和初始状态的统一 API
  4. 支持 TypeScript 以实现类型安全的 Redux 开发

通过使用 createSlice,您可以显著减少样板代码,使您的 Redux 逻辑更易于维护,并专注于您的业务逻辑而非 Redux 实现细节。