首页
/ Redux Essentials Part 3:React + Redux 基础数据流实战——从 configureStore 到 createSlice、useSelector 与 useDispatch

Redux Essentials Part 3:React + Redux 基础数据流实战——从 configureStore 到 createSlice、useSelector 与 useDispatch

2026-09-04 17:07:35作者:何将鹤

本文基于 Redux 官方教程 Redux Essentials, Part 3: Basic Redux Data Flow 展开,带你从零搭建一个社交帖子流应用(social media feed app),完整走通 Redux 的核心数据流:用 configureStore 创建唯一的全局 store,用 createSlice 编写 reducer 逻辑,用 useSelector 从 store 中读取数据,用 useDispatch 派发 action 更新状态。读完后,你将掌握一套可直接复制到实际项目中的 store 配置、slice 编写、TypeScript 类型导出与组件交互的完整模式,并能对照 redux 核心仓库源码理解每一次 dispatch 背后发生了什么。

Redux 单向数据流示意图:View 派发 Actions,Actions 更新 State,State 再驱动 View 重渲染

学习目标与前置知识

按照原文档,本篇教程覆盖四个核心能力:

  • 如何在 React 应用中设置一个 Redux store;
  • 如何用 createSlice 向 Redux store 添加若干"slice"(reducer 逻辑切片);
  • 如何用 useSelector 钩子让组件读取 Redux 数据;
  • 如何用 useDispatch 钩子在组件中派发 action。

前置知识方面,原文档要求熟悉 Redux 的关键术语——"actions"、"reducers"、"store"、"dispatching"(可参考 Part 1: Redux Overview and Concepts),并对 TypeScript 语法有基本了解。教程全程使用 TypeScript 编写代码:Redux 本身可以用纯 JavaScript 编写,但使用 TypeScript 能防止许多常见错误、为代码提供内置文档,并让编辑器在 React 组件和 Redux reducer 等位置提示所需的变量类型。官方强烈建议所有 Redux 应用都使用 TypeScript。

原文档同时提醒:示例应用并非一个完整的生产级项目,其目的是帮助学习 Redux API 与典型使用模式;教程早期搭建的部分内容,后续章节会用更好的方式重新组织。建议完整读完整个教程系列以了解全部概念。

项目准备:一个预配置好的起点

教程基于一个预配置好的 starter 项目:它已经装好 React 和 Redux,带有默认样式,并内置了一个假 REST API(fake REST API),让应用可以编写真实的 API 请求代码。安装依赖后,使用 yarn dev 命令启动本地开发服务器即可(项目默认使用 Yarn 4 作为包管理器,NPM、PNPM、Bun 也可以)。

完成本教程后,如果要开始自己的项目,推荐使用 Redux 官方的 Vite 和 Next.js 模板(见 Installation 文档 的 "Create a React + Redux app" 一节)作为创建新项目最快的方式——模板已预装 Redux Toolkit 和 React-Redux,并附带与 Part 1 相同的 "counter" 示例应用,可以跳过添加 Redux 包和配置 store 的步骤,直接写业务代码。

初始项目的目录结构如下:

  • /public:基础 CSS 样式及图标等静态文件
  • /src
    • main.tsx:应用入口文件,渲染 <App> 组件;示例中还在页面加载时设置假 REST API
    • App.tsx:主应用组件,渲染顶部导航栏,并负责其他内容的客户端路由
    • index.css:整个应用的样式
    • /api
      • client.ts:一个小型 fetch 封装客户端,用于发起 HTTP GET 和 POST 请求
      • server.ts:为数据提供假 REST API,应用后续会从这些假端点获取数据
    • /app
      • Navbar.tsx:渲染顶部页头和导航内容

此时加载应用,只能看到页头和一条欢迎消息,没有任何功能。

设置 Redux Store

添加 Redux 包

项目的 package.json 中已安装好使用 Redux 所需的两个包:

  • @reduxjs/toolkit:现代 Redux 包,包含构建应用所需的全部 Redux 函数
  • react-redux:让 React 组件与 Redux store 交互所需的函数

如果从零开始搭建项目,需要先把这两个包加进项目。

创建 Store:configureStore 与 reducer 选项

Redux 的一个核心原则是:整个应用只能有一个(one)store 实例。

惯例是把 store 实例放在独立文件中创建并导出。目录结构由开发者自己决定,但把应用级的设置和配置放在 src/app/ 文件夹是标准做法。先新建 src/app/store.ts 文件并创建 store:

