如何在TypeScript中使用useContext与useReducer实现redux案列
useContext
和useReducer
合作可以完成类似的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