[React 进阶系列] React Context 案例学习:使用 TS 及 HOC 封装 Context

[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.ts

4 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 = createContext<TodoContextType | null>(null);

这里 <> 是接受 context 的类型,我个人偏向会使用一个具体的 type 以及 null。其原因是 JS/TS 默认没有初始化和没有实现的变量都是 undefined,也因此使用 undefined 的指向性不是很明确。

而使用 null 代表这个变量存在,因此更具有指向性

虽然在 JS 实现中一般我都偷懒没设置默认值……

没有报错真的会忘……超小声 bb

创建 Provider

Provider 的实现如下:

const TodoProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const [todos, settodos] = useState<ITodo[]>([
    {
      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.Provider
      value={{
        todos,
        addTodo,
        removeTodo,
        toggleTodo,
      }}
    >
      {children}
    </TodoContext.Provider>
  );
};

export default TodoProvider;

其实也没什么复杂的,主要就是一个 FC<ChildPops> 这个用法,这代表当前的函数是一个 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

一般的解决方法有两种:

  1. 直接在 Main 上嵌套一个 Provider

    这个的问题就在于,Main 本身就需要调用 context 中的值,如果在这里嵌套的话就会导致 Main 组件中无法使用 context 中的值

  2. 在上层组件中添加对应的 provider

    这样得到 App 层去修改,可以,但是有的情况并不是一个非常的适用,尤其是多个 Provider 嵌套,而其中又有数据依赖的情况下,将 Provider 一层一层往上推意味着创建多个 component 去实现

使用 HOC 的方法是兼具 1 和 2 的解决方案,具体实现如下:

import { ComponentType } from 'react';
import TodoProvider, { TodoContextType } from '../context/todoContext';

const withTodoContext = (WrappedComponent: ComponentType<any>) => () =>
  (
    <TodoProvider>
      <WrappedComponent />
    </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 className="todo-main">
      <input type="text" />
      <Button className="add-btn">
        <AddIcon />
      </Button>

      <br />

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default withTodoContext(Main);

补充一下,如果 HOC 需要接受参数的话,实现是这样的:

const withExampleContext =
  (WrappedComponent: ComponentType<any>) => (moreProps: ExampleProps) =>
    (
      <ExampleProvider>
        <WrappedComponent {...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.ChangeEvent<HTMLInputElement>) => {
    setNewTodo(e.target.value);
  };

  const onAddTodo = () => {
    addTodo({
      title: newTodo,
      description: '',
      completed: false,
    });

    setNewTodo('');
  };

  const onCompleteTodo = (id: number) => {
    toggleTodo(id);
  };

  return (
    <div className="todo-main">
      <input type="text" value={newTodo} onChange={onChangeInput} />
      <Button className="add-btn" onClick={onAddTodo}>
        <AddIcon />
      </Button>

      <br />

      <ul>
        {todos.map((todo) => (
          <li
            key={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 提供的自动提示如下:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/438740.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

光谱整形1

华为张德江&#xff1a;下一代光传送网将走向400G80波WDM系统_通信世界网 (cww.net.cn) 张德江指出&#xff0c;400G WDM系统具有三大基本特征&#xff1a;支持400G80波&#xff0c;单纤32T超大容量&#xff0c;传输距离与100G相当&#xff1b;支持32维以上的光交叉&#xff1…

微前端之使用无界创建一个微前端项目

wujie 使用手册 使用简介 主应用配置 安装 wujie依赖main.js配置 是否开启预加载 生命周期函数 – lifecycle.js配置 子应用配置 跨域设置运行模式 生命周期改造 在主应用中&#xff0c;使用wujie&#xff0c;将子应用引入到主应用中去 wujie 使用手册 wujie 是一个基于 Web…

Linux环境下使用interrupt方式操作UART

目录 概述 1 Linux环境下UART设备 2 轮询方式操作UART功能实现 2.1 打开串口函数&#xff1a;usr_serial_open 2.2 关闭串口函数&#xff1a; usr_serial_close 2.3 发送数据函数&#xff1a; usr_serial_sendbytes 2.4 接收数据函数&#xff1a; usr_serial_readinterr…

Android 性能优化--APK加固(2)加密

文章目录 字符串加密图片加密如何避免应用被重新签名分发APK 加壳的方案简析DEX加密原理及实现 本文首发地址&#xff1a;https://h89.cn/archives/212.html 最新更新地址&#xff1a;https://gitee.com/chenjim/chenjimblog 通过 前文 介绍&#xff0c;我们知晓了如何使用代码…

前端网络请求异步处理——Promise使用记录

Promise是ES6中新增的一个处理复杂异步请求的工具&#xff0c;其主要形式为&#xff1a; const baseUrl http://localhost:80 export const $request (param {}) > {console.log(请求参数, param)return new Promise((resolve, reject) > {wx.request({url: baseUrl …

SpringMVC拦截器和过滤器执行顺序及区别

拦截器&#xff08;Inteceptor&#xff09;和过滤器&#xff08;Filter&#xff09;执行顺序&#xff1f; 拦截器和过滤器区别&#xff1f; 1、拦截次数不同&#xff1a; 过滤器&#xff1a;一次请求只能被一个过滤器拦截一次&#xff0c;它们按照在web.xml中的声明顺序依次执…

Python Web开发记录 Day7:Django(Web框架) part 1

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 七、Django1、快速了解Django①概述②核心功能③…

css flex 布局换行

默认使用display: flex;是不换行的&#xff0c;只需要加上flex-wrap: wrap;就行了&#xff0c;效果图 .app-center {display: flex;flex-wrap: wrap;justify-content:flex-start; } 通过上面我们发现虽然时间换行了&#xff0c;但是每行的边距不一样 加上这个就行了&#xff…

华为配置DHCP Snooping防止DHCP Server仿冒者攻击示例

配置DHCP Snooping防止DHCP Server仿冒者攻击示例 组网图形 图1 配置DHCP Snooping防止DHCP Server仿冒者攻击组网图 DHCP Snooping简介配置注意事项组网需求配置思路操作步骤配置文件 DHCP Snooping简介 在一次DHCP客户端动态获取IP地址的过程中&#xff0c;DHCP Snoopi…

开源分子对接程序rDock使用方法(1)-Docking in 3 steps

欢迎浏览我的CSND博客&#xff01; Blockbuater_drug …点击进入 文章目录 前言一、Docking in 3 steps 标准对接rDock 的基本对接步骤及注意事项 二、 三步对接案例Step 1. 结构文件准备Step 2. 产生对接位点Step 3. 运行分子对接3.1 检查输入文件3.2 测试-只进行打分3.3 运行…

maven项目结构管理统一项目配置操作

一、maven分模块开发 Maven 分模块开发 1.先创建父工程&#xff0c;pom.xml文件中&#xff0c;打包方式为pom 2.然后里面有许多子工程 3.我要对父工程的maven对所有子工程进行操作 二、解读maven的结构 1.模块1 <groupId>org.TS</groupId><artifactId>TruthS…

利用CesiumJS开发模拟飞机飞行的应用(添加飞行轨迹)

上一节介绍了利用CesiumJS开发模拟飞机飞行的应用(一&#xff0c;基本功能)&#xff0c;本节介绍如何在上节基础上添加飞行轨迹所使用数据是从旧金山到哥本哈根的真实航班&#xff0c;并使用 FlightRadar24收集的雷达数据。 添加单个3D点对象 FlightRadar24 使用多种方法&#…

[c/c++] const

const 和 #define 的区别 ? const 和指针一块出现的时候&#xff0c;到底谁不能修改 &#xff1f; const 和 volatile 能同时修饰一个变量吗 ? const 在 c 中的作用 ? 1 const 和 #define 的区别 const 和 #define 的相同点&#xff1a; (1) 常数 const 和 #define 定…

51-27 DirveVLM:自动驾驶与大型视觉语言模型的融合

本文由清华大学和理想汽车共同发布于2024年2月25日&#xff0c;论文名称DRIVEVLM: The Convergence of Autonomous Driving and Large Vision-Language Models. DriveVLM是一种新颖的自动驾驶系统&#xff0c;旨在针对场景理解挑战&#xff0c;利用最近的视觉语言模型VLM&…

蓝桥杯——web(ECharts)

ECharts 初体验 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><script src"echarts.js">&l…

【APB协议 UVM_Sequencer Driver Monitor_2024.03.04】

apb协议 写时序 地址、写信号、PSEL、写数据信号同时发生变化&#xff0c;即传输的第一个时钟被称为SETUP周期。在下个时钟上升沿,PENABLE信号拉高&#xff0c;表示ENABLE周期&#xff0c;在该周期内&#xff0c;数据、地址以及控制信号都必须保持有效。整个写传输在这个周期…

寻找旋转排序数组中的最小值[中等]

优质博文IT-BLOG-CN 一、题目 已知一个长度为n的数组&#xff0c;预先按照升序排列&#xff0c;经由1到n次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组nums [0,1,2,4,5,6,7]在变化后可能得到&#xff1a; 【1】若旋转4次&#xff0c;则可以得到[4,5,6,7,0,1,2…

perf的安装与迁移

前言 perf是性能优化很重要的工具之一&#xff0c;本篇博客就来看一下perf的安装以及遇到的问题。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关注本博主并订阅本专栏&#xff0c;一起讨论一起学…

Unity性能优化篇(四) GPU Instancing

使用GPU Instancing可以在一个Draw Call中同时渲染多个相同或类似的物体&#xff0c;从而减少CPU和GPU的开销。 官方文档&#xff1a;https://docs.unity3d.com/Manual/GPUInstancing.html 启用GPU Instancing&#xff0c;我们可以选中一个材质&#xff0c;然后在Inspector窗口…

【你也能从零基础学会网站开发】Web建站之HTML+CSS入门篇 传统布局和Web标准布局的区别

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 传统布局与…