Redux Toolkit 提供了一个 configureStore 方法。这个函数创建一个全新的 Redux store 实例,可以传入多个选项来改变 store 行为;它还会自动应用最常见、最实用的配置,包括检查典型错误,以及启用 Redux DevTools 扩展(可查看状态内容和 action 历史)。

import { configureStore } from '@reduxjs/toolkit'
import type { Action } from '@reduxjs/toolkit'

interface CounterState {
  value: number
}

// An example slice reducer function that shows how a Redux reducer works inside.
// We'll replace this soon with real app logic.
function counterReducer(state: CounterState = { value: 0 }, action: Action) {
  switch (action.type) {
    // Handle actions here
    default: {
      return state
    }
  }
}

export const store = configureStore({
  // Pass in the root reducer setup as the `reducer` argument
  reducer: {
    // Declare that `state.counter` will be updated by the `counterReducer` function
    counter: counterReducer
  }
})

configureStore 必须传入一个 reducer 选项。这通常是一个对象,包含应用不同部分的各个 "slice reducer"(必要时也可以单独创建根 reducer 函数,直接作为 reducer 参数传入)。

教程第一步先传入了一个临时的 counter slice reducer 用于演示结构,稍后会替换成真实应用的 slice reducer。

仓库中的现成示例可以印证这种写法:counter-ts 示例的 store 同样是 configureStore({ reducer: { counter: counterReducer } }),并在同一文件中导出 AppDispatch / RootState 类型:

export const store = configureStore({
  reducer: {
    counter: counterReducer
  }
})

export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>

如果你使用 Next.js,设置过程会多出几步,详见 Setup with Next.js 页面。

从源码看 reducer 选项如何变成根 reducer

configureStore 内部会把 reducer 对象的各 slice reducer 合并为一个根 reducer。redux 核心仓库中的 combineReducers 正是这种"对象 → 单一 reducer 函数"的转换实现,其中包含几个值得注意的行为(Redux Toolkit 的 configureStore 沿用了同样的校验思路):

  • 初始化探测(见 src/combineReducers.tsassertReducerShape):store 创建时会用 INIT 类型的 action 调用每个 slice reducer。如果某个 slice 在 state === undefined 时返回 undefined,会直接抛出 "returned undefined during initialization" 错误——这就是为什么 slice 必须显式返回初始状态,而不能是 undefined
  • 状态形状校验:在开发环境下,combineReducers 会检查传入的 state 是否包含未定义的 key,并对多余的 key 发出 "Unexpected key(s)" 警告(见 src/combineReducers.tsgetUnexpectedStateShapeWarningMessage),帮助尽早发现 preloaded state 与 reducer 不匹配的问题;
  • 按需更新:只有当某个 slice 的返回值引用发生变化(nextStateForKey !== previousStateForKey)时,组合 reducer 才会返回新的 state 对象(见 src/combineReducers.ts),否则原样返回旧引用——这正是"未处理该 action 的 slice 不触发重渲染"的底层机制。

此外,store 创建时会主动 dispatch 一个 INIT action 来填充初始状态树(见 src/createStore.ts):

// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT } as A)

也就是说,你在 DevTools 里看到的第一份 state(例如 { counter: { value: 0 } }),正是各 slice reducer 对 INIT action 的返回值组合出来的。

提供 Store:通过 Provider 注入

Redux 本身是一个纯 JS 库,可以和任意 UI 层配合。本应用使用 React,因此需要让 React 组件能访问 Redux store——使用 React-Redux 库,把 store 传入 <Provider> 组件。它利用 React 的 Context API 让应用内所有组件都能访问到这个 store。

原文档特别强调:不应该在其他应用代码文件里直接 import 这个 store! 原因有二:

  1. 由于只有一个 store 文件,直接导入 store 可能意外造成循环引用(file A imports B imports C imports A),导致难以追踪的 bug;
  2. 我们需要为组件和 Redux 逻辑编写测试,而这些测试需要创建各自独立的 store 实例。通过 Context 提供 store 保持了灵活性并避免导入问题。

在入口文件 main.tsx 中导入 store,用带 store 的 <Provider> 包裹 <App>

import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'

import App from './App'
import { store } from './app/store'

// skip mock API setup

const root = createRoot(document.getElementById('root')!)

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
)

检查 Redux 状态

有了 store 之后,就可以用 Redux DevTools 扩展查看当前状态:打开浏览器 DevTools(例如在页面任意位置右键选择 "Inspect"),点击 "Redux" 标签页,即可看到已派发 action 的历史和当前 state 值。

