Flow + React/React Native

学习如何在Flow中使用React

将Flow类型添加到React组件后,Flow将静态地确保你按照组件被设计的方式开发。在React早期,该库提供了执行基本运行时检查的PropType。而Flow的强大之处在于它在你运行代码前告诉你哪里出了问题。

如果你想同时进行静态和运行时检查,有一些Babel插件可以做到将Flow类型生成PropTypes,例如babel-plugin-react-flow-props-to-prop-types。

组件(Components)

学习如何在Flow中使用React类组件和无状态函数组件

类组件

在我们展示如何使用Flow键入React类组件之前,让我们先展示如何编写一个没有 Flow 的React类组件,但使用React的prop类型。你将扩展React.Component并添加一个静态propTypes属性。

import React from 'react';
import PropTypes from 'prop-types';

class MyComponent extends React.Component {
  static propTypes = {
    foo: PropTypes.number.isRequired,
    bar: PropTypes.string,
  };

  render() {
    return <div>{this.props.bar}</div>;
  }
}

现在,在我们刚才写的组件中引入flow:

import * as React from 'react';

type Props = {
  foo: number,
  bar?: string,
};

class MyComponent extends React.Component<Props> {
  render() {
    this.props.doesNotExist; // Error! You did not define a `doesNotExist` prop.

    return <div>{this.props.bar}</div>;
  }
}

<MyComponent foo={42} />;

我们删除了对prop-types的依赖,并添加了一个名为Props的Flow对象类型,其形式与prop类型相同,但是使用了Flow的静态类型语法。 然后我们将新的Props类型作为类型参数传递给React.Component。

现在如果你尝试在<MyComponent>中使用一个类型为string的foo(类型是number),将提示错误。无论我们在React组件中何处使用this.props,Flow都会将其视为我们定义的Props类型。

注意:如果你不需要再次使用Props类型,可以用内联方式定义:extends React.Component <{foo: number, bar?: string}>

注意: 我们在这里导入React作为命名空间,import * as React from "react",而不是使用默认导入,import React from "react"。在将React作为ES模块导入时,可以使用任何一种样式,但作为命名空间导入方便你访问React的公共类型。

React.Component<Props, State> 是一个带有两个类型参数泛型类型PropsState。第二个参数State是可选的,默认情况下它是未定义的,所以你可以在上面的例子中看到我们没有包含State

添加状态

现在为React类组件的State创建一个新的对象类型,在下面的例子中我们将其命名为State,并将其作为第二个类型的参数传递给React.Component

import * as React from 'react';

type Props = { /* ... */ };

type State = {
  count: number,
};

class MyComponent extends React.Component<Props, State> {
  state = {
    count: 0,
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(prevState => ({
        count: prevState.count + 1,
      }));
    }, 1000);
  }

  render() {
    return <div>Count: {this.state.count}</div>;
  }
}

<MyComponent />;

在上面的例子中,我们使用了一个React的setState()更新函数,但是你也可以把一个局部的状态对象传给setState()。

注意:如果你不需要再次使用State类型,可以用内联方式定义:extends React.Component <{}, {count: number}>

使用默认Props

React支持defaultProps的概念,你可以把它看作默认的函数参数。当你创建一个元素并且你没有包含一个默认的props时,React将会从defaultProps中获取相应的值。Flow也支持这个概念。要使用defaultProps,则在类中添加static defaultProps

import * as React from 'react';

type Props = {
  foo: number, // 必须
};

class MyComponent extends React.Component<Props> {
  static defaultProps = {
    foo: 42, // ...默认的foo
  };
}

// 不传入foo也无妨
<MyComponent />

Flow会从静态的defaultProps中推断出你的默认props的类型,所以你不需要添加任何类型的注释来使用defaultProps

注意:你不需要在你的Props类型中设置foo为可选的。如果你的默认Props中有foo,Flow将确保foo是可选的。

无状态函数组件

除了类,React还支持无状态的函数组件。你可以像键入一个函数一样键入这些组件:

import * as React from 'react';

type Props = {
  foo: number,
  bar?: string,
};

function MyComponent(props: Props) {
  props.doesNotExist; // Error! You did not define a `doesNotExist` prop.

  return <div>{props.bar}</div>;
}

<MyComponent foo={42} />

无状态函数组件中使用默认Props

