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閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380