当前 state 应该长这样:

{
  counter: {
    value: 0
  }
}

这个形状正是传给 configureStorereducer 选项定义的:一个对象,带有名为 counter 的字段,而 counter 字段对应的 slice reducer 返回 {value} 形状的状态。

导出 Store 类型

既然使用 TypeScript,会经常引用"Redux state 的类型"和"Redux store dispatch 函数的类型"。这些类型需要从 store.ts 文件导出。定义方式是使用 TS 的 typeof 操作符,让 TS 基于 store 定义自动推断类型:

import { configureStore } from '@reduxjs/toolkit'

// omit counter slice setup

export const store = configureStore({
  reducer: {
    counter: counterReducer
  }
})

// Infer the type of `store`
export type AppStore = typeof store
// Infer the `AppDispatch` type from the store itself
export type AppDispatch = typeof store.dispatch
// Same for the `RootState` type
export type RootState = ReturnType<typeof store.getState>

在编辑器中悬停在 RootState 类型上,应该看到 type RootState = { counter: CounterState; }。由于该类型是从 store 定义自动派生的,将来对 reducer 配置的任何修改都会自动反映到 RootState 类型上——只需定义一次,始终精确。

仓库中 counter-ts 示例 使用的就是同一套类型推导模式(AppDispatch = typeof store.dispatchRootState = ReturnType<typeof store.getState>),并额外定义了一个 AppThunk 类型供异步逻辑使用。

导出带类型的 Hooks(pre-typed hooks)

组件中会大量使用 React-Redux 的 useSelectoruseDispatch,每次使用时它们都需要引用 RootStateAppDispatch 类型。可以预先配置好"带类型"的钩子版本,把类型内置进去,避免重复标注。

React-Redux 9.1 提供了 .withTypes() 方法,可为这些钩子应用正确类型。导出这些预置类型钩子后,在应用其余部分直接使用:

// This file serves as a central hub for re-exporting pre-typed Redux hooks.
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>()

仓库中的 counter-ts 示例 hooks 文件 与本教程完全一致,是这套模式的现成参考。

到此,setup 阶段结束。

主帖子信息流(Main Posts Feed)

社交流应用的主功能是一个帖子列表。这个功能会逐步扩展,但第一个目标只是把帖子条目显示在屏幕上。

创建 Posts Slice

"slice"是应用中单个功能的一组 Redux reducer 逻辑和 action,通常一起定义在单个文件中。名字来源于把根 Redux state 对象拆分成多个"切片"。

src 下新建 features 文件夹,在 features 下建 posts 文件夹,并添加文件 postsSlice.ts

将使用 Redux Toolkit 的 createSlice 函数生成一个能处理 posts 数据的 reducer 函数。Reducer 函数需要包含一些初始数据,这样应用启动时 store 里就有这些值。先用一个假帖子对象数组开始搭建 UI:

import { createSlice } from '@reduxjs/toolkit'

// Define a TS type for the data we'll be using
export interface Post {
  id: string
  title: string
  content: string
}

// Create an initial state value for the reducer, with that type
const initialState: Post[] = [
  { id: '1', title: 'First Post!', content: 'Hello!' },
  { id: '2', title: 'Second Post', content: 'More text' }
]

// Create the slice and pass in the initial state
const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {}
})

// Export the generated reducer function
export default postsSlice.reducer

每创建一个新 slice,都要把它的 reducer 函数加进 Redux store。打开 app/store.ts,导入 postsReducer,移除所有 counter 相关代码,并更新 configureStore 调用,让 postsReducer 以字段名 posts 传入:

import { configureStore } from '@reduxjs/toolkit'

// Removed the `counterReducer` function, `CounterState` type, and `Action` import

import postsReducer from '@/features/posts/postsSlice'

export const store = configureStore({
  reducer: {
    posts: postsReducer
  }
})

这告诉 Redux:顶层 state 对象中要有一个名为 posts 的字段,state.posts 的所有数据在派发 action 时都由 postsReducer 更新。可以打开 Redux DevTools 扩展确认当前 state 内容——此时应显示 posts 数组里的那两条假帖子。

显示帖子列表:useSelector 读取数据

现在 store 里有帖子数据了,可以创建一个 React 组件来展示。所有与 feed posts 功能相关的代码都应放在 posts 文件夹内,新建 PostsList.tsx(注意:这是一个使用 JSX 语法、用 TypeScript 编写的 React 组件,必须使用 .tsx 扩展名,TypeScript 才能正确编译)。