与类组件类似,React的无状态函数组件也支持defautProps。无状态函数组件的默认Props将无需任何额外的类型注释。

import * as React from 'react';

type Props = {
  foo: number, // 必须
};

function MyComponent(props: Props) {}

MyComponent.defaultProps = {
  foo: 42, // 默认foo
};

// 不传入foo也无妨
<MyComponent />;

事件处理(Event Handling)

Flow中React事件处理的类型和最佳实践
React文档“处理事件”提供了一些关于如何定义事件处理程序的不同建议。如果你正在使用Flow,建议使用属性初始值设定项语法,因为相对静态类型最简单。 属性初始值设定器的语法如下所示:

class MyComponent extends React.Component<{}> {
  handleClick = event => { /* ... */ };
}

合成事件(SyntheticEvent)

一个跨浏览器原生事件包装器。 它具有与浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault() ,除了事件在所有浏览器中他们工作方式都相同。

编写事件处理程序中你可能会用到SyntheticEvent<T>类型,如下所示:

import * as React from 'react';

class MyComponent extends React.Component<{}, { count: number }> {
  handleClick = (event: SyntheticEvent<HTMLButtonElement>) => {
    // 使用 event.currentTarget访问button 实例
    // (event.currentTarget: HTMLButtonElement);

    this.setState(prevState => ({
      count: prevState.count + 1,
    }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>
          Increment
        </button>
      </div>
    );
  }
}

还有更多特定的合成事件类型,如SyntheticKeyboardEvent <T>SyntheticMouseEvent <T>SyntheticTouchEvent <T>SyntheticEvent <T>类型都采用单一类型的参数。事件处理程序所在的HTML元素的类型。

如果你不想添加你的元素实例的类型,你也可以使用SyntheticEvent,而不需要像这样的类型参数:SyntheticEvent <>

注意:为了得到元素实例,如上例中的HTMLButtonElement,使用event.target而不是event.currentTarget是一个常见的错误。 你想使用event.currentTarget的原因是event.target可能是错误的元素,由于事件冒泡。

注意:React使用自己的事件系统,因此使用SyntheticEvent类型而不是Event,KeyboardEvent和MouseEvent等DOM类型非常重要。

React提供的SyntheticEvent <T>类型和它们相关的DOM事件是:

ref 函数(ref functions)

学习如何在flow中使用ref 函数

React允许你用ref函数获取某个元素或组件的实例。要使用ref函数,可以在你的类中添加一个可选的实例类型,并将你的实例分配给你的ref函数中的该属性。

import * as React from 'react';

class MyComponent extends React.Component<{}> {
  button: ?HTMLButtonElement;
  render() {
    return <button ref={button => (this.button = button)}>Toggle</button>;
  }
}

在上面的例子中,ref的第一个参数是HTMLButtonElement | null,因为当组件卸载时,React将用null调用你的ref回调。另外,在React完成渲染之前,MyComponent上的button属性将不会被设置。在那之前你的按钮引用将是未定义的。使用可选标志?(如在?HTMLButtonElement)避免引用异常。

Children

学习如何使用flow严格规范组件接收的子元素

React元素可以有零个或多个子元素。使用Flow描述这些子元素让你能够构建富有表现力的React API。当为React组件的子项添加类型时,需要考虑的类型是React.Node。

import * as React from 'react';

type Props = {
  children?: React.Node,
};

function MyComponent(props: Props) {
  return <div>{props.children}</div>;
}

注意:这里使用import * as React from 'react'来访问React.Node类型而不是import React from 'react'。 我们在后续的React Type Reference中解释为什么。

然而,如果你想用React children API做更强大的事情,那么你需要对如何使用它有所了解。先从一个没有chidlren组件的例子开始,让我们先看看几个案例。(已经了解的同学可以跳过这一块)

<MyComponent />;
// 等同于...
React.createElement(MyComponent, {});

如果不传入任何子元素,则不会设置props.children。 如果你尝试访问props.children,它将是undefine的。

<MyComponent>{42}</MyComponent>;
// 等同于...
React.createElement(MyComponent, {}, 42);

如果传递一个单一的值,那么props.children就是那个单一的值。这里props.children将是数字42。重要的是,props.children不会是一个数组它的值是42。

<MyComponent>{1}{2}</MyComponent>;
// 等同于...
React.createElement(MyComponent, {}, 1, 2);

或者

<MyComponent>{'hello'} world</MyComponent>;
// 注意这里world前面的空格
// 等同于...
React.createElement(MyComponent, {}, 'hello', ' world');

亦或者

<MyComponent>{'hello'} <strong>world</strong></MyComponent>;
// 注意这里<strong>前面的空格
// <MyComponent>
//   {'hello'}
//   <strong>world</strong>
// </MyComponent>
// 换成这样写后换行符后的换行符和缩进会被删除,所以这里这样写不会有空格。

// 等同于...
React.createElement(
  MyComponent,
  {},
  'hello',
  ' ',
  React.createElement('strong', {}, 'world'),
);

现在传递两个或多个值,props.children是一个数组,上面的例子中,它的值可能是[1, 2]或者['hello', 'world']或者['hello', ' ', <strong>world</strong>]

这里思考一个问题,如果我们的children是一个数组,那么props.children是一个二维数组吗?不是的。

<MyComponent>{[1, 2]}</MyComponent>;
// 等同于...
React.createElement(MyComponent, {}, [1, 2]);

与传递单一值的规则一样,尽管[1,2]是一个数组,它是一个单一的值,所以props.children就是这个值。这就是说props.children将是数组[1,2]而不是一个二维数组。

同样的,当你使用array.map()传递一个数组时也是相当于传递单一值。

<MyComponent>
  {messages.map(message => <strong>{message}</strong>)}
</MyComponent>
// 等同于...
React.createElement(
  MyComponent,
  {},
  messages.map(message => React.createElement('strong', {}, message)),
);

但是,如果是多个数组元素并且是独立的,那将会发生什么?

<MyComponent>{[1, 2]}{[3, 4]}</MyComponent>;
// 等同于...
React.createElement(MyComponent, {}, [1, 2], [3, 4]);

这里props.children将是一个二维数组。具体值将是[[1,2],[3,4]]

React children的规则是,如果传递值,那么props.children不会被设置,如果传递单一的值,那么props.children将被设置为正确的值,如果传递多个值,props.children将是这些值的新数组。

注意

<MyComponent>
  // some comment...
  {42}
</MyComponent>

这里会编译成:React.createElement(MyComponent, {}, '// some comment...', 42),这时候prop.children的值为['// some comment...', 42]。在JSX中使用注释需遵循下面的语法:

<MyComponent>
  {/* some comment... */}
  {42}
</MyComponent>

只允许接收特定子元素

有时候你想你的React组件只接收特定的组件作为子元素。这通常发生构建一个需要特定列子组件的表格组件或者需要为每个选项卡进行特定配置的选项卡栏时。比如,React Native的<TabBarIOS>标签栏组件就是使用这种模式模式。

React Native的<TabBarIOS>组件只允许React元素子元素,而且这些元素必须具有<TabBarIOS.Item>的组件类型。如:

<TabBarIOS>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
</TabBarIOS>

而当我们用以下的方式使用<TabBarIOS>时则会抛出异常:

<TabBarIOS>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
  <View>{/* ... */}</View>
  <SomeOtherComponent>{/* ... */}</SomeOtherComponent>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
</TabBarIOS>

所以flow是如何帮助我们阻止这种不规范的用法?

import * as React from 'react';

class TabBarIOSItem extends React.Component<{}> {
  // implementation...
}

type Props = {
  children: React.ChildrenArray<React.Element<typeof TabBarIOSItem>>,
};

class TabBarIOS extends React.Component<Props> {
  static Item = TabBarIOSItem;
  // implementation...
}

<TabBarIOS>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
  <TabBarIOS.Item>{/* ... */}</TabBarIOS.Item>
</TabBarIOS>;

我们将propschildren的类型设置为React.ChildrenArray <React.Element <typeof TabBarIOSItem>>,这将保证<TabBarIOS>只能接收TabBarIOS.Item 类型的React元素。

注意:如果想使用map()forEach()方法或想跟处理JavaScript数组一样处理数组React.ChildrenArray<T>,React在React.Children API中提供了React.Children.toArray()方法使你可以像JavaScript一样处理数组React.ChildrenArray<T>

强制只接收一个子元素。

有时候你想强制你的组件只接收一个子元素。你可以使用React.Children.only()函数来约束这种行为,但是你也可以使用Flow实现同样的效果。现在你不能再使用React.ChildrenArray<T>包裹并定义你子元素的类型:

import * as React from 'react';
type Props = {
  children: React.Element<any>,
};
function MyComponent(props: Props) {...}
// 抛异常! 必须传入子元素
<MyComponent />;
// 抛异常! 传入了两个或多个子元素
<MyComponent>
  <div />
  <div />
  <div />
</MyComponent>;
// 正常,传入一个子元素
<MyComponent>
  <div />
</MyComponent>;

React允许你传递任何值来作为React组件的子项:

<MyComponent>
  {data => (
    <div>{data.foo}</div>
  )}
</MyComponent>

react-router 版本4允许接收一个函数作为<Route> 组件的子元素。

<Route path={to}>
  {({ match }) => (
    <li className={match ? 'active' : ''}>
      <Link to={to} {...rest}/>
    </li>
  )}
</Route>

如何在Flow中使用<Route>组件:

import * as React from 'react';
type Props = {
  children: (data: { match: boolean }) => React.Node,
  path: string,
  // 其它props...
};

class Route extends React.Component<Props> {...}
<Route path={to}>
  {({ match }) => (
    <li className={match ? 'active' : ''}>
      <Link to={to} {...rest}/>
    </li>
  )}
</Route>;

children的类型是一个函数,它接收一些对象类型并返回一个React.NodeReact.Node是React可以渲染的任何值的类型。一个子函数不需要返回React.Node。它可以返回任何类型,但是在这种情况下,react-router需要渲染由children函数返回的结果。

React.Nodechidlren的一般类型,但有时你可能想要使用React.Node,同时排除一些像字符串和数字的基本类型。例如,React Native <View>组件就执行了此操作。

React Native <View>组件将允许任何原始值或任何React元素作为其子元素。 但是,<View>不允许字符串或数字作为子项! 你可以使用React.Node作为<View>的子类型,但是React.Node包含了我们不希望用于<View>的字符串。 所以我们需要创建自己的类型。

import * as React from 'react';

type ReactNodeWithoutStrings = React.ChildrenArray<
  | void
  | null
  | boolean
  | React.Element<any>
>;

type Props = {
  children?: ReactNodeWithoutStrings,
  // other props...
};

class View extends React.Component<Props> {
  // implementation...
}

React.ChildrenArray<T>是为React嵌套数组数据结构建模的一种类型。 ReactNodeWithoutStrings使用React.ChildrenArray <T>作为null,boolean或React元素的任意嵌套数组。

React.Element <typeof Component>是React元素的类型,如<div /><MyComponent />值得注意的是元素和组件不一样!

高阶组件(Higher-order Components)

学习如何在flow中使用React高阶组件

高阶组件模式是React中流行的一种模式。如果你还不知道更高级的组件是什么,那么请确保在继续之前阅读高阶组件的React文档。

要学习如何使用更高阶的组件,我们先看看Recompose 中有关高阶组件的例子。Recompose是一个流行的React库,提供了许多更高阶的组件,其作者也是Redux的编写者。下面是Recompose中关于mapProps()高阶组件的部分实现。

mapProps()接收一个函数,其改变传入值后返回新的值:

function MyComponent({ bar }: { bar: number }) {
  return <div>{bar}</div>;
}

const MyEnhancedComponent = mapProps(
  ({ foo }) => ({ bar: foo + 1 }),   // 接收原始bar的值,返回加1后的新的bar值
)(MyComponent);

<MyEnhancedComponent foo={1} />; // bar的值为2

我们将使用React.ComponentType<Props>来定义MyComponent类型和MyEnhancedComponent类型。 React.ComponentType<Props>是无状态功能组件和类组件的联合,其中Props是组件的props的定义类型。

我们希望mapProps()返回一个函数,该函数将React组件作为其第一个也是唯一的参数,并返回一个React组件。

import * as React from 'react';

function mapProps(): (React.ComponentType<any>) => React.ComponentType<any> {
  return Component => {
    // implementation...
  };
}

上面我们使用了React.ComponentType<any>定义类型!接下来我们将使用范型函数类型来代替any类型。

import * as React from 'react';

function mapProps<PropsInput: {}, PropsOutput: {}>(
  // TODO
): (React.ComponentType<PropsOutput>) => React.ComponentType<PropsInput> {
  return Component => {
    // implementation...
  };
}

PropsInputPropsOutput绑定了{}。这意味着PropsInputPropsOutput必须是对象类型,所以在mapProps()的实现中如果你类型非对象将无法传递PropsInput或PropsOutput。

现在,为mapperFn添加类型,它接收PropsInput并返回PropsOutputmapProps()

import * as React from 'react';

function mapProps<PropsInput: {}, PropsOutput: {}>(
  mapperFn: (PropsInput) => PropsOutput,
): (React.ComponentType<PropsOutput>) => React.ComponentType<PropsInput> {
  return Component => {
    // implementation...
  };
}

现在你可以放心地使用mapProps()来确保你的类型是正确的。

用高阶组件注入Props

高阶组件的常见用法是注入一个prop。我们先看看不注入props的高阶组件写法:

import * as React from 'react';

function injectProp<Props: {}>(
  Component: React.ComponentType<Props>,
): React.ComponentType<Props> {
  // implementation...
}

这个泛型函数接收一个React组件,返回一个React组件。 要从返回的组件中删除一个prop,我们可以使用$Diff

import * as React from 'react';

function injectProp<Props: {}>(
  Component: React.ComponentType<Props>,
): React.ComponentType<$Diff<Props, { foo: number | void }>> {
  // implementation...
}

这里使用$Diff来表示props的类型,并返回除number类型的foo外的Props中的所有东西。

注意:如果foo不存在Props中会抛出异常,$Diff<{},{foo: number}>将抛出异常。所以需要与void的联合$Diff <{}, {foo: number | void}>。一个可选的prop不会完全删除foo$Diff <{foo: number}, {foo?: number}>

现在我们可以使用injectProp()来注入foo

import * as React from 'react';

function injectProp<Props: {}>(
  Component: React.ComponentType<Props>,
): React.ComponentType<$Diff<Props, { foo: number | void }>> {
  return function WrapperComponent(props: Props) {
    return <Component {...props} foo={42} />;
  };
}

class MyComponent extends React.Component<{
  a: number,
  b: number,
  foo: number,
}> {}

const MyEnhancedComponent = injectProp(MyComponent);

// 尽管MyComponent需要foo,但是这里不是必须传入.
<MyEnhancedComponent a={1} b={2} />;

使用React.ElementConfig<>支持defaultProps

到目前为止,我们编写的高阶组件都必须有defaultProps。为了保留defaultProps的可选性,你可以使用React.ElementConfig<typeof Component>

function myHOC<Props, Component: React.ComponentType<Props>>(
  WrappedComponent: Component
): React.ComponentType<React.ElementConfig<Component>> {
  return props => <WrappedComponent {...props} />;
}

Redux

Redux有三个主要部分需要使用flow定义:

  • State
  • Actions
  • Reducers

Redux state

定义State与定义其它对象是一样的

type State = {
  users: Array<{
    id: string,
    name: string,
    age: number,
    phoneNumber: string,
  }>,
  activeUserID: string,
  // ...
};

确保Redux state不变性

Redux state 本身意味着不可变:创建一个新的状态对象,而不是改变单个对象的属性。

你可以通过在整个状态对象中使用“协变”属性将每个属性有效地设置为“只读”来强制执行此操作。

type State = {
  +users: Array<{
    +id: string
    +name: string,
    +age: number,
    +phoneNumber: string,
  }>,
  +activeUserID: string,
  // ...
};

而当你尝试改变state中的属性值时,flow会抛出异常

// @flow
type State = {
  +foo: string
};

let state: State = {
  foo: "foo"
};

state.foo = "bar"; // Error!

Redux actions

Redux actions的基本类型是一个带type属性的对象

type Action = {
  +type: string,
};

你可能想要为actions定义更具体的类型,那么使用不相交的联合和每个单独的类型。这样flow可以更好地理解reducer

type Action =
  | { type: "FOO", foo: number }
  | { type: "BAR", bar: boolean }
  | { type: "BAZ", baz: string };

Redux creators

// @flow
type FooAction = { type: "FOO", foo: number };
type BarAction = { type: "BAR", bar: boolean };

type Action =
  | FooAction
  | BarAction;

function foo(value: number): FooAction {
  return { type: "FOO", foo: value };
}

function bar(value: boolean): BarAction {
  return { type: "BAR", bar: value };
}

Redux thunk actions

使用Redux thunk actions需要为ThunkAction添加函数DispatchGetStateGetState是一个返回Object的函数。 Dispatch接收Action,ThunkAction,PromiseAction和Array <Action>的不相交联合,并可以返回any

type Dispatch = (action: Action | ThunkAction | PromiseAction) => any;
type GetState = () => State;
type ThunkAction = (dispatch: Dispatch, getState: GetState) => any;
type PromiseAction = Promise<Action>;

然后编写thunk action creator,将ThunkAction的返回类型添加到creator中。

type Action =
  | { type: "FOO", foo: number }
  | { type: "BAR", bar: boolean };

type GetState = () => State;
type PromiseAction = Promise<Action>;
type ThunkAction = (dispatch: Dispatch, getState: GetState) => any;
type Dispatch = (action: Action | ThunkAction | PromiseAction | Array<Action>) => any;

function foo(): ThunkAction {
  return (dispatch, getState) => {
    const baz = getState().baz
    dispatch({ type: "BAR", bar: true })
    doSomethingAsync(baz)
      .then(value => {
        dispatch({ type: "FOO", foo: value })
      })
    }
}

Redux reducers

Reducers接收并合并传入的状态和行为。

function reducer(state: State, action: Action): State {
  // ...
}

你还可以default中使用空类型来验证你是否处理了每种类型的操作。

// @flow
type State = { +value: boolean };
type FooAction = { type: "FOO", foo: boolean };
type BarAction = { type: "BAR", bar: boolean };
type Action = FooAction | BarAction;

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "FOO": return { ...state, value: action.foo };
    case "BAR": return { ...state, value: action.bar };
    default:
      (action: empty);
      return state;
  }
}

