组件声明
在React中,组件的声明方式有两种:函数组件和类组件, 来看看这两种类型的组件声明时是如何定义TS类型的。
类组件
类组件的定义形式有两种:React.Component<P, S={}>
和 React.PureComponent<P, S={} SS={}>
,它们都是泛型接口,接收两个参数,第一个是props类型的定义,第二个是state类型的定义,这两个参数都不是必须的,没有时可以省略:
1 | interface IProps { |
React.PureComponent<P, S={} SS={}>
也是差不多的:
1 | class App extends React.PureComponent<IProps, IState> {} |
React.PureComponent
是有第三个参数的,它表示getSnapshotBeforeUpdate
的返回值。
那PureComponent和Component 的区别是什么呢?它们的主要区别是PureComponent中的shouldComponentUpdate 是由自身进行处理的,不需要我们自己处理,所以PureComponent可以在一定程度上提升性能。
那如果定义时候我们不知道组件的props的类型,只有在调用时才知道组件类型,该怎么办呢?这时泛型就发挥作用了:
1 | // 定义组件 |
函数组件
1 | interface IProps { |
除此之外,函数类型还可以使用React.FunctionComponent<P={}>
来定义,也可以使用其简写React.FC<P={}>
,两者效果是一样的。它是一个泛型接口,可以接收一个参数,参数表示props的类型,这个参数不是必须的。它们就相当于这样:
1 | type React.FC<P = {}> = React.FunctionComponent<P> |
最终的定义形式如下:
1 | interface IProps { |
当使用这种形式来定义函数组件时,props中默认会带有children属性,它表示该组件在调用时,其内部的元素,来看一个例子,首先定义一个组件,组件中引入了Child1和Child2组件:
1 | import Child1 from "./child1"; |
Child1组件结构如下:
1 | interface IProps { |
我们在Child1组件中打印了children属性,它的值是一个数组,包含Child2对象和后面的文本:
使用 React.FC 声明函数组件和普通声明的区别如下:
- React.FC 显式地定义了返回类型,其他方式是隐式推导的;
- React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全;
- React.FC 为 children 提供了隐式的类型(ReactElement | null)。
那如果我们在定义组件时不知道props的类型,只有调用时才知道,那就还是用泛型来定义props的类型。对于使用function定义的函数组件:
1 | // 定义组件 |
React Hooks
useState
默认情况下,React会为根据设置的state的初始值来自动推导state以及更新函数的类型:
如果已知state 的类型,可以通过以下形式来自定义state的类型:
1 | const [count, setCount] = useState<number>(1) |
如果初始值为null,需要显式地声明 state 的类型:
1 | const [count, setCount] = useState<number | null>(null); |
如果state是一个对象,想要初始化一个空对象,可以使用断言来处理:
1 | const [user, setUser] = React.useState<IUser>({} as IUser); |
实际上,这里将空对象{}断言为IUser接口就是欺骗了TypeScript的编译器,由于后面的代码可能会依赖这个对象,所以应该在使用前及时初始化 user 的值,否则就会报错。
根据useState在类型声明文件中的定义:定义两种形式,分别是有初始值和没有初始值的形式。
useEffect
useEffect的主要作用就是处理副作用,它的第一个参数是一个函数,表示要清除副作用的操作,第二个参数是一组值,当这组值改变时,第一个参数的函数才会执行,这让我们可以控制何时运行函数来处理副作用:
1 | useEffect( |
当函数的返回值不是函数或者effect函数中未定义的内容时,如下:
1 | useEffect( |
TypeScript就会报错:
根据useEffect在类型声明文件中的定义:useEffect的第一个参数只允许返回一个函数。
useContext
useContext需要提供一个上下文对象,并返回所提供的上下文的值,当提供者更新上下文对象时,引用这些上下文对象的组件就会重新渲染:
1 | const ColorContext = React.createContext({ color: "green" }); |
在使用useContext时,会自动推断出提供的上下文对象的类型,所以并不需要我们手动设置context的类型。当前,我们也可以使用泛型来设置context的类型:
1 | interface IColor { |
其他
import React
在React项目中使用TypeScript时,普通组件文件后缀为.tsx,公共方法文件后缀为.ts。在. tsx 文件中导入 React 的方式如下:
1 | import * as React from 'react' |
这是一种面向未来的导入方式,如果想在项目中使用以下导入方式:
1 | import React from "react"; |
就需要在tsconfig.json配置文件中进行如下配置:
1 | "compilerOptions": { |
Types or Interfaces?
我们可以使用types或者Interfaces来定义类型吗,那么该如何选择他俩呢?建议如下:
- 在定义公共 API 时(比如编辑一个库)使用 interface,这样可以方便使用者继承接口,这样允许使用最通过声明合并来扩展它们;
- 在定义组件属性(Props)和状态(State)时,建议使用 type,因为 type 的约束性更强。
interface 和 type 在 ts 中是两个不同的概念,但在 React 大部分使用的 case 中,interface 和 type 可以达到相同的功能效果,type 和 interface 最大的区别是:type 类型不能二次编辑,而 interface 可以随时扩展:
1 | interface Animal { |
type对于联合类型是很有用的,比如:type Type = TypeA | TypeB。而interface更适合声明字典类行,然后定义或者扩展它。