基于网站开发的app,网络管理软件app,收图片的网站,网站开发赚钱吗?[React 进阶系列] React Context 案例学习#xff1a;使用 TS 及 HOC 封装 Context
具体 context 的实现在这里#xff1a;[React 进阶系列] React Context 案例学习#xff1a;子组件内更新父组件的状态。
根据项目经验是这样的#xff0c;自从换了 TS 之后#xff0c;…[React 进阶系列] React Context 案例学习使用 TS 及 HOC 封装 Context
具体 context 的实现在这里[React 进阶系列] React Context 案例学习子组件内更新父组件的状态。
根据项目经验是这样的自从换了 TS 之后就再也没有二次封装过了使用 TS 真的可以有效解决 typo 和 intellisense 的问题
这里依旧使用一个简单的 todo 案例去完成
使用 TypeScript
结构方面采用下面的结构
❯ tree src
src
├── App.css
├── App.test.tsx
├── App.tsx
├── context
│ └── todoContext.tsx
├── hoc
├── index.css
├── index.tsx
├── logo.svg
├── models
│ └── todo.type.ts
├── react-app-env.d.ts
├── reportWebVitals.ts
└── setupTests.ts4 directories, 11 files创建 type
这里的 type 指的是 Todo 的类型以及 context 类型这是一个简单案例结构就不会特别的复杂 todo.type.ts export type ITodo {id: number;title: string;description: string;completed: boolean;
};todoContext.tsx import { ITodo } from ../models/todo.type;export type TodoContextType {todos: ITodo[];addTodo: (todo: ITodo) void;removeTodo: (id: number) void;toggleTodo: (id: number) void;
};这种 type 的定义我基本上说 component 在哪里就会定义在哪里而不会单独创建一个文件在 model 下面去实现当然这种其实也挺看个人偏好的……
创建 context
这里主要就是提供一个 context以供在其他地方调用 useContext 而使用
export const TodoContext createContextTodoContextType | null(null);这里 是接受 context 的类型我个人偏向会使用一个具体的 type 以及 null。其原因是 JS/TS 默认没有初始化和没有实现的变量都是 undefined也因此使用 undefined 的指向性不是很明确。
而使用 null 代表这个变量存在因此更具有指向性
虽然在 JS 实现中一般我都偷懒没设置默认值……
没有报错真的会忘……超小声 bb
创建 Provider
Provider 的实现如下
const TodoProvider: FC{ children: ReactNode } ({ children }) {const [todos, settodos] useStateITodo[]([{id: 1,title: Todo 1,completed: false,description: Todo 1,},{id: 2,title: Todo 2,completed: false,description: Todo 1,},]);const addTodo (todo: ITodo) {const newTodo: ITodo {id: todos.length 1,title: todo.title,description: todo.description,completed: false,};settodos([...todos, newTodo]);};const removeTodo (id: number) {const newTodos todos.filter((todo) todo.id ! id);settodos(newTodos);};const toggleTodo (id: number) {const newTodos todos.map((todo) {if (todo.id id) {return { ...todo, completed: !todo.completed };}return todo;});settodos(newTodos);};return (TodoContext.Providervalue{{todos,addTodo,removeTodo,toggleTodo,}}{children}/TodoContext.Provider);
};export default TodoProvider;其实也没什么复杂的主要就是一个 FCChildPops 这个用法这代表当前的函数是一个 Functional Component它只会接受一个参数并且它的参数会是一个 ReactNode
添加 helper func
如果想要直接使用 const {} useContext(TodoContest) 的话TS 会报错——默认值是 null。所以这个时候可以创建一个 helper func保证返回的 context 一定有值
export const useTodoContext () {const context useContext(TodoContext);if (!context) {throw new Error(useTodoContext must be used within a TodoProvider);}return context;
};这样可以保证调用 useTodoContext 一定能够获取值。两个错误对比如下 不过这个时候页面渲染还是有一点问题因为没有提供对应的 provider 使用 HOC
一般的解决方法有两种 直接在 Main 上嵌套一个 Provider 这个的问题就在于Main 本身就需要调用 context 中的值如果在这里嵌套的话就会导致 Main 组件中无法使用 context 中的值 在上层组件中添加对应的 provider 这样得到 App 层去修改可以但是有的情况并不是一个非常的适用尤其是多个 Provider 嵌套而其中又有数据依赖的情况下将 Provider 一层一层往上推意味着创建多个 component 去实现
使用 HOC 的方法是兼具 1 和 2 的解决方案具体实现如下
import { ComponentType } from react;
import TodoProvider, { TodoContextType } from ../context/todoContext;const withTodoContext (WrappedComponent: ComponentTypeany) () (TodoProviderWrappedComponent //TodoProvider);export default withTodoContext;这样 Main 层面的调用如下
import React from react;
import { Button } from mui/material;
import AddIcon from mui/icons-material/Add;
import { useTodoContext } from ../context/todoContext;
import withTodoContext from ../hoc/withTodoContext;const Main () {const { todos } useTodoContext();return (div classNametodo-maininput typetext /Button classNameadd-btnAddIcon //Buttonbr /ul{todos.map((todo) (li key{todo.id}{todo.title}/li))}/ul/div);
};export default withTodoContext(Main);补充一下如果 HOC 需要接受参数的话实现是这样的
const withExampleContext (WrappedComponent: ComponentTypeany) (moreProps: ExampleProps) (ExampleProviderWrappedComponent {...moreProps} //ExampleProvider);export default withExampleContext;这样导出的方式还是使用 withExampleContext(Component)不过上层可以用 Component prop1{} prop2{} / 的方式向 Component 中传值
调用
完整实现如下
const Main () {const [newTodo, setNewTodo] useState();const { todos, addTodo, toggleTodo } useTodoContext();const onChangeInput (e: React.ChangeEventHTMLInputElement) {setNewTodo(e.target.value);};const onAddTodo () {addTodo({title: newTodo,description: ,completed: false,});setNewTodo();};const onCompleteTodo (id: number) {toggleTodo(id);};return (div classNametodo-maininput typetext value{newTodo} onChange{onChangeInput} /Button classNameadd-btn onClick{onAddTodo}AddIcon //Buttonbr /ul{todos.map((todo) (likey{todo.id}style{{textDecoration: todo.completed ? line-through : none,cursor: pointer,}}onClick{() onCompleteTodo(todo.id)}{todo.title}/li))}/ul/div);
};export default withTodoContext(Main);效果如下 TS 提供的自动提示如下