参考文章

类型参考(Type Reference)

由React模块导出的所有公共Flow类型的参考。

当使用高级React模式时,React会导出一些可能对你有用的公共类型。以下是每种类型的完整参考,以及一些如何使用它们的例子。
目录:

这些类型导出全部来自React模块。 如果你想访问React对象的成员(例如React.NodeReact.StatelessFunctionalComponent),并且将React作为ES模块导入,那么你应该把React作为一个命名空间导入:

import * as React from 'react';

你也可以使用CommonJS的方式导入React

const React = require('react');

你还可以在ES模块环境或CommonJS环境中使用命名的类型导入:

import type {Node} from 'react';

注意:当我们使用默认导入的方式导入React:

import React from 'react';

你将有权访问React导出的所有值,但是无法访问目录列出的类型!Flow不会将类型添加到默认导出,因为默认导出可以是任何值(如数字)。Flow会将导出的命名类型添加到ES命名空间对象,你可以通过import * as React from 'react'react获取,因为Flow知道你是否导出了与导出类型具有相同名称的值。除了这种方式,你还可以使用命名类型去访问具体类型如:import type {Node} from 'react'

React.Node

这表示React可以渲染的任何节点。React.Node可以是undefined,null,布尔值,数字,字符串,React元素或可递归数组。

