实战用 Custom Hook + TS泛型实现 useArray

Posted 程序边界

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实战用 Custom Hook + TS泛型实现 useArray相关的知识,希望对你有一定的参考价值。

文章目录


一、题目

完善自定义 Hook —— useArray ,使其能够完成 tryUseArray 组件中测试的功能:

  • 入参:数组
  • 返回值:
    • value:最新状态的数组;
    • add:添加元素;
    • removeIndex:移除数组特定位置的元素;
    • clear:清空数组;

相关文件代码:

  • src\\utils\\index.ts
import  useEffect, useState  from "react";

export const useMount = (cbk: () => void) => useEffect(() => cbk(), []);

export const useArray = () => ;
  • src\\tryUseArray.tsx
import  useArray, useMount  from "utils";

const TryUseArray = () => 
  const persons:  name: string; age: number [] = [
     name: "jack", age: 25 ,
     name: "ma", age: 22 ,
  ];
  const  value, clear, removeIndex, add  = useArray(persons);

  useMount(() => 
    // 期待这里报错:Property 'notExist' does not exist on type ' name: string; age: number; []'.
    // console.log(value.notExist);
    // 期待这里报错:Property 'age' is missing in type ' name: string; ' but required in type ' name: string; age: number; '.
    // add( name: "david" );
    // 期待这里报错:Argument of type 'string' is not assignable to parameter of type 'number'.
    // removeIndex("123");
  );
  return (
    <div>
      /*期待: 点击以后增加 john */
      <button onClick=() => add( name: "john", age: 22 )>add john</button>
      /*期待: 点击以后删除第一项*/
      <button onClick=() => removeIndex(0)>remove 0</button>
      /*期待:点击以后清空列表*/
      <button style= marginBottom: "50px"  onClick=() => clear()>
        clear
      </button>
      value.map((person, index) => (
        <div key=index style= marginBottom: "30px" >
          <span style= color: "red" >index</span>
          <span>person.name</span>
          <span>person.age</span>
        </div>
      ))
    </div>
  );
;

export default TryUseArray;
  • src\\App.tsx
import "./App.css";
import TryUseArray from "tryUseArray";

function App() 
  return (
    <div className="App">
      <TryUseArray />
    </div>
  );


export default App;

答 答
案 案
在 在
后 后
面 面
, ,
没 没
有 有
完 完
成 成
不 不
要 要
偷 偷
看 看
哦 哦
! !

二、答案(非标准)

import  useEffect, useState  from "react";

// 我的练习作业
// export const useArray = <T>(array: T[]) => 
//   const [value, setValue] = useState(array)
//   const clear = () => setValue([])
//   const removeIndex = (index: number) => setValue([...value].filter((item, _index) => _index !== index))
//   const add = (item: item) => setValue([...value, item]))
//   return 
//     value, clear, removeIndex, add
//   
// 

export const useArray = <T>(array: T[]) => 
  const [value, setValue] = useState(array);
  return 
    value,
    add: (item: T) => setValue([...value, item]),
    removeIndex: (index: number) => 
      const temp = [...value];
      temp.splice(index, 1);
      setValue(temp);
    ,
    clear: () => setValue([]),
  ;
;

三、关键知识点

1.Custom Hook

官方文档:自定义 Hook – React

关键点

  • 定义 Custom Hook 是一个函数,名字必须以 use 开头
  • hook 只能在 React 函数组件 或其他 Hook 函数中调用(普通 js/ts 函数中不可用)
  • 相同的 Hook 不共享 state (重用状态逻辑的机制,所有 state 和副作用都是完全隔离的)
  • 不要在循环,条件或嵌套函数中调用 Hook(建议在 Hook 内部使用循环,条件或嵌套函数)
  • React 16.8+ 中使用

案例

useMount

  • 封装
export const useMount = (cbk: () => void) => useEffect(() => cbk(), []);
  • 调用
import  useMount  from "utils";

const [list, setList] = useState([]);

useMount(() => 
  fetch(`$apiUrl/list`).then(async (res) => 
    if (res.ok) 
      setList(await res.json());
    
  );
);

useDebounce

  • 封装
