React + ts 实践(三)useReducer实现多组件数据共享

1,602 阅读3分钟

  上一章 我们创建了一个todolist,数据什么都是写死,本章我们将要在todoList上面增加对应的功能操作,建议大家下载代码一起配合文章一起来学习,我尽量用图画相结合的方式将结构说明白,下面是本章的代码tags

  在开始本节的实践之前,我现将系统的代码结构示意图展示在此处,方便大家结合代码迅速上手,下面我将todoList的组件结构展示在这里

  接下来我们将AppStateContext抽离到单独的文件目录下,我们都放在src/state目录下,具体可以参考gitee上面本章的tag下的代码

1、定义我们的Reducer和修改Action动作

接下来,为了方便我们理解这些操作,我们将TodoStateContext模块构造画图说明一下:

  大概是这样的一个构成,上一讲我们用到了useAppState获取我们在TodoStateContext中写入的一些数据及方法,但是作为一个todoList,我们不仅仅考虑的是展示,同时考虑如何添加,因为我们考虑是用useReducer的方式管理数据,通过上一讲我们了解过useReducer的基本用法,话不多说,我们创建action.ts,来定义我们的操作:

import { Action } from "./type"

// 创建任务
export const addTask = (text: string, listId: string): Action => ({
  type: 'ADD_TASK',
  payload: {
    text,
    listId
  }
})
// 创建列表
export const addList = (text: string): Action => ({
  type: 'ADD_LIST',
  payload: text
})

下面我们调整调整Reducer方法,这里大家参考我这里的写法

import { nanoid } from "nanoid";
import { Action, AppState } from "./type";

// 调整数据方法
export const appStateReducer = (drap: AppState, action: Action): AppState | void => {
  switch (action.type) {
    case "ADD_LIST":
      drap.lists.push({
        id: nanoid(),
        text: action.payload,
        tasks: []
      })
      break;
    case "ADD_TASK":
      const { listId, text } = action.payload
      const targetIndex = drap.lists.findIndex((item) => item.id === listId)
      drap.lists[targetIndex].tasks.push({
        id: nanoid(),
        text
      })
      break
    default:
      break;
  }
}

2、调整AppStateContext

  上面我们完成Action及Reducer定义,那么我们要考虑的是如何将这些数据传递给所有组件来去使用,这就得用到我们通过creatContext方式创建出来的数据注入组件AppStateProvider,下面我们就将这些数据及方法挂载到AppStateProvider

import React, { createContext, useContext, Dispatch } from 'react'
import { List, Task, AppState, Action} from './type'
import { useImmerReducer } from 'use-immer'
import { appStateReducer } from './Reducer'

export interface AppStateContextProps {
  lists: List[]
  getTasksByListId(id: string): Task[]
  dispatch: Dispatch<Action>
}

const AppStateContext = createContext<AppStateContextProps>(
  {} as AppStateContextProps
)

const appData: AppState = {
  lists: [
    {
      id: "0",
      text: "待处理",
      tasks: [{ id: "c0", text: "我想去新疆天山" }]
    },
    {
      id: "1",
      text: "进行中",
      tasks: [{ id: "c2", text: "不好意思居家隔离了" }]
    },
    {
      id: "2",
      text: "已完成",
      tasks: [{ id: "c3", text: "车票已经作废了" }]
    }
  ]
}

export const AppStateProvider: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
  // 创建我们子组件中用到的数据
  const [todoInfo, dispatch] = useImmerReducer(appStateReducer, appData)

  const { lists } = todoInfo
  const getTasksByListId = (id: string) => {
    return lists.find((i: List) => i.id === id)?.tasks || []
  }

  return (
    <AppStateContext.Provider value={{ ...todoInfo, getTasksByListId, dispatch}}>
      {children}
    </AppStateContext.Provider>
  )
}

export const useAppState = () => {
  return useContext(AppStateContext)
}
  • 1、这里先推荐一个库immerjs ,还有衍生工具库 use-immer;
  • 2、Dispatch是react支持的里面的一个泛型函数,定义是这样的type Dispatch<A> = (value: A) => void
  • 3、关于useImmerReducer是保证了state在使用期间不会被深拷贝,我们的操作都是基于原始数组或者对象上进行的;

3、组件中使用dispatch

  组件中我们可以通过获取dispatch方法,将我们的更新方法提供给dispatch的方式进行更新;具体如下:

import React from 'react'
import AddNewItem from './AddNewItem';
import Column from './Columns';
import { useAppState } from '../../state/TodoState/AppStateContext';
import { List } from '../../state/TodoState/type';
import { addList } from '../../state/TodoState/Appction';

const Todo: React.FC = () => {
  
  const { lists, dispatch } = useAppState()
  
  return (
    <>
      {lists.map((i: List) => (<Column text={i.text} id={i.id} key={`${i.id}${i.text}`} />))}
      <AddNewItem
        toggleButtonText='添加另一个'
        onAdd={(text: string) => dispatch(addList(text))}
        ></AddNewItem>
    </>
  );
}

export default Todo

// Column.tsx
import { ColumnContainer, ColumnTitle } from "../../styles"
import Card from "./Card"
import { useAppState } from "../../state/TodoState/AppStateContext"
import { Task } from "../../state/TodoState/type"
import AddNewItem from "./AddNewItem"
import { addTask } from "../../state/TodoState/Appction"

type ColumnProps = {
  text: string
  id: string
}

const Column: React.FC<ColumnProps> = ({ text, id }) => {

  const { getTasksByListId, dispatch } = useAppState()
  const tasks = getTasksByListId(id)

  return (
    <ColumnContainer>
      <ColumnTitle>{text}</ColumnTitle>
      {
        tasks.map((i: Task) => (
          <Card id={i.id} text={i.text} key={`${i.id}${i.text}`}  />
        ))
      }
      <AddNewItem
        toggleButtonText='添加下一个任务'
        onAdd={(text: string) => dispatch(addTask(text, id))}
      ></AddNewItem>
    </ColumnContainer>
  )
}

export default Column

  如此我们基本完成了一个通过createContextuseReducer方式创建的多组件之间数据共享,代码看起来比较枯燥,我们通过图画的方式进行解读基本原理:

  希望上面的说明能帮助到你,喜欢我的文章就点评赞来一个,三连我也不介意~~ 谢谢各位看官的支持,后面再次附上本章代码tag地址