class MyComponent extends React.Component<{}> {
  render(): React.Node {
    // ...
  }
}
function MyComponent(props: {}): React.Node {
  // ...
}

这里对render()方法或无状态函数组件的返回类型的定义不是必须的。

下面是React.Node作为子类型的示例:

function MyComponent({ children }: { children: React.Node }) {
  return <div>{children}</div>;
}

所有react-dom JSX内部函数都有React.Node作为它们的子类型。 <div>,<span>和其他所有内容。React.Node的定义可以粗略地等同于React.ChildrenArray<T>

type Node = React.ChildrenArray<void | null | boolean | string | number | React.Element<any>>;

React.Element<typeof Component>

React元素是JSX元素的值的类型:

const element: React.Element<'div'> = <div />;

React.Element<typeof Component>React.createElement()的返回类型。

React.Element <typeof Component>接收一个单一的类型参数,typeof Componenttypeof Component是React元素的组件类型。对于一个内部元素,typeof Component将是你使用的内在的字符串文字。以下是一些示例:

(<div />: React.Element<'div'>); // OK
(<span />: React.Element<'span'>); // OK
(<div />: React.Element<'span'>); // Error: div is not a span.

typeof Component也可以React 组件或无状态函数组件

class Foo extends React.Component<{}> {}
function Bar(props: {}) {}