/**
 * @param  值  val
 * @param  延时:默认 1000  delay
 * @returns 在某段时间内多次变动后最终拿到的值(delay 延迟的是存储在队列中的上一次变化)
 */
export const useDebounce = <V>(val: V, delay: number = 1000) => 
  const [tempVal, setTempVal] = useState(val);

  useEffect(() => 
    // 每次在 val 变化后,设置一个定时器
    const timeout = setTimeout(() => setTempVal(val), delay);
    // 每次在上一个 useEffect 处理完以后再运行(useEffect 的天然功能即是在运行结束的 return 函数中清除上一个(同一) useEffect)
    return () => clearTimeout(timeout);
  , [val, delay]);

  return tempVal;
;
  • 调用
import  useDebounce  from "utils";
// 对 param 进行防抖处理
const lastParam = useDebounce(param);
const [list, setList] = useState([]);

useEffect(() => 
  fetch(
    // name=$param.name&personId=$param.personId
    `$apiUrl/projects?$qs.stringify(lastParam)`
  ).then(async (res) => 
    if (res.ok) 
      setList(await res.json());
    
  );
, [lastParam]);

注意区别于 节流

拓展学习:

2.TS 泛型

官方文档:

关键点

  • 不预先指定其具体的类型,而在使用的时候再进行定义
  • 函数是对“值”的编程,泛型是对“类型”的编程
  • 泛型是类型的变量

拓展学习:

ts类泛型接口扩展

ts定义类

es5的类是这样定义的

在这里插入图片描述

es6类是这样定义的

在这里插入图片描述

ts中也是类似

在这里插入图片描述
实例化的属性需要在一开头定义,
在这里插入图片描述
在这里插入图片描述
二缺一也不行。

ts的继承

在这里插入图片描述
也是使用extends跟super实现继承。

类里面的修饰符

ts中提供三种修饰符
public 公有 哪里都可以访问,
protected: 保护类型,在类子类可以访问,类外不行,
private:私有,只能在自己类里访问,其他对方访问报错。
属性不加修饰符默认就是公有
在这里插入图片描述
该属性公共,在哪都可以访问在这里插入图片描述
该属性受保护,只能在类或者子类使用
在这里插入图片描述
该属性私有,只能在自己的类里面访问
在这里插入图片描述
对方法也是一样的。

静态属性,静态方法

es5中静态属性静态方法

在这里插入图片描述

其实可以这样理解,Son虽说是函数,本质也是对象,下面的操作是直接在Son这个对象上加属性,而不会被new后分配给实例,因为他们是Son对象上的属性和方法。而函数里面的this.run就是实例方法,会被实例继承的。

ts的类的静态方法

在这里插入图片描述
只需要在方法前加上static,定义是静态方法,不会被实例继承。
在这里插入图片描述
静态属性也是加static定义。

多态

父类定义一个方法不实现,让继承其的子类实现,每个子类有不同的表现
在这里插入图片描述
父类定义run方法,每个继承的子类实现该方法。这种写法只是表明多态是什么,结合抽象类能具体实现

抽象类 absrtact,要求子类比如包含某方法(抽象方法只能在抽象类使用)

在这里插入图片描述
父类不能实现该方法,交由子类实现
在这里插入图片描述
这时候必须子类必须实现抽象方法。在这里插入图片描述
子类必须实现抽象父类的抽象方法,抽象顾名思义就是不具体的,所以子类必须给个具体的说法。

函数类接口

对函数传入的参数以及返回值进行约束
在这里插入图片描述

这就是一个基本的函数接口,直接对传入的参数以及返回值进行约束。相当于我们直接在后面写上类型()=>类型
这样约束。写成即接口类型可以复用。

可索引接口 数组 对象的约束

正常数组的约束
在这里插入图片描述
写成接口
在这里插入图片描述
因为数组的索引值是number类型的,所以适用于数组,而对象的属性都是string,
对对象的约束
在这里插入图片描述

类类型接口

与抽象类相似

在这里插入图片描述
类使用接口要用implements,此时接口就像一个抽象父类,里面的属性就像抽象属性方法,必须由子类具体实现
在这里插入图片描述
但这个抽象类的抽象方法的入参约束不到在这里插入图片描述

