15TypeScript全解TS与JSX、TS、React、Vue配合

561 阅读4分钟

一. 如何使用tsx

  • webpack配合create-react-app,Vue Cli
  • vite选择tsx模板配合vite的ts插件,@vitejs/plugin-vue-jsx
  • 如果后台要用tsx,next.js/remix.js/fresh.js

二. tsx中标签与断言的冲突

const header = <h1>hi</hi>
const a = 1 as unknown
const b = a as number
const c = <number>a // 不要再tsx中出现这种断言,会有冲突

二. JSX/TSX的本质

const header = <h1 name="frank">hi</h1>
// 编译成
import {create} from 'react_or_vue/jsx'
const header = create('h1', { name: 'frank' }, 'hi')
// 这么写的类型是什么?
  • 需要写上类型才能使用
const header = <h1 name="frank">hi</h1>;

// 通过这种方式定义和扩展tsx的类型
declare global {
  namespace JSX {
    interface IntrinsicElements {
      name?: string;
      age?: number;
      enabled: boolean;
    }
  }
}

  • declare global { ... }: 这是一个全局声明,它告诉 TypeScript 在全局范围内添加一些额外的类型定义。
  • namespace JSX { ... }: 这里创建了一个 JSX 的命名空间,用于定义 JSX 元素的类型。
  • interface IntrinsicElements { ... }: 在 JSX 命名空间中,定义了一个名为 IntrinsicElements 的接口。这个接口用于定义 JSX 元素的类型信息。
  • { name?: string; age?: number; enabled: boolean; }: 在 IntrinsicElements 接口中定义了一个对象类型,其中包含了三个属性:
    • name?: string;:一个可选的字符串属性,表示 JSX 元素可以具有一个名为 "name" 的属性,其值是字符串类型。
    • age?: number;:一个可选的数字属性,表示 JSX 元素可以具有一个名为 "age" 的属性,其值是数字类型。
    • enabled: boolean;:一个必需的布尔属性,表示 JSX 元素必须具有一个名为 "enabled" 的属性,其值是布尔类型。

三. jsx.element 是什么

const header = <h1 name="frank">hi</h1>;
 
// 通过这种方式定义和扩展tsx的类型
declare global {
  // 我们创建的标签类型是由jsx.element指定的
  interface Element {
    tag: string;
  }
  namespace JSX {
      interface IntrinsicElements {
      name?: string;
      age?: number;
      enabled: boolean;
    }
  }
}

  • 总结如下图所示

1.png

四. 函数组件

  • 函数组件的props,不是由IntrinsicElements的value指定,而是由函数组件的第一个参数决定
const header = <h1 enabled>hi</h1>;
const Header = (props: { level: number }, context: unknown) => {
  return <h1>我的等级是{props.level}</h1>;
};

// 如果用的是函数组件,props就不是由IntrinsicElements的value指定,而是由函数组件的第一个参数决定
const App = <Header level={123}></Header>;

declare global {
  interface Element {
    tag: string;
  }
  namespace JSX {
    interface IntrinsicElements {
      name?: string;
      age?: number;
      enabled: boolean;
    }
  }
}

五. 类组件



declare global {

  namespace JSX {
    interface Element {
      tag: string;
    }
    interface IntrinsicElements {
      name?: string;
      age?: number;
      enabled: boolean;
    }
  }
}

六. 组件共有属性

  • IntrinsicAttributes表示每一个标签都有的属性
declare global {
  namespace JSX {
    interface Element {
      tag: string;
    }

    // 函数和类都有
    interface IntrinsicAttributes {
      key: string;
    }

    // 原生有的属性 
    interface IntrinsicElements {
      h1: {
        name?: string;
        age?: number;
        enabled: boolean;
      } & IntrinsicAttributes; // 这样写之后h1页必须有key
    }
  }
}

  • IntrinsicClassAttributes表示固有的class属性

class ClassHeader {
  props: {
    level: number;
  };
  constructor(props: { level: number }) {
    this.props = props;
  }
  render() {
    return <h1>level</h1>;
  }
}s

const App2 = (
  <ClassHeader level={123} key="abc" ref={{ current: null }}></ClassHeader>
);

declare global {
  namespace JSX {
    interface Element {
      tag: string;
    }

    // 函数和类都有
    interface IntrinsicAttributes {
      key: string;
    }

    // 原生有的属性
    interface IntrinsicElements {
      h1: {
        name?: string;
        age?: number;
        enabled: boolean;
      } & IntrinsicAttributes; // 这样写之后h1页必须有key
    }

    interface IntrinsicClassAttributes<T> {
      ref: {
        current: T | null;
      };
    }
  }
}

