typescript数据类型 + React

862 阅读9分钟

ts基础

符号

?可选类型:位置在变量或属性名后,表示该值可写可不写 - 在变量后 age?.length { age?:number }

: 后接类型限制 - 变量后 num : number

| 联合类型,优先级较低,使用时配合括号使用,两边接数据类型 - 在类型之间 number | string

! 非空断言,表示断定!前面的变量不为空 - 表达式后 localStorage.getItem(token)!

& 并集 交叉 将类型合拼 需要注意冲突问题,可以使用Omit解决

原始数据类型

形式:变量名:数据类型

    let num: number = 1030;
    
    let str: string = 'string';
    
    let bool: boolean = true;
    
    let un: undefined = undefined;
    
    let nu: null = null;

数组类型

形式:变量名:数据类型[]

    // 形式1
    let arr1: number[] = [1, 2, 3];
    // 形式2
    let arr2: (number | string)[] = [1, 2, 3, 'abc'];
    // 形式3 - 配合自定义类型
    type ArrNS = (number | string)[];
    let arr3: ArrNS = [1, 2, 3, 'abc'];

## 函数类型 - void表示没有返回值

参数数量确定

    // 形式1
    function sum(n1: number, n2: number): number {
      return n1 + n2;
    }
    // 形式2 - 配合类型别名
    type Fn = (n1: number, n2: number) => number;
    const add: Fn = (n1, n2) => {
      return n1 + n2;
    };
    // 形式3
    const fn = (n1:number,n2:number):number =>{
      return n1 + n2
    }

参数数量不确定 + 给定初始值

    function sum(n1: number, n2: number = 2, n3?: number): number {
      return n1 + n2 + n3;
    }

对象类型

    const user: {   // 花括号中即为对象类型限制
      name: string;
      age: number;
    } = {
      name: 'iceahh',
      age: 23,
    };

配合接口使用或类型别名

    interface Obj {
      name: string;
      age: number;
      sayHi(): void;  // 冒号或箭头后面表示返回值
      log: (n1: number, n2: number) => number;
    }
    
    type Obj = {
      name: string;
      age: number;
      sayHi(): void;  // 冒号或箭头后面表示返回值
      log: (n1: number, n2: number) => number;
    }
    
    
    const obj: Obj = {
      name: 'iceahh',
      age: 23,
      sayHi() {
        console.log('hi');
      },
      log: (n1, n2) => {
        return n1 + n2;
      },
    };

注意:对象中的属性不能比接口中对应的类型少,但是可以多于接口中的类型

对象类型不确定时,使用 可选属性?

    interface Obj {
      name?: string;
      age?: number;
      sayHi?(): void;  // 冒号或箭头后面表示返回值
      log?: (n1: number, n2: number) => number;
    }

可选属性符号写在属性名或变量名的后面

元组

可以指定数据的类型和数量 -- 数组形式

从类型和数量上同时限制了数据

    let arr:[number,number] = [1,2]
    
    function sum (n1:number,n2:number):[number,number] {
      return [n1,n2]
    } 

接口interface

用来指定对象的类型

    interface IPerson {
          name:string
          age:number
          sayHi:()=>void
          sayHello():void
    }

类的继承 - 使用 extends 限制

    interface IPoint2d {
          x:number
          y:number
    }
    
    interface IPoint3d extends IPoint2d{
          z:number
    }

type类型别名

功能比interface更强大,不仅可以指定对象的类型限制,也可以指定其他类型的限制

    type Person = {
      name?:string
      age?:number
      sayHi?():void
      sayHello?:()=>void
    }

      const user:Person = {
          name:"z3"
      }

    type Fn = (n1:number,n2:number)=>number
    let fn:Fn = (n1,n2)=>{
      return n1+n2
    }

类型推论

当明确初次具有赋值操作的时候,可以通过类型推论自动确定类型

  1. 声明变量并初始化时
  2. 函数拥有返回值时
  3. 泛型

字面量类型

指定值-常量

值和类型是用一个

    let num:1 = 1  // 指定num的值只能是1

配合联合类型使用

    type actionType = 'ADD_COUNT'|'SUB_COUNT'

可以指定可选值有哪些

枚举enum

