React Redux is the official React UI bindings layer for Redux. It lets your React components read data from a Redux store, and dispatch actions to the store to update state.

(Excerpted from react-redux.js.org)

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

想做全局变量管理,于是来学

基本概念

数据流更新动画

单向数据流

有以下基本流程:

  • 用 state 来描述应用程序在特定时间点的状况
  • 基于 state 来渲染出 view
  • 当发生某些事情时(例如用户单击按钮),state 会根据发生的事情进行更新,生成新的 state
  • 基于新的 state 重新渲染 view

不可变性

原来的对象或数组中的内容不改变,通过复制的方式先获取一份 copy,然后更新 copy 中的内容。

const obj = {
  a: {
    // 为了安全的更新 obj.a.c,需要先复制一份
    c: 3
  },
  b: 2
}

const obj2 = {
  // obj 的备份
  ...obj,
  // 覆盖 a
  a: {
    // obj.a 的备份
    ...obj.a,
    // 覆盖 c
    c: 42
  }
}

const arr = ['a', 'b']
// 创建 arr 的备份,并把 c 拼接到最后。
const arr2 = arr.concat('c')

// 或者,可以对原来的数组创建复制体
const arr3 = arr.slice()
// 修改复制体
arr3.push('c')

Redux 期望所有状态更新都是使用不可变的方式

state, view, action

  • state: 储存数据的一个个“状态”
  • view: 当前绘制出的 UI
  • action: 由用户交互而触发的事件,可以引起 state 的更新,进而重新渲染 view.
    • action 是一个具有 type 字段的普通 JavaScript 对象。
    • type 字段是一个字符串,给这个 action 一个描述性的名字,比如"todos/todoAdded"。我们通常把那个类型的字符串写成“域/事件名称”,其中第一部分是这个 action 所属的特征或类别,第二部分是发生的具体事情。
    • action 对象可以有其他字段,其中包含有关发生的事情的附加信息。按照惯例,我们将该信息放在名为 payload 的字段中。
// Example for action
const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

reducer

reducer 是一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态。函数签名是:(state, action) => newState

你可以将 reducer 视为一个事件监听器,它根据接收到的 action(事件)类型处理事件。

Reducer 必需符合以下规则:

  • 仅使用 stateaction 参数计算新的状态值
  • 禁止直接修改 state。必须通过复制现有的 state 并对复制的值进行更改的方式来做 不可变更新(immutable updates)
  • 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码
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

当前 Redux 应用的状态存在于一个名为 store 的对象中。

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

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

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

dispatch

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

Reducer 就像事件监听器一样,当它们收到关注的 action 后,它就会更新 state 作为响应。

store.dispatch({ type: 'counter/increment' })

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

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

store.dispatch(increment())

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

辅助函数类型

action creator

action creator ([text => action])是一个创建并返回一个 action 对象的函数。它的作用是让你不必每次都手动编写 action 对象.

const addTodo = text => {
  return {
    type: 'todos/todoAdded',
    payload: text
  }
}

selector

Selector 函数可以从 store 状态树中提取指定的片段.

const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

functions provided by toolkit

createSlice

可以定义 初始状态, reducer 函数, slice name, 然后自动生成相应的action creator 和 action type.

内部重写了实现逻辑,可以使用可变的方式来进行状态修改。

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

interface CounterState {
  value: number
}

const initialState = { value: 0 } as CounterState

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment(state) {
      state.value++
    },
    decrement(state) {
      state.value--
    },
    incrementByAmount(state, action: PayloadAction<number>) {
      state.value += action.payload
    },
  },
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

Return values:

{
    name : string,
    reducer : ReducerFunction,
    actions : Record<string, ActionCreator>,
    caseReducers: Record<string, CaseReducer>
}

Redux 数据流

具体来说,对于 Redux,我们可以将这些步骤分解为更详细的内容:

  • 初始启动:
    • 使用最顶层的 root reducer 函数创建 Redux store
    • store 调用一次 root reducer,并将返回值保存为它的初始 state
    • 当 UI 首次渲染时,UI 组件访问 Redux store 的当前 state,并使用该数据来决定要呈现的内容。同时监听 store 的更新,以便他们可以知道 state 是否已更改。
  • 更新环节:
    • 应用程序中发生了某些事情,例如用户单击按钮
    • dispatch 一个 action 到 Redux store,例如 dispatch({type: 'counter/increment'})
    • store 用之前的 state 和当前的 action 再次运行 reducer 函数,并将返回值保存为新的 state
    • store 通知所有订阅过的 UI,通知它们 store 发生更新
    • 每个订阅过 store 数据的 UI 组件都会检查它们需要的 state 部分是否被更新。
    • 发现数据被更新的每个组件都强制使用新数据重新渲染,紧接着更新网页

数据流更新动画

Practice

好,现在我已经掌握了 React Redux 的基本用法了

img

Store.tsx

import {configureStore, createSlice} from '@reduxjs/toolkit';

const DataSlice = createSlice({
  name: 'data',
  initialState: {loggedIn: false, role: '', childProps: {}, parentProps: {}},
  reducers: {
    setLoggedIn: (state, action) => {
      state.loggedIn = action.payload;
    },
    setRole: (state, action) => {
      state.role = action.payload;
    },
    setChildProps: (state, action) => {
      state.childProps = action.payload;
    },
    setParentProps: (state, action) => {
      state.parentProps = action.payload;
    },
  },
});

export const {setLoggedIn, setRole, setChildProps, setParentProps} =
  DataSlice.actions;

export default configureStore({
  reducer: {data: DataSlice.reducer},
});

App.tsx

import {Provider} from 'react-redux';
import Child from './UI/Child';

const App = () => {
  return (
    <Provider store={Store}>
      <View />
    </Provider>
  );
};

然后就可以在代码中调用 Store 提供的 setLoggedIn, setRole, setChildProps, setParentProps 这些 action creators 和 dispatch 方法了.

Reference