七. jsx如何把内容变成props的属性

const Header = (
  // children来控制标签中间的内容
  props: { level: number; children: number },
  context: unknown
) => {
  return <h1>我的等级是{props.level}</h1>;
};

const App = <Header level={123}>{123}</Header>;

declare global {
  namespace JSX {
    interface ElementChildrenAttribute {
      children: {};
    }
    ...
  }
}

八. React与Vue源码中的JSX声明

  • 在项目中写上type X = JSX.Element,之后点JSX进去查看源文件,react和vue都可以

九. JSX.Element VS ReactElement VS ReactNode

  • JSX.Element就是ReactElement
  • ReactNode 包含了ReactElement等元素
import { ReactElement, ReactNode } from 'react';

type X = JSX.Element; // JSX.Element继承了ReactElement

type A = ReactElement;

type B = ReactNode; // 这个范围更大具体根据定义有以下这些

// type ReactNode =
// | ReactElement
// | string
// | number
// | Iterable<ReactNode>
// | ReactPortal
// | boolean
// | null
// | undefined
// | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES];

九. React 事件处理函数的类型怎么写?

  • 总结: 先点以下原有的类型,然后拷过来,有泛型就传泛型
import { ChangeEventHandler, MouseEventHandler } from 'react';
const App2 = () => {
  const onClick: MouseEventHandler<HTMLInputElement> | undefined = (e) => {
    console.log(e.currentTarget.value);
    console.log((e.target as HTMLInputElement).value);
  };
  const onChange: ChangeEventHandler<HTMLInputElement> | undefined = (e) => {
    console.log(e.target.value);
  };

  // onchange 触发时机是在失去焦点时
  // oninput 触发时机是在输入时
  // oncompositionstart 开始输入
  // oncompositionend 结束输入
  return (
    <input onClick={onClick} onChange={onChange}>
      hi
    </input>
  );
};

十. 如何指定children的类型?

  • 总结: Children能够指定很容易区分的东西,比如数组,string等,但是做不到是B组件创建的Div还是C组件的Div,这只能用js做检查
type AProps = {
  children?: ReturnType<typeof B>;
};
type BProps = {};
type CProps = {};

const A: React.FC<AProps> = (props) => {
  return <div>{props.children}</div>;
};
const B: React.FC<BProps> = (props) => <div>B组件</div>;
const C: React.FC<CProps> = (props) => <div>B组件</div>;

const App = (
  <A>
    {/* 没有办法指定children是函数组件,因为returnType就是ReactElement */}
    <B></B>
    <C></C>
  </A>
);
  • 用js做判断
type AProps = {
  children?: React.ReactElement<typeof B>;
};
type BProps = {};
type CProps = {};

const B: React.FC<BProps> = (props) => <div>B组件</div>;
const C: React.FC<CProps> = (props) => <div>B组件</div>;

const A: React.FC<AProps> = (props) => {
  if (props.children?.type !== B) {
    throw new Error('children必须是B组件');
  }
  return <div>{props.children}</div>;
};

十一. React泛型组件是什么

const App = () => <Show<string> content="hi" onClick={(c) => console.log(c)} />;
interface Props<T> {
  content: T;
  onClick: (arg: T) => void;
}

function Show<T>(props: Props<T>) {
  const { content, onClick } = props;
  return (
    <div>
      {content as React.ReactNode}
      <button onClick={() => onClick(content)}>Click me</button>
    </div>
  );
}
  • ts中只传泛型
function f<T>(x: T) {
  return null
}
// 注意这里f要用括号括起来
const f2 = (f)<string>
f2('hi')
  • 之前的代码可以写成
const App = () => {
  const X = (Show)<string>
  return (
    <X content="hi" onClick={(c) => console.log(c)} >
  )
}
interface Props<T> {
  content: T;
  onClick: (arg: T) => void;
}

function Show<T>(props: Props<T>) {
  const { content, onClick } = props;
  return (
    <div>
      {content as React.ReactNode}
      <button onClick={() => onClick(content)}>Click me</button>
    </div>
  );
}
  • 总结: 只要两个属性有强关联,就可以声明泛型组件。这个组件接收一个泛型,这个泛型和props相关联,至少和两个props的属性关联