不常用,可以使用字面量+联合类型代替

    enum Direction {
      up,
      down,
      left,
      right,
    }
    console.log(Direction.up)  // 需要这样调用
    
    enum Sex {
        man = 1,
        women = 2
    }  // 值可以修改
    function send(sex:Sex):void{
      console.log(sex)
    }
    send(Sex.man)  ==>  send(1)

类型断言

指定所选dom的类型,常配合操作dom时使用

    const alink = document.getElementById('link') as HTMLAnchorElement

typeof

可以快速的到一个数据的类型,从而能根据此数据的类型来作为限制标准

    let obj = {
      name: 'z3',
      age: 29,
    };
    //得到和obj相同的类型限制
    
    type Obj = typeof obj;

Snipaste_2022-04-28_22-13-09.png

unknown

未知类型,可以配合typeof进行类型收窄

    let num:unknow = 1
    
    if(typeof num === "string"){
      console.log(num.length)
    }

高级数据类型

泛型

类似变量,在调用的时候指定具体类型,实现多种类型的复用

    const fn<Type> = (n1:Type):Type => {
      return n1
    }
    
    fn<number>(3)
    fn<string>('abc')

约束泛型 - 通过extends限制泛型

    interface Ilength {
      length:number
    }
    function getE<T extends Ilength>(n1:T):T{  // 对参数的类型进行了限制,必须拥有length属性
      return n1
    }

多个类型限制

keyof表示是对象类型T的键名T[K],而extends表示继承于对象类型T的键名群

    function fn<T,K>(n1:T,n2:K){
    }
    
    function fn<T extends object,K extends keyof T>(n1:T,n2:K){
    }

接口泛型约束

    interface MyArr<T> {
      length:number,
      push(n1:T):void,
      pop:(n1:T) =>void
    }

泛型工具

Partial - 将属性设置为可选属性

Snipaste_2022-04-29_12-22-15.png Readonly - 将属性设置为可读属性

Snipaste_2022-04-29_12-23-14.png Pick - 选择几个属性

Snipaste_2022-04-29_12-24-49.png Omit - 排除几个属性

Snipaste_2022-04-29_12-26-20.png ReturnType - 获取函数类型的返回值的类型,常配合typeof使用 ----- useSelector需要用到该工具

typeof可以获得某个值的具体类型

    function fn(n1:number,n2:number):number{
      return n1 + n2
    }

    type FnReturn = RetrunType<typeof fn>  // 通过ReturnType和typeof的配合使用即可得到fn的返回值类型

索引签名类型

可以指定属性的类型 - number 或 string

    interface Demo<T> {
      [key:number]:T
    }  // 不能在其中配合其他形式

索引查询类型

类似获取对象属性时使用方括号

    type User = {
      name:string
      age:number
    }
    
    type A = User['name'] // 获取类型User中的name的类型

ts文件类型

d.ts文件 - 一般配合js文件使用

类型声明文件,可以指定js或ts文件中数据的类型,其中只能写类型声明,且可以配合declare使用,将文件中let const等非直接定义类型的关键字使用declare

    declare let num:number
    declare const p:string
    
    type Position = {
      x:number
      y:number
    }
    declare let position:Position
    function add(n1:number,n2:number):void {}
    export default {num , p ,position , add}
    
    // 1.使用let const 等js关键字确定类型时,需要在前面添加declare,
    // 而支持ts的类型不需要添加declare
    
    // 2.导出时,导出的并非类型,因为js中无法使用,而是导出变量
    
    // 3.写类型限制时,无须赋值,只用限定类型,如函数,不需要写完整形式
    

ts文件 - 既可以写类型,也可以写js语句

tsx文件 - 是用来写组件的

react和ts

useRef

配合ts使用useRef时

  1. 常配合非空断言使用
  2. 在创建ref时通过泛型指定类型
    const imgRef = useRef<HTMLImageElement>(null);
    console.log(imgRef.current!.src);

useState

需要添加泛型限制

    const [num, setNum] = useState<number>(0)

useLocation

需要使用泛型,根据源码,指定的泛型会作用在state上

Snipaste_2022-04-29_18-34-14.png

Snipaste_2022-04-29_18-35-09.png 举例代码

    const location = useLocation<{ from: string }>();
    const pathname = location.state ? location.state.from : '/home';

useSelector - 找到state参数和函数返回值的类型

useSelector参数为函数,其中参数state的类型为 store.getState() 的返回值

  1. useSelector泛型有两个参数
  a.  参数1:指定的是state的类型
  b.  参数2:指定的是返回值的类型

Snipaste_2022-04-29_22-57-32.png store.ts 中获取 RootState ==> useSelector中函数参数的参数的类型

    export type RootState = ReturnType<typeof store.getState>;

在useSelector中指定state的类型

    const user = useSelector<RootState, User>((state) => state.profile.user);
    // RootState ==>  state                User ==> 函数返回值,该User从reducer文件中导出
    
    
    // 推荐方式2
    const user = useSelector((state:RootState) => state.profile.user);
    // 可以不指定User类型,因为依据RootState等能推断出其类型

依据原js标签封装组件技巧

以input为例

  1. 使用interface
    interface Props extends InputHTMLAttributes<HTMLInputElement> {
      onExtraClick?: () => void;
      extra?: string;
      className?: string;
    }
  1. 使用type配合Omit和&
    type Props = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className'> & {
      onExtraClick?: () => void;
      extra?: string;
      className?: string;
    };

使用技巧:如何查看原生标签

Snipaste_2022-04-29_16-59-18.png

查看e事件对象的类型

技巧

Snipaste_2022-04-29_17-01-35.png

鉴权路由 PrivateRoute

    import { getTokenKey } from '@/utils/storage';
    import React from 'react';
    import { Redirect, Route, RouteProps, useLocation } from 'react-router-dom';
    interface Props extends RouteProps {
      component: any;
    }
    export default function AuthRoute({ component: Component, ...rest }: Props) {
      const location = useLocation();
      return (
        <div>
          <Route
            {...rest}
            render={() => {
              if (getTokenKey().token) {
                return <Component></Component>;
              } else {
                return (
                  <Redirect
                    to={{
                      pathname: '/login',
                      state: { from: location.pathname },
                    }}
                  ></Redirect>
                );
              }
            }}
          ></Route>
        </div>
      );
    }

定时器问题

ts会默认把setTimeout和setInterval当成nodejs环境中的定时器,而window下的定时器类型是number,推荐使用定时器的时候在前面把window补全,这样ts类型推断就会把定时器推断成number类型

异步action中的dispatch

  1. 如果显示类型错误,可以在dispatch后面加上泛型 异步action中的dispatch由thunk中间件提供,而thunk中间件中的dispatch继承于redux中的dispatch,因此我们指定异步action中的dispatch时,继承于redux中的dispatch

Snipaste_2022-04-29_18-50-04.png

Snipaste_2022-04-29_18-50-53.png 使用方法

    import { Dispatch } from 'redux';
    
    export const reqLogin = (mobile: number, code: number) => {
      return async (dispatch: Dispatch) => {  // 该处指定dispatch类型
        const res = await request({
          method: 'post',
          url: '/authorizations',
          data: {
            mobile,
            code,
          },
        });
        dispatch(saveToken(res.data));
        saveTokenKey(res.data);
      };
    };

reducer - ts改造

js形式

    import { LOGOUT, SAVE_TOKEN } from '@/store/constants/login';
    
    const initValue = {
      token: '',
      refresh_token: '',
    };
    export default function reducer(state = initValue, action) {
      switch (action.type) {
        case SAVE_TOKEN:
          return action.payload;
        case LOGOUT:
          return {};
        default:
          return state;
      }
    }

import { LOGOUT, SAVE_TOKEN } from '@/store/constants/login';

const initValue = {
  token: '',
  refresh_token: '',
};
export default function reducer(state = initValue, action) {
  switch (action.type) {
    case SAVE_TOKEN:
      return action.payload;
    case LOGOUT:
      return {};
    default:
      return state;
  }
}

ts形式 - 推荐方式2

  1. 将Action写在一起
    // import { LOGOUT, SAVE_TOKEN } from '@/store/constants/login';
    type Token = {  // 修改1
      token: string;
      refresh_token: string;
    };
    
    type Action = {  // 修改2:将action写在一起
      type: 'login/token' | 'login/logout';
      payload: Token;
    };
    
    const initValue: Token = {
      token: '',
      refresh_token: '',
    };
    
    export default function reducer(state = initValue, action: Action) {  // 添加类型限制
      switch (action.type) {
        case 'login/token':
          return action.payload;
        case 'login/logout':
          return {} as Token; // 修改3 该处state可能为空对象,不符合Token类型的限制,添加类型断言
        default:
          return state;
      }
    }

  1. 将Action分小模块写 推荐
    type Action =
      | {
          type: 'login/token';
          payload: Token;
        }
      | {
          type: 'login/logout';
          payload: null;
        };