React 组件可以通过 React-Redux 库的 useSelector 钩子从 store 读取数据。自己写的"selector 函数"会接收整个 Redux state 对象作为参数,应返回该组件需要的特定数据。由于使用 TypeScript,所有组件都应使用 src/app/hooks.ts 中定义好的预置类型 useAppSelector 钩子。

初始的 PostsList 组件从 store 读取 state.posts,遍历帖子数组并在屏幕上逐条显示:

import { useAppSelector } from '@/app/hooks'

export const PostsList = () => {
  // Select the `state.posts` value from the store into the component
  const posts = useAppSelector(state => state.posts)

  const renderedPosts = posts.map(post => (
    <article className="post-excerpt" key={post.id}>
      <h3>{post.title}</h3>
      <p className="post-content">{post.content.substring(0, 100)}</p>
    </article>
  ))

  return (
    <section className="posts-list">
      <h2>Posts</h2>
      {renderedPosts}
    </section>
  )
}

然后更新 App.tsx 的路由:导入 PostsList,用 <PostsList /> 替换欢迎文本,并用 React Fragment 包一层(因为很快要在主页添加别的内容):

import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'

import { Navbar } from './components/Navbar'
import { PostsList } from './features/posts/PostsList'

function App() {
  return (
    <Router>
      <Navbar />
      <div className="App">
        <Routes>
          <Route
            path="/"
            element={
              <>
                <PostsList />
              </>
            }
          ></Route>
        </Routes>
      </div>
    </Router>
  )
}

export default App

添加完成后,主页应呈现一个帖子列表("First Post!" 与 "Second Post" 两条帖子):

Redux Essentials 示例应用初始帖子列表界面

有了进展:数据已加入 Redux store,并已在 React 组件中显示出来。

添加新帖子

添加新帖表单

posts 文件夹中创建 AddPostForm.tsx,加入帖子标题文本输入和正文文本域:

import React from 'react'

// TS types for the input fields
interface AddPostFormFields extends HTMLFormControlsCollection {
  postTitle: HTMLInputElement
  postContent: HTMLTextAreaElement
}
interface AddPostFormElements extends HTMLFormElement {
  readonly elements: AddPostFormFields
}

export const AddPostForm = () => {
  const handleSubmit = (e: React.FormEvent<AddPostFormElements>) => {
    // Prevent server submission
    e.preventDefault()

    const { elements } = e.currentTarget
    const title = elements.postTitle.value
    const content = elements.postContent.value

    console.log('Values: ', { title, content })

    e.currentTarget.reset()
  }

  return (
    <section>
      <h2>Add a New Post</h2>
      <form onSubmit={handleSubmit}>
        <label htmlFor="postTitle">Post Title:</label>
        <input
          type="text"
          id="postTitle"
          name="postTitle"
          defaultValue=""
          required
        />
        <label htmlFor="postContent">Content:</label>
        <textarea
          id="postContent"
          name="postContent"
          defaultValue=""
          required
        />
        <button>Save Post</button>
      </form>
    </section>
  )
}

注意此时还没有任何 Redux 相关逻辑。示例中使用"uncontrolled"输入和 HTML5 表单校验来防止提交空字段——如何读取表单值属于 React 使用习惯问题,与 Redux 无关。

把该组件导入 App.tsx,放在 <PostsList /> 上方:

// omit outer `<App>` definition
<Route
  path="/"
  element={
    <>
      <AddPostForm />
      <PostsList />
    </>
  }
></Route>

表单应显示在页头下方。

保存帖子条目:case reducer 与 PayloadAction

更新 posts slice 来新增帖子。posts slice 负责处理所有 posts 数据的更新。createSlice 调用中有一个 reducers 对象(当前为空),需要在里面添加一个处理"新增帖子"情况的 reducer 函数。

reducers 内添加名为 postAdded 的函数,接收两个参数:当前 state 值和被派发的 action 对象。由于 posts slice 只知道它负责的数据,state 参数是帖子数组本身,而不是整个 Redux state 对象。

action 对象会把新帖子放在 action.payload 字段中。声明 reducer 函数时还需要告诉 TypeScript action.payload 的具体类型,以便它在传参和访问 action.payload 内容时做正确检查:从 Redux Toolkit 导入 PayloadAction 类型,把 action 参数声明为 action: PayloadAction<Post>

实际的状态更新就是把新帖子对象加入 state 数组,可以直接用 state.push()

