学习如何在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>
是一个带有两个类型参数泛型类型,Props
和State
。第二个参数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事件是:
-
SyntheticEvent<T>
--- Event -
SyntheticAnimationEvent<T>
--- AnimationEvent -
SyntheticCompositionEvent<T>
--- CompositionEvent -
SyntheticInputEvent<T>
--- InputEvent -
SyntheticUIEvent<T>
--- UIEvent -
SyntheticFocusEvent<T>
--- FocusEvent -
SyntheticKeyboardEvent<T>
--- KeyboardEvent -
SyntheticMouseEvent<T>
--- MouseEvent -
SyntheticDragEvent<T>
--- DragEvent -
SyntheticWheelEvent<T>
--- WheelEvent -
SyntheticTouchEvent<T>
--- TouchEvent -
SyntheticTransitionEvent<T>
--- TransitionEvent
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>;
我们将props
中children
的类型设置为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.Node
,React.Node
是React可以渲染的任何值的类型。一个子函数不需要返回React.Node
。它可以返回任何类型,但是在这种情况下,react-router
需要渲染由children
函数返回的结果。
React.Node
是chidlren
的一般类型,但有时你可能想要使用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...
};
}
PropsInput
和PropsOutput
绑定了{}。这意味着PropsInput
和PropsOutput
必须是对象类型,所以在mapProps()
的实现中如果你类型非对象将无法传递PropsInput或PropsOutput。
现在,为mapperFn
添加类型,它接收PropsInput
并返回PropsOutput
给mapProps()
。
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
添加函数Dispatch
和GetState
。GetState
是一个返回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;
}
}
参考文章
- Using Redux with Flow - Alex Kotliarskyi
- Redux and Flowtype - Christian de Botton
类型参考(Type Reference)
由React模块导出的所有公共Flow类型的参考。
当使用高级React模式时,React会导出一些可能对你有用的公共类型。以下是每种类型的完整参考,以及一些如何使用它们的例子。
目录:
React.Node
React.Element<typeof Component>
React.ChildrenArray<T>
React.ComponentType<Props>
React.StatelessFunctionalComponent<Props>
React.ElementType
React.Key
React.Ref<typeof Component>
React.ElementProps<typeof Component>
React.ElementConfig<typeof Component>
React.ElementRef<typeof Component>
这些类型导出全部来自React模块。 如果你想访问React对象的成员(例如React.Node
或React.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 Component
。 typeof 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>
不包含像div
或span
这样的内在的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'>
是HTMLDivElement
。React.ElementRef<'input'>
将会是HTMLInputElement
。
与React.Element<typeof Component>一样,必须是React组件的类型。