(<Foo />: React.Element<typeof Foo>); // OK
(<Bar />: React.Element<typeof Bar>); // OK
(<Foo />: React.Element<typeof Bar>); // Error: Foo is not Bar

React.ChildrenArray<T>

React children数组可以是单个值或嵌套到任何级别的数组。它被设计来与React.Children API一起使用。

例如,如果你想从React.ChildrenArray<T>获得一个正常的JavaScript数组,请看下面的例子:

import * as React from 'react';

// 单个值
const children: React.ChildrenArray<number> = 42;
// 任何嵌套级别的数组
const children: React.ChildrenArray<number> = [[1, 2], 3, [4, 5]];

// 使用`React.Children` API 展开数组
const array: Array<number> = React.Children.toArray(children);

React.ComponentType<Props>

这是类组件或无状态函数组件的联合。这是你要用于接收或返回React组件(如高阶组件或其他公共组件)的函数的类型。

如何使用React.ComponentType<Props>React.Element <typeof Component>来构造一个具有一组特定props的组件:

type Props = {
  foo: number,
  bar: number,
};

function createMyElement<C: React.ComponentType<Props>>(
  Component: C,
): React.Element<C> {
  return <Component foo={1} bar={2} />;
}

React.ComponentType<Props>不包含像divspan这样的内在的JSX元素类型。React.ComponentType<Props>的定义大致是:

type ComponentType<Props> =
  | React.StatelessFunctionalComponent<Props>
  | Class<React.Component<Props, any>>;

React.StatelessFunctionalComponent<Props>

无状态函数组件的类型

type StatelessFunctionalComponent<Props> =
  (props: Props) => React.Node;

React.ElementType

React.ComponentType<Props>类似,除了它也包含JSX内在函数(字符串)。

type ElementType =
  | string
  | React.ComponentType<any>;

React.Key

React元素的key值类型。它是一个字符串和数字的联合,定义如下:

type Key = string | number;

React.Ref<typeof Component>

React元素上的ref属性的类型。React.Ref<typeof Component>可以是一个字符串或ref函数。ref函数将接收一个唯一的参数,它将是使用React.ElementRef<typeof Component>检索的元素实例,或者是null,因为React在卸载时会将null传递给ref函数。与React.Element<typeof Component>一样,typeof Component必须是React组件的类型。React.Ref<typeof Component>的定义大致是:

type Ref<C> =
  | string
  | (instance: React.ElementRef<C> | null) => mixed;

React.ElementProps<typeof Component>

获取React元素类型的props。类型可以是React类组件,无状态功能组件或JSX内部字符串。此类型用于React.Element<typeof Component>上的props属性。与React.Element<typeof Component>一样,必须是React组件的类型

React.ElementConfig<typeof Component>

React.ElementProps<typeof Component>,此方法获取除defaultProps外的组件的props的类型。

import * as React from 'react';

class MyComponent extends React.Component<{foo: number}> {
  static defaultProps = {foo: 42};

  render() {
    return this.props.foo;
  }
}

// `React.ElementProps<>` 需要有`foo`值即使本身是 `defaultProp`.
({foo: 42}: React.ElementProps<typeof MyComponent>);

// `React.ElementConfig<>` 不需要 `foo` 值,因为本身就是`defaultProp`.
({}: React.ElementConfig<typeof MyComponent>);

React.Element<typeof Component>一样,必须是React组件的类型。

React.ElementRef<typeof Component>

获取React元素的实例类型。对于各种组件类型,实例会有所不同:

  • React类组件将是类实例。所以如果你有类Foo继承React.Component<{}>{}并使用React.ElementRef<typeof Foo>,那么类型将是Foo的实例。
  • React无状态函数组件没有返回实例,所以React.ElementRef <typeof Bar>(当Bar是bar(){})时,会给你undefined的类型。
  • div的JSX内联函数会返回DOM实例。React.ElementRef<'div'>HTMLDivElementReact.ElementRef<'input'>将会是HTMLInputElement

与React.Element<typeof Component>一样,必须是React组件的类型。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260