您现在的位置是:首页 >技术杂谈 >React 中 TypeScript 和装饰器及 Hooks网站首页技术杂谈

React 中 TypeScript 和装饰器及 Hooks

东日是个卷毛 2024-06-14 17:19:22
简介React 中 TypeScript 和装饰器及 Hooks

概念

TypeScript 是强类型语言,相对于JavaScript 弱类型语言,它具有类型检测的功能,扩展了JavaScript 的语法。

TS的安装与执行:

//全局安装typescript
npm install typescript -g

// 第二个因为  本来的node是不可能支持 ts那种民间语法的 所以ts-node包对node进行了处理
npm install -g ts-node

npm install -d tslib @types/node


//执行
ts-node 文件名

类型推断 *

当我们没有定义类型时,TS 会自动实现一个类型推断的过程,当 TS 无法类型推断时,需要手动使用类型注解

type 与 interface的异同 

interface :是接口,用于描述一个对象。

type :是类型别名,用于给各种类型定义别名,让 TS 写起来更简洁、清晰。

相同点:1、都可以定义一个对象或函数     2、都可以实现继承

1、定义函数
type addType = (num1:number,num2:number) => number

interface addType {
    (num1:number,num2:number):number
}
//调用
const add:addType = (num1, num2) => {
    return num1 + num2
}


2、继承
//interface 使用 extends 实现继承
interface Person { 
  name: string 
}
interface Student extends Person { 
  grade: number 
}
//type 使用交叉类型实现继承
type Person = { 
  name: string 
}
type Student = Person & { grade: number  }    用交叉类型

不同点:

1、interface 使用 extends 或 implements 实现继承, type 使用交叉类型实现继承

2、type 可以声明基本类型、联合类型、交叉类型、元组等,interface 不可以

3、interface 可以合并重复声明,type 不可以

基础数据类型 

  • 常用:boolean、number、string、array、enum(枚举)、any、void
  • 不常用:tuple(元祖)、null、undefined、never

对象类型 

  • 对象类型、数组类型、类类型、函数类型在 TS 中统称对象类型
  • TS 中用接口(interface )来定义对象类型

数组类型

interface IItem {
  id: number;
  name: string;
  isGod: boolean;
}
const objectArr: IItem[] = [{ id: 1, name: '俊劫', isGod: true }];
// or
const objectArr: Array<IItem> = [{ id: 1, name: '俊劫', isGod: true }];

const numberArr: number[] = [1, 2, 3];

const arr: (number | string)[] = [1, "string", 2];

元祖tuple

赋值的类型、位置、个数需要和定义(声明)的类型、位置、个数一致。

// 数组 某个位置的值可以是注解中的任何一个
const LOL: (string | number)[] = ["zed", 25, "darts"];

// 元祖 每一项数据类型必须一致
const LOL: [string, string, number] = ["zed", "darts", 25];

联合与交叉类型

  • 联合类型:某个变量可能是多个 interface 中的其中一个,用 | 分割
  • 交叉类型:由多个类型组成,用 & 连接
// anjiao 某胖博主爱好
interface Waiter {
  anjiao: boolean;
  say: () => {};
}

interface Teacher {
  anjiao: boolean;
  skill: () => {};
}

// 联合类型
function judgeWho(animal: Waiter | Teacher) {}
// 交叉类型 
// 同名类型会进行合并,同名基础类型属性的合并返回:never
// 同名非基础类型属性可以正常合并
function judgeWho(jishi: Waiter & Teacher) {}

可索引类型接口:需要使用下标

interface UserArr{
    //定义索引key为number类型,索引值为string类型
    [index:number]:string
}
var arr1:UserArr;
 arr1=["aa","bb","cc"];//数组自带下标也就是索引

var arr2: UserArr
 arr2={1:"hello",2:"world"};

console.log(arr1);
console.log(arr2);


/*
* ts中接口
*   可索引(数组对象)  复合接口
* */
interface hanleA {
 [index:number]:hanleB
}

interface hanleB {
 name: string,
 age: number,
 flag?: boolean
}


function handleReduce(arr: hanleA) {
 console.log(arr)
}

handleReduce([{name: 'shun', age: 12}, {name: 'enne', age: 12}])

类接口

  • 类接口中使用 implements 或 extends 关键字实现继承
interface Animal { //在typescript中可以通过类的接口 锁死类的写法类型 
    name: string;
    eat(s: string): string;
  
}

//实现接口使用implements关键字,继承 使用extends关键字
//狗类
class Dog implements Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    //实现接口中抽象方法
    eat(s) {
        return this.name + "吃肉:" + s;
    }
}
//猫类
class Cat implements Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    //实现接口中抽象方法
    eat(s) {
        return this.name + "吃鱼:" + s;
    }
}

var dog = new Dog("tom");
var cat = new Cat("kitty");
console.log(dog.eat("五花肉"));
console.log(cat.eat("乌鳢鱼"));

函数接口

