您现在的位置是:首页 >技术杂谈 >React 中 TypeScript 和装饰器及 Hooks网站首页技术杂谈
React 中 TypeScript 和装饰器及 Hooks
简介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)
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。