接口扩展 接口继承接口在这里插入图片描述这个主要用于封装组建的时候继承原生的HTML接口

在这里插入图片描述
使用的时候就可以传入原生的html属性
在这里插入图片描述
在这里插入图片描述

可以同时继承并且定义类的接口,因为name属性已经在父类定义了,所以super继承后,只需要定义eat方法就可。

泛型

泛型使得一个组件可以支持多个数据类型,提高复用性以及对不特定类型的支持。
基本使用
在这里插入图片描述

泛型类,类也可以作为参数传入泛型

在这里插入图片描述
在这里插入图片描述
如图,类可以作为参数传入,为什么不用接口呢?因为接口无法实例化

泛型接口

在这里插入图片描述
根据这些基本用法不难看出泛型可以大大提供复用性。相当于加了类型检验的any。

多个参数

在这里插入图片描述

案例 实现一个简单的可以操作数据库的苦库,支持mysql mongodb

首先应该想到使用接口约束参数,使用泛型来实现可复用性。
定义接口
在这里插入图片描述
定义操作类
在这里插入图片描述

类要以上面的接口为约束。
操作用户表
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
获取数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
演示基本的操作,更多复杂的操作可以自己实现,主要是定义接口和实现类接口,规范约束。

ts 命名空间

模块化主要侧重代码的复用,比如一个模块可能有好几个命名空间,命名空间主要是用来组织代码,防止命名冲突。
最基本的用法
在这里插入图片描述
ts的namespace转化成js就是函数自执行函数
也可以暴露出去
在这里插入图片描述
在别的文件就可以通过A.XX来调用。

ts装饰器

装饰器是一种特殊类型的声明,能够被附加到类声明,方法,属性或者参数上,可以修改类的行为,通俗的讲就是装饰器是一个方法,可以注入到类,属性,方法来扩展这些功能,
常见的装饰器有类 属性 方法 参数装饰器。
装饰器的写法有 普通装饰器(无法传参) 装饰器工厂(可以传参)

类装饰器,应用于构造函数,可用来监视替换修改类定义
普通装饰器(无法拆参)

在这里插入图片描述
在装饰器里面可以对类进行操作,就类似于react的高阶组件,这个类通过装饰器这个"高阶组件",功能更丰富了。如图
在这里插入图片描述
可以在装饰器在原型上增加一些属性方法供实例继承。

装饰器工厂 可传参 (之后的属性,方法等都是装饰器工厂

在这里插入图片描述
这种就是装饰器工厂。
类装饰器胡子哎沟站杉树运行时被调哟干嘛,类的构造函数作为其唯一的参数,但是如果,类装饰器返回了一个值,那么就会使用返回的值替代构造函数。
在这里插入图片描述
正常
在这里插入图片描述
使用装饰器
在这里插入图片描述
返回一个子类,该子类在继承父类实例的时候可以扩展自己的功能,也相当于react的高阶组件。在这里插入图片描述

属性装饰器 接受两个参数 (原型对象 属性名称)

在这里插入图片描述
属性装饰器
在这里插入图片描述
在这里插入图片描述

attr就是属性的名称,target就是这个类,如图aa已经被修改了。传入的参数就是params,返回一个函数。
要注意这里的target跟类装饰器的target有点区别。在这里插入图片描述
在这里插入图片描述
一个是构造函数,一个是整个类,因为本质来说,属性装饰器只是该属性的一个“高阶组件“,而类是整个类的”高阶组件”,所以不同。

方法装饰器

方法装饰器会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义。在运行时传入三个参数,1 构造函数/类原型对象, 2 成员名字 3 成员属性描述符
在这里插入图片描述
在这里插入图片描述
当方法为static静态方法时
在这里插入图片描述

在这里插入图片描述
拿到的target就是构造函数

以上是关于实战用 Custom Hook + TS泛型实现 useArray的主要内容,如果未能解决你的问题,请参考以下文章

React+hook+ts+ant design封装一个input和select搜索的组件

组件库实战 | 用vue3+ts实现全局Header和列表数据渲染ColumnList

React Custom Hook set 返回的函数不是函数

TS之泛型

[React] Detect user activity with a custom useIdle React Hook

TS泛型类、泛型接口、泛型函数