//函数型接口 ,非常类似于java、c#中使用lambda表达式传入匿名函数

//对两个数进行运算得到另一个数  函数型接口
interface CalcTwo{
    (a:number,b:number):number;
}
function calcArr(arr:number[],calc:CalcTwo,initVal:number):number{
      var result=initVal; // 0 
      arr.forEach((value)=>{
          result = calc(result,value); //9 
      });
     return result;
}
//相加
var arr:number[]=[1,3,5,7,9];
var add=function(a:number,b:number):number{
      return a+b;
};

console.log("相加",calcArr(arr, add, 0));//25

//相乘
var multiply=function(a:number,b:number):number{
    return a*b;
};
console.log("相乘",calcArr(arr, multiply, 1));//945
//或者直接传入一个匿名函数亦可
var s1=calcArr(arr,function(a,b){
    return a*b;
},1);
console.log("s1",s1);//945

var s2=calcArr(arr,function (a,b) {
    return (a+1)*(b+1);
 },1);
console.log("s2",s2);//10170

补充写法:

书写方式一:
const cluo: {
    name: string,
    age: number,
} = {
    name: "cluo",
    age: 18,
};
console.log(cluo.name);


书写方式二:参数解构化
function add({ one, two }: { one: number, two: number }):number {
    return one + two;
  } 
const total = add({ one: 1, two: 2 });

never返回值类型

如果一个函数是永远也执行不完的,就可以定义返回值为never,那什么样的函数是永远也执行不完的那?我们先来写一个这样的函数(比如执行执行的时候,抛出了异常,这时候就无法执行完了)。

还有一种是一直循环,也是我们常说的死循环,这样也运行不完

//抛出异常
function errorFuntion(): never {
  throw new Error();
  console.log("Hello World");
}

//死循环
function forNever(): never {
  while (true) {}
  console.log("Hello  ");
}

枚举类型

  • 枚举类型是有对应的数字值的,默认是从 0 开始的。
  • 以下是一个案例:比如我现在去"澳门 "时,通过掷色子随机选择一项服务,进行程序化模拟

enum Status {
  MASSAGE,
  SPA,
  LAOHUJI,
}

function getServe(status: any) {
  if (status === Status.MASSAGE) {
    return "massage";
  } else if (status === Status.SPA) {
    return "spa";
  } else if (status === Status.LAOHUJI) {
    return "dabaojian";
  }
}

const result = getServe(Status.SPA);

console.log(`我要去${result}`);//我要去spa


console.log(Status.MASSAGE);//0
console.log(Status.SPA);//1
console.log(Status.LAOHUJI);//2

泛型

  • 泛型: 方法名<类型形参>,会在参数函数调用之前进行类型形参的调用
function join<T>(first: T, second: T) {// 此时所有的T都是string 
    return `${first}${second}`;
}
console.log(join<number>( 1,2))
  • 泛型接口
// 实现泛型接口的两种方式
// 方式一:定义一个泛型接口,用函数实现
interface Func {
    <T>(value: T): T
}

// 定义一个函数实现泛型接口
var f: Func = function<T>(value: T):T {
    return value;
}
f<string>("hello")
f<number>(666)

// 方式二:定义一个user类和数据库进行映射,用类实现
class MysqlDb <T>{
    add(info: T): boolean {
        console.log(info);
        return true;
    }
 }

 class User {
    usernam: string | undefined;
    password: string | undefined;
 }
 var u1 = new User();
 u1.usernam = "pika";
 u1.password = "pika"
 
 // 实例化一个数据库(类当成一个参数来约束传入参数的类型)
 var msql = new MysqlDb<User>();
 msql.add(u1);   // 保证传入数据的格式是User类型的

装饰器模式

  • 在JavaScript中,装饰器的语法比较特殊。它们的前缀是@符号,放在我们需要装饰的代码前面。

第一步:需要 tsc 初始化:tsc --init,会产生一个tsconfig.json文件

第二步:加入"experimentalDecorators": true,表示开启装饰器模式

 

第三步:装饰器模式的使用

function logClass(params: any) {
    console.log(params);
    // params 就是当前类
    params.prototype.apiUrl = '动态扩展的属性';
    params.prototype.run = function () {
        console.log('我是一个run方法');
    }

}
@logClass // 相当于logClass(HttpClient )
class HttpClient {
    constructor() {
    }
    getData() {

    }
}
var http: any = new HttpClient();
console.log(http.apiUrl);
http.run();

React 新特性 hooks

  • React 16.8之后出现了 hooks 钩子,React 17之后 hooks 较为稳定。
  • hooks 是为了解决函数式组件没有生命周期,无法实现数据的响应式。
  • 同时解决类组件中,难以复用组件间状态逻辑(组件状态逻辑的复用,需要 props render和高阶组件等解决方案,这将会导致层级冗余的问题),以及this指向的问题。
  • hooks 钩子使用 'use'前缀命名,如useXxx。
  • 常用的 hooks 钩子有useState、useEffect、useContext、useReducer、useRef、useMemo、useCallback等等

优点:

  • 不用考虑 this 指向问题
  • 解决了类组件中层级冗余的问题

缺点:

  • 只能函数组件中使用

状态钩子 useState

useState 函数可以声明组件状态,设置初始值,及【初始值,初始值修改函数】

import React,{useState} from 'react'

function App() {
  // count 0  setCount扳手 只有这个扳手才能拧动 count这个螺丝
  const [count,setCount] = useState(0)
  const addCount= ()=>{
    let newCount = count 
    setCount(newCount+1)
  }
  return (
    <div className="App">
       <div>{count}</div>
       <button onClick={addCount}>点击+1</button>
    </div>
  );
}

export default App;

共享状态组件的钩子 useContext:相当于类组件中的跨组件传值

import React,{useContext} from 'react'
function App() {
  //react自带的API返回一个 AppContext的上下文
  const AppContext = React.createContext()
  const Achild=()=>{
      const {name} = useContext(AppContext)
      return (
          <div>组件的名字  {name}</div>
      )
  }
  const Bchild=()=>{
    const {name2} = useContext(AppContext)
    return (
        <div>组件的名字  {name2}</div>
    )
}  
  return (
     <AppContext.Provider value={{name:"dddd",name2:'梅西'}}>
            <Achild></Achild>
            <Bchild></Bchild>
     </AppContext.Provider>
  );
}

export default App;

状态管理钩子 useReducer

import React,{useState,useContext,useReducer} from 'react'
function App() {
  
  const reducer = (state,action)=>{
     const   actionFn = {
          add:function(){
              return {
                  ...state,
                  count:state.count+1
              }
          }
      }
      return actionFn[action.type]()
  }

const [state,dispatch] = useReducer(reducer,{count:0})
  const addCount =()=>{
      dispatch({
          type:'add'
      })
  }
  return (
     <div>
         <div>{state.count}</div>
         <button onClick={addCount}>点击+1</button>
     </div>
  );
}

export default App;

副作用钩子 useEffect

  • useEffect 第一次执行会调用如同 vue2 中前四个生命周期函数,当数据更新时,在调用 updated 钩子函数,当销毁时调用 destroyed 钩子函数。
  • useEffect 相当类组件中的3个生命周期 componentDidMount 、componentDidUpdate、 componetWillUnMount
  • 此函数的操作是异步的。
import React,{useState,useEffect} from "react"; 
// useEffect 翻译 副作用钩子  直译 影响钩子 功能让函数组件中有类似于生命周期效果
function App() {
    const [loading,setLoading] = useState(true )
    //影响钩子 内部有一个回调匿名函数 
    // 第一次执行时机 dom生成之后  相当于模拟了mounted 生命周期 
    useEffect(()=>{ 
        console.log('useEffect')
        setTimeout(() => {
            setLoading(false)
        }, 3000);
    })
    return (
        <div>
            {
                loading ? (<div>loading.......</div>) : <div>加载成功</div>
            }
        </div>
    )
}

export default App 
// 影响钩子 不是生命周期 但是用买房子的三个阶段 来模拟生命周期
// 买房 回调函数开始执行  mounted 
// 装修 回调函数又执行    updated 
// 房子塌了 回调函数再次执行  destoryed 
useEffect 还可以指定调用如下案例:
import React,{useState,useEffect} from "react"; 
function App() {
    const [carBenzNum,setCarbenz] = useState(0)
    const [carqqnum,setCarqq] = useState(0)
    //影响钩子 内部有一个回调匿名函数 
    useEffect(()=>{ 
        console.log('useEffect')
        
    },[carBenzNum])
    const addBuyCar1 = () =>{
       setCarbenz(carBenzNum+1)
    }
    const addBuyCar2= () =>{
       setCarqq(carqqnum+1)
    }
    return (
        <div>
             <h1>影响钩子 副作用钩子</h1>
             <button onClick={addBuyCar1}>点击买奔驰</button>
             <button onClick={addBuyCar2}>点击买QQ车</button>
             <h1>家里的奔驰车数量{carBenzNum}</h1>
             <h1>家里的QQ车数量{carqqnum}</h1>
        </div>
    )
}
export default App 

记忆组件 useMemo(可以理解为计算属性)

import React, { useState, useMemo } from 'react';
const Memo = () => {
  const [count, setCount] = useState(1)
  const [val, setValue] = useState('')

  // 返回上一次缓存的值
  const sum = useMemo(() => {
    return count + 10
  }, [count]);

  return (
    <div>
      <h3>{count}-{val}-{sum}</h3>
      <div>
        <button onClick={() => setCount(count + 1)}>+ count</button>
        <input value={val} onChange={event => setValue(event.target.value)} />
      </div>
    </div>
  )
}

注:部分代码搬运自:一篇够用的TypeScript总结 - 掘金 (juejin.cn)

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。