警告:Redux reducer 函数必须始终以不可变方式创建新 state(做拷贝)!createSlice() 内部调用 Array.push() 这类"突变"函数、或直接修改字段(如 state.someField = someValue)是安全的,因为 createSlice 内部使用 Immer 库把这些突变转换为安全的不可变更新;但不要在 createSlice 之外尝试突变任何数据!

这个警告在仓库示例中同样成立:counterSlice.ts 的注释明确写道——"Redux Toolkit allows us to write 'mutating' logic in reducers. It doesn't actually mutate the state because it uses the Immer library, which detects changes to a 'draft state' and produces a brand new immutable state based off those changes"。

写好 postAdded reducer 后,createSlice 会自动生成同名的action creator 函数。可以导出这个 action creator,在 UI 组件中于用户点击 "Save Post" 时派发该 action:

// Import the `PayloadAction` TS type
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

// omit initial state

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    // Declare a "case reducer" named `postAdded`.
    // The type of `action.payload` will be a `Post` object.
    postAdded(state, action: PayloadAction<Post>) {
      // "Mutate" the existing state array, which is
      // safe to do here because `createSlice` uses Immer inside.
      state.push(action.payload)
    }
  }
})

// Export the auto-generated action creator with the same name
export const { postAdded } = postsSlice.actions

export default postsSlice.reducer

从术语上说,这里的 postAdded 是一个 "case reducer" 的例子:它是 slice 内部处理某一个特定 action 类型的 reducer 函数。概念上等同于在一个 switch 里写了一个 case——"当看到这个确切的 action 类型时,执行这段逻辑":

function sliceReducer(state = initialState, action) {
  switch (action.type) {
    case 'posts/postAdded': {
      // update logic here
    }
  }
}

注意 action 类型字符串的格式:createSlicename 字段('posts')与 reducer 名(postAdded)拼成 'posts/postAdded'——这就是后面在 DevTools 的 action 列表中看到的名字。

派发 "Post Added" action

AddPostForm 有文本输入和触发 submit handler 的 "Save Post" 按钮,但按钮目前什么都不做。需要更新 submit handler,派发 postAdded action creator 并传入包含用户所写标题和正文的新帖子对象。

帖子对象还需要 id 字段。初始测试帖子用的是假编号,与其手写代码推算下一个自增 ID,不如生成一个随机唯一 ID——Redux Toolkit 提供了一个 nanoid 函数(Part 4 会进一步讨论 ID 生成与派发 action)。

要从组件派发 action,需要拿到 store 的 dispatch 函数——通过调用 React-Redux 的 useDispatch 钩子获得。既然使用 TypeScript,应该导入带正确类型的 useAppDispatch 钩子。同时还需要把 postAdded action creator 导入该文件。

有了 dispatch 函数后,就可以在提交处理中调用 dispatch(postAdded()):从表单取出 title 和 content,生成新 ID,组合成新帖子对象传给 postAdded()

import React from 'react'
import { nanoid } from '@reduxjs/toolkit'

import { useAppDispatch } from '@/app/hooks'

import { type Post, postAdded } from './postsSlice'

// omit form types

export const AddPostForm = () => {
  // Get the `dispatch` method from the store
  const dispatch = useAppDispatch()

  const handleSubmit = (e: React.FormEvent<AddPostFormElements>) => {
    // Prevent server submission
    e.preventDefault()

    const { elements } = e.currentTarget
    const title = elements.postTitle.value
    const content = elements.postContent.value

    // Create the post object and dispatch the `postAdded` action
    const newPost: Post = {
      id: nanoid(),
      title,
      content
    }
    dispatch(postAdded(newPost))

    e.currentTarget.reset()
  }

  return (
    <section>
      <h2>Add a New Post</h2>
      <form onSubmit={handleSubmit}>
        <label htmlFor="postTitle">Post Title:</label>
        <input
          type="text"
          id="postTitle"
          name="postTitle"
          defaultValue=""
          required
        />
        <label htmlFor="postContent">Content:</label>
        <textarea
          id="postContent"
          name="postContent"
          defaultValue=""
          required
        />
        <button>Save Post</button>
      </form>
    </section>
  )
}

现在输入标题和正文、点击 "Save Post",帖子列表里应该出现这条新帖子。

恭喜——你刚刚构建了第一个能跑的 React + Redux 应用!

完整的数据流闭环(以及它在源码中的样子)

