jtwang7 / React-Note

React 学习笔记
8 stars 2 forks source link

Redux 使用笔记 #52

Open jtwang7 opened 2 years ago

jtwang7 commented 2 years ago

Redux 使用笔记

❇️ Quick Start

Create a Redux Store

创建一个 Redux 仓库 (根 store)

import { configureStore } from '@reduxjs/toolkit'

// 创建 redux store 仓库
export const store = configureStore({
  reducer: {},
})

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

Define Typed Hooks 定制类型化版本的 useSelector / useDispatch:虽然可以将 RootState 和 AppDispatch 类型导入每个组件,但最好创建 useDispatch 和 useSelector 挂钩的类型化版本以供在应用程序中使用:

由于这些是实际变量,而不是类型,因此将它们定义在单独的文件(例如 app/hooks.ts)中很重要,而不是定义在 store 仓库内。这允许您将它们导入到需要使用挂钩的任何组件文件中,并避免潜在的循环导入依赖问题。

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

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

Provide the Redux Store to React

创建 store 后,我们可以通过在 src/index.js 中的应用程序周围放置一个 React-Redux <Provider> 来使其对我们的 React 组件可用,导入我们刚刚创建的 Redux store。

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import { store } from './app/store'
import { Provider } from 'react-redux'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Create a Redux State Slice

从 Redux Toolkit 导入 createSlice API。创建切片需要一个字符串名称来标识切片、一个初始状态值以及一个或多个 reducer 函数来定义如何更新状态。创建切片后,我们可以导出生成的 Redux action creators 和整个切片的 reducer 函数。

Redux 要求我们通过制作数据副本和更新副本来不可变地写入所有状态更新。但是,Redux Toolkit 的 createSlice 和 createReducer API 在内部使用 Immer 允许我们编写“变异”更新逻辑,从而成为正确的不可变更新。

import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

// 每个切片文件都应该为其初始状态值定义一个类型,以便 createSlice 可以正确推断每个 case reducer 中的状态类型。
export interface CounterState {
  value: number
}

const initialState: CounterState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      // 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
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    // 所有生成的动作都应该使用 Redux Toolkit 中的 PayloadAction<T> 类型来定义,该类型将 action.payload 字段的类型作为其通用参数。
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer

Add Slice Reducers to the Store

我们需要从 counter slice 中导出 reducer 函数,并将其添加到我们的 store 中。通过在 reducer 参数中定义一个字段,我们告诉 store 使用这个 slice reducer 函数来处理对该状态的所有更新。

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice' // 导出 reducer

export const store = configureStore({
  reducer: {
    counter: counterReducer, // 注入 reducer
  },
})

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch

Use Redux State and Actions in React Components

使用 React-Redux 钩子让 React 组件与 Redux 存储交互。我们可以使用 useSelector 从存储中读取数据,并使用 useDispatch 调度操作。创建一个包含 组件的文件,然后将该组件导入 App.js 并在 中渲染它。

若定制了类型化 hooks,则在组件文件中,从 React-Redux 导入预类型的钩子而不是标准的钩子。

import React from 'react'
import type { RootState } from '../../app/store'
import { decrement, increment } from './counterSlice'
// import { useSelector, useDispatch } from 'react-redux'
import { useAppSelector, useAppDispatch } from 'app/hooks'

export function Counter() {
  // const count = useSelector((state: RootState) => state.counter.value)
  // const dispatch = useDispatch() 

  // The `state` arg is correctly typed as `RootState` already
  const count = useAppSelector((state) => state.counter.value)  // 使用 useSelector 从 store 状态中读取数据
  const dispatch = useAppDispatch() // 使用 useDispatch 调度操作

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

createAsyncThunk

jtwang7 commented 2 years ago

Redux 调试 state 状态

引用: Debug draft state in redux-toolkit (immer.js)

当使用 console.log 或调试器在 redux-toolkit reducer 中显示 DraftState 时,我们通常会得到 Immer.js 代理对象,其中包含许多不必要的信息。 (redux-toolkit 默认使用immer.js)

When using console.log or debugger to display draftState in redux-toolkit reducer we normally get Immer.js proxy object which contains lots of unnecessary information. (Immer.js is used by redux-toolkit by default)

要显示实际数据,请使用从 @redux/toolkit 导入的 current 函数

To display the actual data use current function imported from @redux/toolkit

import { createSlice, current } from '@reduxjs/toolkit'
...
console.log(current(draftState))

在日志输出中,我们只会看到我们的数据。

In the log output we’ll see only our data.