Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redux 使用笔记 #52

Open
jtwang7 opened this issue Jul 24, 2022 · 1 comment
Open

Redux 使用笔记 #52

jtwang7 opened this issue Jul 24, 2022 · 1 comment
Labels

Comments

@jtwang7
Copy link
Owner

jtwang7 commented Jul 24, 2022

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 挂钩的类型化版本以供在应用程序中使用:

  • For useSelector, it saves you the need to type (state: RootState) every time
  • For useDispatch, the default Dispatch type does not know about thunks. In order to correctly dispatch thunks, you need to use the specific customized AppDispatch type from the store that includes the thunk middleware types, and use that with useDispatch. Adding a pre-typed useDispatch hook keeps you from forgetting to import AppDispatch where it's needed.

由于这些是实际变量,而不是类型,因此将它们定义在单独的文件(例如 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>
  )
}
  • The corresponding Redux action will be dispatched to the store
  • The counter slice reducer will see the actions and update its state
  • The component will see the new state value from the store and re-render itself with the new data

createAsyncThunk

@jtwang7 jtwang7 added the Redux label Jul 24, 2022
@jtwang7
Copy link
Owner Author

jtwang7 commented Aug 20, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant