如何在TypeScript中使用useContext与useReducer实现redux案列

129 阅读2分钟

如何在TypeScript中使用useContext与useReducer实现redux案列

useContextuseReducer合作可以完成类似的Redux库的操作,实现状态全局化并能统一管理状态useReducer 可以让代码具有更好的可读性和可维护性,它类似于Redux中的reducer ,reducer 这个函数接收两个参数,一个是状态,一个用来控制业务逻辑的判断参数。

案例:todo添加删除

1、文件目录结构

-- Todo
    |-- index.tsx 		 todo页面
    |-- TodoHeader.tsx 		 Todo头部组件
    |-- TodoItem.tsx 		 Todo项组件
    |-- TodoList.tsx 		 Todo列表组件
    |-- provider
    |   -- TodoProvider.tsx  让子孙组件共享数据
    |-- reducer
    |   -- TodoReducer.ts    管理及保存共享数据

2、TodoReducer设计

import { nanoid } from 'nanoid'

/**
 * 要传递数据的属性
 */
export interface StateProps {
  id: string
  name: string
}

/**
 * 操作状态的方法
 */
export interface ActionProps {
  type: 'addTodo' | 'deleteTodo'
  data: StateProps
}

/**
 * 定义TodoReducer函数
 * @param state 前一个状态
 * @param action 当前操作状态的方法
 * @returns 新状态
 */
const TodoReducer = (state: StateProps[], action: ActionProps): StateProps[] => {
  switch (action.type) {
    case 'addTodo':
      return [{ name: action.data.name, id: nanoid() }, ...state]
    case 'deleteTodo':
      return state.filter(item => item.id !== action.data.id)
    default:
      return state
  }
}

export default TodoReducer

3、TodoProvider的设计

import React, { FC, PropsWithChildren, createContext, useContext, Dispatch } from 'react'

import { StateProps, ActionProps } from '../reducer/TodoReducer'

/**
 * context传递的数据属性接口
 */
export interface ContextProps {
  state: StateProps[]
  dispatch: Dispatch<ActionProps>
}

// 创建一个TodoContext
const TodoContext = createContext<ContextProps>({} as ContextProps)

/**
 * 从TodoContext中取出Context数据的方法
 */
export const useTodoContext = (): ContextProps => useContext(TodoContext)

/**
 * 创建一个TodoProvider组件 用于传递数据
 */
const TodoProvider: FC<PropsWithChildren<ContextProps>> = props => {
  return (
    <TodoContext.Provider value={{ dispatch: props.dispatch, state: props.state }}>
      {props.children}
    </TodoContext.Provider>
  )
}

export default TodoProvider

4、如何在todo页面中使用

import React, { FC, useReducer } from 'react'
import reducer, { StateProps } from './reducer/TodoReducer'
import TodoProvide, { ContextProps } from './provider/TodoProvider'

import TodoHeader from './TodoHeader'
import TodoList from './TodoList'

/**
 * todo页面
 */
const Todo: FC = () => {
  // 初始化数据
  const initData: StateProps[] = [{ id: '0001', name: '我是一个todo项' }]
  // 使用useReducer存储数据
  const [state, dispatch] = useReducer(reducer, initData)
  // 要传递给子孙的数据
  const transferParameter: ContextProps = { state, dispatch }

  return (
    <div style={{ padding: '20px', backgroundColor: 'orange' }}>
      <h1> Todo Demo</h1>
      <TodoProvide {...transferParameter}>
        <TodoHeader />
        <TodoList />
      </TodoProvide>
    </div>
  )
}

export default Todo

5、TodoHeader组件取出数据使用

import React, { FC, useRef } from 'react'
import { useTodoContext } from './provider/TodoProvider'

/**
 *  TodoHeader组件
 */
const TodoHeader: FC = () => {
  const inputRef = useRef<HTMLInputElement>(null)
  
  const { dispatch } = useTodoContext() // 取出数据

  const addTodo = () => {
    if (inputRef.current === null || inputRef.current.value === '') return
    const todoName = inputRef.current.value
    dispatch({ type: 'addTodo', data: { id: '', name: todoName } })
    inputRef.current.value = ''
  }

  return (
    <div style={{ backgroundColor: '#1890ff', padding: '10px' }}>
      <h5>TodoHeader</h5>
      <div>
        <input ref={inputRef} type="text" placeholder="请输入" />
        <button onClick={addTodo}>添加</button>
      </div>
    </div>
  )
}

export default TodoHeader

6、TodoList组件取出数据使用

import React, { FC } from 'react'
import { useTodoContext } from './provider/TodoProvider'

import TodoItem from './TodoItem'

/**
 *  TodoList组件
 */
const TodoList: FC<{}> = () => {
  const { state } = useTodoContext() // 取出数据
  return (
    <div style={{ padding: '20px', backgroundColor: 'pink' }}>
      {state.map(item => (
        <TodoItem key={item.id} {...item} />
      ))}
    </div>
  )
}

export default TodoList

7、TodoItem取出数据使用

import React, { FC } from 'react'
import { StateProps } from './reducer/TodoReducer'
import { useTodoContext } from './provider/TodoProvider'

/**
 *  todo项组件
 */
const TodoItem: FC<StateProps> = ({ id, name }) => {
  // 取出数据
  const { dispatch } = useTodoContext()

  const deleteTodoById = (todoId: string) => {
    dispatch({ type: 'deleteTodo', data: { id: todoId, name: '' } })
  }

  return (
    <div style={{ margin: '3px', border: '1px solid #000' }}>
      <span style={{ marginRight: '10px' }}>
        id:{id}-------------name:{name}
      </span>
      <button onClick={() => deleteTodoById(id)}>删除</button>
    </div>
  )
}

export default TodoItem