CLAYYA

乌贼馋辣鱼的博客

Redux

2024-11-19

Redux

基础

Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。

创建一个redux react应用:

npx create-react-app my-app --template redux

image

由于单向数据流,可通过”提升state“实现多个组件共享state,将整个组件树作为大view,任何组件都可以访问state或触发action,这就是redux的思想:应用中使用集中式的全局状态来管理,并明确更新状态的模式,以便让代码具有可预测性。

术语

action

action 是一个具有 type​ 字段的普通 JavaScript 对象。你可以将 action 视为描述应用程序中发生了什么的事件.type​字段给action一个描述性的名,一般采用 ”域(所属特征或类别)/事件名字“, 附加信息放在payload​里

const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

reducer

一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态。可以作为事件监听器看待

函数内部逻辑一般遵循:

  • 检查reducer是否关心action,如果关心就复制state,使用新的state副本,然后返回新state
  • 否则返回原来的state
const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // 检查 reducer 是否关心这个 action
  if (action.type === 'counter/increment') {
    // 如果是,复制 `state`
    return {
      ...state,
      // 使用新值更新 state 副本
      value: state.value + 1
    }
  }
  // 返回原来的 state 不变
  return state
}

store

store 是通过传入一个 reducer​ 来创建的,并且有一个名为 getState​ 的方法,它返回当前状态值:

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}

dispatch

store的方法,更新 state 的唯一方法是调用 store.dispatch()​ 并传入一个 action 对象。 store 将执行所有 reducer​ 函数并计算出更新后的 state。

dispatch 一个 action 可以形象的理解为 "触发一个事件"

const increment = () => {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

console.log(store.getState())
// {value: 2}

selector

Selector 函数可以从 store 状态树中提取指定的片段。随着应用变得越来越大,会遇到应用程序的不同部分需要读取相同的数据,selector 可以避免重复这样的读取逻辑

redux数据流

  • 初始启动:

    • 使用最顶层的 root reducer 函数创建 Redux store

    • store 调用一次 root reducer,并将返回值保存为它的初始 state

    • 当视图 首次渲染时,视图组件访问 Redux store 的当前 state,并使用该数据来决定要呈现的内容。同时监听 store 的更新,以便他们可以知道 state 是否已更改。

  • 更新环节:

    • 应用程序中发生了某些事情,例如用户单击按钮
    • dispatch​ 一个 action 到 Redux store,例如 dispatch({type: 'counter/increment'})
    • store 用之前的 state 和当前的 action 再次运行 reducer ​函数,并将返回值保存为新的 state
    • store 通知所有订阅过的视图,通知它们 store 发生更新
    • 每个订阅过 store 数据的视图 组件都会检查它们需要的 state 部分是否被更新
    • 发现数据被更新的每个组件都强制使用新数据重新渲染,紧接着更新网页

ReduxDataFlowDiagram-49fa8c3968371d9ef6f2a1486bd40a26

应用结构

目录(以计数器为例)

  • /src

    • index.js:app入口

    • App.js:顶级react入口

    • /app

      • store.js创建redux store实例
    • /features

      • Counter.js:展示counter特性的react组件
      • counterSlice.js:counter特性相关的redux逻辑

创建store

redux slice:“slice” 是应用中单个功能的 Redux reducer 逻辑和 action 的集合, 通常一起定义在一个文件中。该名称来自于将根 Redux 状态对象拆分为多个状态 “slice”。

比如,在一个博客应用中,store 的配置大致长这样:

import { configureStore } from '@reduxjs/toolkit'
import usersReducer from '../features/users/usersSlice'
import postsReducer from '../features/posts/postsSlice'
import commentsReducer from '../features/comments/commentsSlice'

export default configureStore({
  reducer: {
    users: usersReducer,
    posts: postsReducer,
    comments: commentsReducer
  }
})

创建slice、action

createSlice()

函数createSlice​,它负责生成 action 类型字符串、action creator 函数和 action 对象的工作。你所要做的就是为这个 slice 定义一个名称,编写一个包含 reducer 函数的对象,它会自动生成相应的 action 代码。name ​选项的字符串用作每个 action 类型的第一部分,每个 reducer 函数的键名用作第二部分。因此,"counter" ​名称 + "increment"​ reducer 函数生成了一个 action 类型 {type: "counter/increment"}​。除了 name ​字段,createSlice ​还需要我们为 reducer 传入初始状态值,以便在第一次调用时就有一个 state。在这种情况下,我们提供了一个对象,它有一个从 0 开始的 value 字段。

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: state => {
      // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。
      // 并不是真正的改变 state 因为它使用了 immer 库
      // 当 immer 检测到 "draft state" 改变时,会基于这些改变去创建一个新的
      // 不可变的 state
      state.value += 1
    },
    decrement: state => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    }
  }
})

createSlice​和createReducer​内部使用Immer库,可在内部编写mutation逻辑

thunk异步

thunk 是一种特定类型的 Redux 函数,可以包含异步逻辑。Thunk 是使用两个函数编写的:

  • 一个内部 thunk 函数,它以 dispatch​ 和 getState ​作为参数
  • 外部创建者函数,它创建并返回 thunk 函数
// 下面这个函数就是一个 thunk ,它使我们可以执行异步逻辑
// 你可以 dispatched 异步 action `dispatch(incrementAsync(10))` 就像一个常规的 action
// 调用 thunk 时接受 `dispatch` 函数作为第一个参数
// 当异步代码执行完毕时,可以 dispatched actions
export const incrementAsync = amount => dispatch => {
  setTimeout(() => {
    dispatch(incrementByAmount(amount))
  }, 1000)
}

组件

使用 useSelector 提取数据

useSelector​ 这个 hooks 让我们的组件从 Redux 的 store 状态树中提取它需要的任何数据。

const count = useSelector(selectCount)

每当一个 action 被 dispatch​ 并且 Redux store 被更新时,useSelector​ 将重新运行我们的选择器函数。如果选择器返回的值与上次不同,useSelector​ 将确保我们的组件使用新值重新渲染。

使用 useDispatch 来 dispatch action

useDispatch​ hooks 为我们完成了这项工作,并从 Redux store 中为我们提供了实际的 dispatch 方法:

const dispatch = useDispatch()

providing the 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'
import * as serviceWorker from './serviceWorker'

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

为了让像 useSelector​ 这样的 hooks 正常工作,我们需要使用一个名为 <Provider> ​的组件在幕后传递 Redux store,以便他们可以访问它。
我们在这里引用来自 app/store.js ​中创建的 store。然后,用 <Provider> ​包裹整个​ <App>​,并传入 store:<Provider store={store}>​。