给了initialValue但初始值不想写全 - 类型断言

    type initialValue = {
      user: {
        art_count: number;
        fans_count: number;
        follow_count: number;
        id: string;
        like_count: number;
        name: string;
        photo: string;
      };
      profile: {
        id: string;
        photo: string;
        name: string;
        mobile: string;
        gender: number;
        birthday: string;
        intro: string;
      };
    };
    
    const initValue: initialValue = {
      user: {},
      profile: {},
    } as initialValue  // 使用类型断言

initialValue拆开写

将reducer中state保存的类型分开写类型限制,再糅合到initialValue中 -- 主要方便action

改造前

type initialValue = {
  user: {
    art_count: number;
    fans_count: number;
    follow_count: number;
    id: string;
    like_count: number;
    name: string;
    photo: string;
  };
  profile: {
    id: string;
    photo: string;
    name: string;
    mobile: string;
    gender: number;
    birthday: string;
    intro: string;
  };
};

type Action =
  | {
      type: 'profile/userinfo';
      payload: {
        art_count: number;
        fans_count: number;
        follow_count: number;
        id: string;
        like_count: number;
        name: string;
        photo: string;
      };
    }
  | {
      type: 'profile/profile';
      payload: {
        id: string;
        photo: string;
        name: string;
        mobile: string;
        gender: number;
        birthday: string;
        intro: string;
      };
    };
const initValue: initialValue = {
  user: {},
  profile: {},
} as initialValue

改造后

type User = {
  art_count: number;
  fans_count: number;
  follow_count: number;
  id: string;
  like_count: number;
  name: string;
  photo: string;
};
type Profile = {
  id: string;
  photo: string;
  name: string;
  mobile: string;
  gender: number;
  birthday: string;
  intro: string;
};
type initialValue = {
  user: User;
  profile: Profile;
};

type Action =
  | {
      type: 'profile/userinfo';
      payload: User;
    }
  | {
      type: 'profile/profile';
      payload: Profile;
    };
const initValue: initialValue = {
  user: {},
  profile: {},
} as initialValue;

问题解决:useSelector时,显示never类型

问题描述:reducer修改为ts文件时,页面文件为js文件,此时能够正常运行,将页面文件改为tsx文件时,发现了问题,其实之前问题就存在,只是因为没有改为tsx所以没有显示出来:使用useSelector获取该文件对应state中的数据时,显示为never类型

Snipaste_2022-04-30_07-56-10.png 原因是reducer中的类型写得太乱了,然后就把数据重新写了一遍,学到了

  1. action的类型和数据的类型是不同的
a.  action中的数据类型是action文件传递过来的类型
b.  数据的类型是保存在state中的类型
  1. 对类型可以分解的地方做到有度分解,这样使用时更方便

问题解决:在request文件中saveToken,dispatch时报错

Snipaste_2022-04-30_09-36-22.png

Snipaste_2022-04-30_09-38-47.png 解决方法

  1. 给action添加返回值类型
    import { LoginAction } from '../reducers/login'; // 导入reducer中的action
    export const saveToken = (payload: Token): LoginAction => { // 指定函数返回值类型即可
      return {
        type: 'login/token',
        payload,
      };
    };
  1. 使用类型断言
    export const saveToken = (payload: Token) => { 
      return {
        type: 'login/token' as const,  // 此处指定为字面量类型
        payload,
      };
    };

RootThunkAction

在store.ts文件夹暴露

    import {Anyaction} from "redux"
    import {thunkAction} from "thunk"

    // R:thunk的action返回值类型 =》void  Promise<void>
    // S:需要指定getState的返回值类型
    // E:extra:额外参数 any
    // A:需要指定Action的类型  Anyaction
    export type RootThunkAction = thunkAction<Promise<void>,RootState,unknown,Anyaction>