与我交流
与我交流

React JSX

在 React 中使用 TypeScript 的教学视频

建立

TypeScript in the browser 章节中,我们已经学会开始开发 React 的应用了,以下是一些重点:

  • 使用文件后缀 .tsx(替代 .ts);
  • 在你的 tsconfig.json 配置文件的 compilerOptions 里设置选项 "jsx": "react"
  • 在你的项目里为 JSXReact 安装声明文件:npm i -D @types/react @types/react-dom
  • 导入 react 到你的 .tsx 文件(import * as React from 'react')。

HTML 标签 vs 组件

React 不但能渲染 HTML 标签(strings)也能渲染 React 组件(classes)。JavaScript 触发这些的原理是不同的(React.createElement('div') vs React.createElement(MyComponent)), 确定使用哪一种方式取决于首字母的大小写,foo 被认为是 HTML 标签,Foo 被认为是一个组件。

类型检查

HTML 标签

一个 HTML 标签 foo 被标记为 JSX.IntrinsicElements.foo 类型。在我们已经安装的文件 react-jsx.d.ts 中定义了所有主要标签的类型,如下是一部分示例:

declare namespace JSX {
  interface IntrinsicElements {
    a: React.HTMLAttributes;
    abbr: React.HTMLAttributes;
    div: React.HTMLAttributes;
    span: React.HTMLAttributes;

    // 其他
  }
}

函数式组件

你可以使用 React.FunctionComponent 接口定义函数组件:

type Props = {
  foo: string;
};

const myComponent: React.FunctionComponent<Props> = props => {
  return <span>{props.foo}</span>;
};

<MyComponent foo="bar" />;

类组件

根据组件的 props 属性对组件进行类型检查。这是以 JSX 如何转换做为蓝本,例如:属性成为 props 的组成部分。

react.d.ts 文件定义了 React.Component<Props,State>,你应该使用自己所需的 PropsState 声明扩展它:

type Props = {
  foo: string;
};

class MyComponent extends React.Component<Props, {}> {
  render() {
    return <span>{this.props.foo}</span>;
  }
}

<MyComponent foo="bar" />;

React JSX Tip: 接收组件的实例

react 类型声明文件提供了 React.ReactElement<T>,它可以让你通过传入 <T/>,来注解类组件的实例化结果。

class MyAwesomeComponent extends React.Component {
  render() {
    return <div>Hello</div>;
  }
}

const foo: React.ReactElement<MyAwesomeComponent> = <MyAwesomeComponent />; // Okay
const bar: React.ReactElement<MyAwesomeComponent> = <NotMyAwesomeComponent />; // Error!

TIP

当然,你可以将它用作函数参数的注解,甚至可以是 React 组件的 prop 成员。

React JSX Tip: 接受一个可以在 Props 起作用,并使用 JSX 渲染的组件

类型 React.Component<Props>React.ComponentClass<P>React.StatelessComponent<P> 的组合,所以你可以接受一些可以用作 Props 类型和使用 JSX 渲染的组件。

const X: React.Component<Props> = foo; // from somewhere

// Render X with some props:
<X {...props} />;

React JSX Tip: 可渲染的接口

React 可以渲染一些像 JSX 或者是 string 的内容,这些被合并到类型 React.ReactNode 中,因此,当你接收可渲染的内容时,你可以使用它:

type Props = {
  header: React.ReactNode;
  body: React.ReactNode;
};

class MyComonent extends React.Component<Props, {}> {
  render() {
    return (
      <div>
        {this.props.header}
        {this.props.body}
      </div>
    );
  }
}

<MyComponent header={<h1>Header</h1>} body={<i>body</i>} />

React JSX tip: 接收组件的接口

React 声明文件提供 React.ReactElement<T> 的接口,可以让你注解一个类组件实例化的返回值<T/>,如:

class MyAwesomeComponent extends React.Component {
  render() {
    return <div>Hello</div>;
  }
}

const foo: React.ReactElement<MyAwesomeComponent> = <MyAwesomeComponent />; // Okay
const bar: React.ReactElement<MyAwesomeComponent> = <NotMyAwesomeComponent />; // Error!

TIP

你也可以将其用做函数参数的注解,或者是 React 组件的 prop 注解。

React JSX tip: 接收可以作为 props 的组件,并且使用 JSX 渲染它

类型 React.Component<Props> 合并了 React.ComponentClass<P>React.StatelessComponent<P>,因此,你可以接收一些使用 Prop 类型的组件,并使用 JSX 渲染它:

const X: React.Component<Props> = foo // 来自其他地方

// 渲染 X
<X {...props} />

React JSX tip: 泛型组件

它完全能按我们预期工作,如:

// 一个泛型组件
type SelectProps<T> = { items: T[] };
class Select<T> extends React.Component<SelectProps<T>, any> {}

// 使用
const Form = () => <Select<string> items={['a', 'b']} />;

泛型函数

一些像下面这样的正常工作:

function foo<T>(x: T): T {
  return x;
}

然而不能使用箭头泛型函数:

const foo = <T>(x: T) => T; // Error: T 标签没有关闭

解决办法:在泛型参数里使用 extends 来提示编译器,这是个泛型:

const foo = <T extends {}>(x: T) => x;

React Tip: 强类型的 Refs

基本上你在初始化一个变量时,使用 ref 和 null 的联合类型,并且在回调函数中初始化他:

class Example extends React.Component {
  example() {
    // ... something
  }

  render() {
    return <div>Foo</div>;
  }
}

class Use {
  exampleRef: Example | null = null;

  render() {
    return <Example ref={exampleRef => (this.exampleRef = exampleRef)} />;
  }
}

使用原生元素时也一样:

class FocusingInput extends React.Component<{ value: string; onChange: (value: string) => any }, {}> {
  input: HTMLInputElement | null = null;

  render() {
    return (
      <input
        ref={input => (this.input = input)}
        value={this.props.value}
        onChange={e => {
          this.props.onChange(e.target.value);
        }}
      />
    );
  }
  focus() {
    if (this.input != null) {
      this.input.focus();
    }
  }
}

类型断言

如我们之前提到的,可以使用 as Foo 语法进行类型断言。

默认 Props

  • 在有状态组件中使用默认的 Props:你可以通过 null 操作符(这不是一个理想的方式,但是这是我能想到的最简单的最小代码解决方案)告诉 TypeScript 一个属性将会被外部提供(React)。
class Hello extends React.Component<{
  /**
   * @default 'TypeScript'
   */
  compiler?: string;
  framework: string;
}> {
  static defaultProps = {
    compiler: 'TypeScript'
  };
  render() {
    const compiler = this.props.compiler!;
    return (
      <div>
        <div>{compiler}</div>
        <div>{this.props.framework}</div>
      </div>
    );
  }
}

ReactDOM.render(
  <Hello framework="React" />, // TypeScript React
  document.getElementById('root')
);
  • 在 SFC 中使用默认的 Props:推荐使用简单的 JavaScript 参数,因为同样适用于 TypeScript 类型系统:
const Hello: React.SFC<{
  /**
   * @default 'TypeScript'
   */
  compiler?: string;
  framework: string;
}> = ({
  compiler = 'TypeScript', // Default prop
  framework
}) => {
  return (
    <div>
      <div>{compiler}</div>
      <div>{framework}</div>
    </div>
  );
};

ReactDOM.render(
  <Hello framework="React" />, // TypeScript React
  document.getElementById('root')
);