以上步骤演示了完整的 Redux 数据流循环:

  • 帖子列表通过 useSelector 从 store 读取初始帖子集,渲染了初始 UI
  • 我们派发了 postAdded action,其中包含新帖子的数据
  • posts reducer 看到 postAdded action,用新条目更新了帖子数组
  • Redux store 通知 UI 有数据发生了变化
  • 帖子列表读取到更新后的帖子数组,重新渲染以显示新帖子

后续要添加的所有新功能都会遵循同样的基本模式:添加 state slice、编写 reducer 函数、派发 action、根据 store 数据渲染 UI。

可以打开 Redux DevTools 扩展,查看已派发的 action,以及 state 如何因该 action 更新:点击 action 列表中的 "posts/postAdded" 条目,"Action" 标签页会显示该 action 对象(type: 'posts/postAdded'payload 中的新帖子);"Diff" 标签页则会显示 state.posts 在索引 2 处新增了一个条目。

从源码层面看,这一次 dispatch(postAdded(newPost)) 在 redux 核心中的执行路径是(见 src/createStore.ts):

  1. action 合法性校验:base dispatch 要求 action 必须是普通对象(isPlainObject 检查,否则报错提示可能需要 redux-thunk 等中间件),且 type 必须存在、必须为字符串;
  2. 禁止 reducer 再派发:如果当前正处于 dispatch 过程中(isDispatching === true),调用 dispatch 会直接抛出 "Reducers may not dispatch actions." 错误,保证 reducer 是纯函数式的单向执行;
  3. 同步执行 reducercurrentState = currentReducer(currentState, action)——整个 state 树由根 reducer(由 combineReducers 式的合并逻辑生成)基于旧 state 和 action 计算出来,postAdded 的 case reducer 通过 Immer 对 state.postspush 被转成了不可变更新;
  4. 通知所有订阅者:dispatch 完成后遍历 currentListeners(订阅列表在每次 dispatch 前做快照拷贝,防止 dispatch 期间 subscribe/unsubscribe 造成 bug),逐个调用 listener——React-Redux 的 useSelector 就是通过这些订阅感知到 state 变化、重跑 selector 并按需重渲染组件的。

store 对外暴露的完整接口定义在 src/types/store.tsStore 接口中:dispatchgetStatesubscribereplaceReducer 以及 [Symbol.observable]——DevTools、React-Redux 都建立在这几个基础能力之上。

设计原则:store 只放"全局"数据

原文档强调:Redux store 只应包含被认为是应用"全局"(global)的数据! 本例中,只有 AddPostForm 需要知道输入框的最新值。即使表单改用"受控"(controlled)输入,临时数据也应该放在 React 组件 state 中,而不是塞进 Redux store。当用户完成表单操作后,再派发一个 Redux action,用用户的最终输入去更新 store。

本篇总结

到这里,Redux 应用的基础已经搭好——store、带 reducer 的 slice、以及派发 action 的 UI。回顾本部分学到的要点:

  • 一个 Redux 应用只有一个 store,通过 <Provider> 组件传递给 React 组件
  • Redux state 由 "reducer 函数" 更新
    • reducer 总是以不可变方式计算新 state:拷贝现有 state 值,并在拷贝上应用新数据
    • Redux Toolkit 的 createSlice 会为你生成 "slice reducer" 函数,让你可以写"突变"式代码,由内部转化为安全的不可变更新
    • 这些 slice reducer 被加入 configureStorereducer 字段,从而定义了 store 内的数据和 state 字段名
  • React 组件用 useSelector 钩子从 store 读取数据
    • selector 函数接收整个 state 对象,应返回一个值
    • 每次 Redux store 更新时 selector 都会重跑,如果其返回的数据发生变化,组件会重渲染
  • React 组件用 useDispatch 钩子派发 action 更新 store
    • createSlice 会为 slice 中每个 reducer 生成对应的 action creator 函数
    • 在组件中调用 dispatch(someActionCreator()) 派发 action
    • reducer 运行后检查该 action 是否与己相关,必要时返回新 state
    • 表单输入值等临时数据应保留为 React 组件 state 或纯 HTML 输入字段;用户完成表单后再派发 Redux action 更新 store
  • 如果使用 TypeScript,初始 setup 应基于 store 定义 RootStateAppDispatch 类型,并导出 React-Redux useSelectoruseDispatch 钩子的预置类型版本

下一步

掌握了基础 Redux 数据流之后,可以继续 Part 4: Using Redux Data,为应用添加更多功能,学习如何操作已经在 store 中的数据;更完整的数据流全景可以结合 Part 1Part 2: App Structure 一起阅读。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384