react + ts 实践(二)

2,748 阅读4分钟

上一篇我们讲了项目构建好了之后的一些基础的ts用法,今天我们先跑题,简单的用hooks方式处理一下状态管理,来来来动手搞起来,此处附带上次项目地址,根据具体章节选择合适的版本下载

1、使用useReducer

useReducer可以提供一个类似于穷人版本的Redux功能,那么首先我们看看useReducer的基本用法:

const reducer = (state, action) => newState
const [state, dispatch] = useReducer(reducer, initValues)
// 改变值
dispatch(action)

参考上面的伪代码,我们可知useReducer的一个基本用法:

  • 1、初始化通过useReducer注册一个默认的state值,同时注册一个reducer方法用于触发及更新值;
  • 2、useReducer返回两个值statedispatch一个是值,一个是触发修改值的方法;
  • 3、通过dispatch将注册的action触发reducer方法进行值state值更新

基于上面我们来一手最基础版本的加减数字的小把戏,参考代码目录src/components/Count/index.tsx,代码如下


import React, { useReducer } from "react"

interface State {
  count: number;
}

type ActionType = "increment" | "decrement"

interface Action {
  type: ActionType
}

const reducer = (state: State, action: Action) => {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 }
    case "decrement":
      return { count: state.count - 1 }
    default:
      throw new Error()
  }
}

const Count: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0 })
  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  )
}

export default Count

注意,这里我们可以使用ActionType是通过|吧几个类型之关联起来,这种方式是ts中我们用的比较多的联合类型,我们在实际项目中具有多种情况值,可以通过联合类型的方式进行声明,例如string | number这样的;

2、createContext与useContext解决跨组件数据

接下来,我们需要接着上一次的代码,做一些改动调整,去实现一个老生常谈的todoList,具体界面如下:
image.png
我大概解释一下数据模型和通过reducer实现部分全局状态管理数据的办法,那么我们先看一下数据的处理办法,如下图所示:

具体我们可以用typescript中的interface对于我们的数据类型进行定义,同样我们可以参考下面示例的注释方式,这样我们就可以使用原子属性就会有一个非常良好的文字提示,注释写的好,妈妈就再也不用担心我迷路了~

image.png

下面是代码部分

export interface Task {
  /**
   * @description 任务id
   */
  id: string
  /**
   * @description 任务名称
   */
  text: string
}

export interface List {
  /**
   * @description 任务板块id
   */
  id: string
  /**
   * @description 板块名称
   */
  text: string
  /**
   * @description 当前板块任务list
   */
  tasks: Task[]
}

export interface AppState {
  lists: List[]
}

export interface AppStateContextProps {
  lists: List[]
}

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: "车票已经作废了" }]
    }
  ]
}

对于上面的todoList我们用这样的方式去创建数据模型,可以满足block + list的展现模式

接下来,我们通过React中提供的两个api(createContext, useContext)来去实现数据全局挂载了,如下图所示:

实际项目用法具体如下:

import React, { createContext, useContext } from 'react'

export interface Task {
  /**
   * @description 任务id
   */
  id: string
  /**
   * @description 任务名称
   */
  text: string
}

export interface List {
  /**
   * @description 任务板块id
   */
  id: string
  /**
   * @description 板块名称
   */
  text: string
  /**
   * @description 当前板块任务list
   */
  tasks: Task[]
}

export interface AppState {
  /**
   * @description 板块list
   */
  lists: List[]
}

export interface AppStateContextProps {
  lists: List[]
  getTasksByListId(id: string): Task[]
}

const appStateReducer = (state: AppState, action: any): AppState => state

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 getTasksByListId = (id: string) => {
    return appData.lists.find((i: List) => i.id === id)?.tasks || []
  }

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

export const useAppState = () => {
  return useContext(AppStateContext)
}

针对我们AppStateProvider,我们需要挂载到组件的最外层,这样我们就可以在组件内部去使用useContext返回的内容值,因为demo中有现在所有内容都卸载App.tsx上面,AppStateProvider就需要挂载在App.tsx外层,
具体如下:

import React from 'react'
// import Count from './components/Count';
import Todo from './pages/Todo'

import { AppStateProvider } from './pages/Todo/TodoStateContext'
import { AppContainer } from './styles'


const App: React.FC = () => {

  return (
    <AppContainer>
      <AppStateProvider>
        <Todo />
      </AppStateProvider>
      {/* useReducer模拟写法 */}
      {/* <Count></Count> */}
    </AppContainer>
  );
}

export default App;

接下来就是我们在各个分散的组件中使用方式了,首先是Columns列中使用的办法

import { ColumnContainer, ColumnTitle } from "../../styles"
import Card from "./Card"
import { Task, useAppState } from "./TodoStateContext"

type ColumnProps = {
  text: string
  id: string
}

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

  const { getTasksByListId } = 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}`}  />
        ))
      }
    </ColumnContainer>
  )
}

export default Column

通过上面一系列操作,我们用完成了组件可以使用当前AppStateProvider注入进来的数据,做一系列操作,勉强可以跨多个组件做一些公共数据的处理,下面我们将重点放在如何通过useReducer来去做数据的增删改查,本章到此,附上本章的代码tag地址,感兴趣本系列内容的兄弟姐妹们,点个关注~感谢各位看官转评赞一键三连我也不介意~~~~