使用TypeScript编写React的优秀实践!

大家好,我是 CUGGZ。

公司主营业务:网站建设、成都网站设计、移动网站开发等业务。帮助企业客户真正实现互联网宣传,提高企业的竞争能力。创新互联是一支青春激扬、勤奋敬业、活力青春激扬、勤奋敬业、活力澎湃、和谐高效的团队。公司秉承以“开放、自由、严谨、自律”为核心的企业文化,感谢他们对我们的高要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。创新互联推出丽水免费做网站回馈大家。

在单独使用 TypeScript 时没有太多坑,不过和 React 结合之后就会复杂很多。下面就来看一看如何在 React 项目中优雅的使用 TypeScript!

一、组件声明

在React中,组件的声明方式有两种:函数组件和类组件, 来看看这两种类型的组件声明时是如何定义TS类型的。

1. 类组件

类组件的定义形式有两种:React.Component 和 React.PureComponent,它们都是泛型接口,接收两个参数,第一个是props类型的定义,第二个是state类型的定义,这两个参数都不是必须的,没有时可以省略:

interface IProps {
name: string;
}

interface IState {
count: number;
}

class App extends React.Component {
state = {
count: 0
};

render() {
return (

{this.state.count}
{this.props.name}

);
}
}

export default App;

React.PureComponent 也是差不多的:

class App extends React.PureComponent {}

React.PureComponent是有第三个参数的,它表示getSnapshotBeforeUpdate的返回值。

那PureComponent和Component 的区别是什么呢?它们的主要区别是PureComponent中的shouldComponentUpdate 是由自身进行处理的,不需要我们自己处理,所以PureComponent可以在一定程度上提升性能。

有时候可能会见到这种写法,实际上和上面的效果是一样的:

import React, {PureComponent, Component} from "react";

class App extends PureComponent {}

class App extends Component {}

那如果定义时候我们不知道组件的props的类型,只有在调用时才知道组件类型,该怎么办呢?这时泛型就发挥作用了:

// 定义组件
class MyComponent

extends React.Component

{
internalProp: P;
constructor(props: P) {
super(props);
this.internalProp = props;
}
render() {
return (
hello world
);
}
}

// 使用组件
type IProps = { name: string; age: number; };

name="React" age={18} />; // Success
name="TypeScript" age="hello" />; // Error

2. 函数组件

通常情况下,函数组件我是这样写的:

interface IProps {
name: string
}

const App = (props: IProps) => {
const {name} = props;

return (

hello world


{name}



);
}

export default App;

除此之外,函数类型还可以使用React.FunctionComponent来定义,也可以使用其简写React.FC,两者效果是一样的。它是一个泛型接口,可以接收一个参数,参数表示props的类型,这个参数不是必须的。它们就相当于这样:

type React.FC

= React.FunctionComponent

最终的定义形式如下:

interface IProps {
name: string
}

const App: React.FC = (props) => {
const {name} = props;
return (

hello world


{name}



);
}

export default App;

当使用这种形式来定义函数组件时,props中默认会带有children属性,它表示该组件在调用时,其内部的元素,来看一个例子,首先定义一个组件,组件中引入了Child1和Child2组件:

import Child1 from "./child1";
import Child2 from "./child2";

interface IProps {
name: string;
}
const App: React.FC = (props) => {
const { name } = props;
return (


TypeScript

);
};

export default App;

Child1组件结构如下:

interface IProps {
name: string;
}
const Child1: React.FC = (props) => {
const { name, children } = props;
console.log(children);
return (

hello child1


{name}



);
};

export default Child1;

我们在Child1组件中打印了children属性,它的值是一个数组,包含Child2对象和后面的文本:

使用 React.FC 声明函数组件和普通声明的区别如下:

  • React.FC 显式地定义了返回类型,其他方式是隐式推导的;
  • React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全;
  • React.FC 为 children 提供了隐式的类型(ReactElement | null)。

那如果我们在定义组件时不知道props的类型,只有调用时才知道,那就还是用泛型来定义props的类型。对于使用function定义的函数组件:

// 定义组件
function MyComponent

(props: P) {
return (

{props}

);
}

// 使用组件
type IProps = { name: string; age: number; };

name="React" age={18} />; // Success
name="TypeScript" age="hello" />; // Error

如果使用箭头函数定义的函数组件,直接这样调用时错误的:

const MyComponent = 

(props: P) {
return (

{props}

);
}

必须使用extends关键字来定义泛型参数才能被成功解析:

const MyComponent = 

(props: P) {
return (

{props}

);
}

二、React内置类型

1. JSX.Element

先来看看JSX.Element类型的声明:

declare global {
namespace JSX {
interface Element extends React.ReactElement { }
}
}

可以看到,JSX.Element是ReactElement的子类型,它没有增加属性,两者是等价的。也就是说两种类型的变量可以相互赋值。

JSX.Element 可以通过执行 React.createElement 或是转译 JSX 获得:

const jsx = 
hello

const ele = React.createElement("div", null, "hello");

2. React.ReactElement

React 的类型声明文件中提供了 React.ReactElement<T>,它可以让我们通过传入<T/>来注解类组件的实例化,它在声明文件中的定义如下:

interface ReactElement

= string | JSXElementConstructor> {
type: T;
props: P;
key: Key | null;
}

ReactElement是一个接口,包含type,props,key三个属性值。该类型的变量值只能是两种:null 和 ReactElement实例。

通常情况下,函数组件返回ReactElement(JXS.Element)的值。

3. React.ReactNode

ReactNode类型的声明如下:

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

可以看到,ReactNode是一个联合类型,它可以是string、number、ReactElement、null、boolean、ReactNodeArray。由此可知。ReactElement类型的变量可以直接赋值给ReactNode类型的变量,但反过来是不行的。

类组件的 render 成员函数会返回 ReactNode 类型的值:

class MyComponent extends React.Component {
render() {
return
hello world

}
}
// 正确
const component: React.ReactNode = ;
// 错误
const component: React.ReactNode = ;

上面的代码中,给component变量设置了类型是Mycomponent类型的react实例,这时只能给其赋值其为MyComponent的实例组件。

通常情况下,类组件通过 render() 返回 ReactNode的值。

4. CSSProperties

先来看看React的声明文件中对CSSProperties 的定义:

export interface CSSProperties extends CSS.Properties {
/**
* The index signature was removed to enable closed typing for style
* using CSSType. You're able to use type assertion or module augmentation
* to add properties or an index signature of your own.
*
* For examples and more information, visit:
* https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors
*/
}

React.CSSProperties是React基于TypeScript定义的CSS属性类型,可以将一个方法的返回值设置为该类型:

import * as React from "react";

const classNames = require("./sidebar.css");

interface Props {
isVisible: boolean;
}

const divStyle = (props: Props): React.CSSProperties => ({
width: props.isVisible ? "23rem" : "0rem"
});

export const SidebarComponent: React.StatelessComponent = props => (

{props.children}

);

这里divStyle组件的返回值就是React.CSSProperties类型。

我们还可以定义一个CSSProperties类型的变量:

const divStyle: React.CSSProperties = {
width: "11rem",
height: "7rem",
backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})`
};

这个变量可以在HTML标签的style属性上使用:

在React的类型声明文件中,style属性的类型如下:

style?: CSSProperties | undefined;

三、React Hooks

1. useState

默认情况下,React会为根据设置的state的初始值来自动推导state以及更新函数的类型:

如果已知state 的类型,可以通过以下形式来自定义state的类型:

const [count, setCount] = useState(1)

如果初始值为null,需要显式地声明 state 的类型:

const [count, setCount] = useState(null);

如果state是一个对象,想要初始化一个空对象,可以使用断言来处理:

const [user, setUser] = React.useState({} as IUser);

实际上,这里将空对象{}断言为IUser接口就是欺骗了TypeScript的编译器,由于后面的代码可能会依赖这个对象,所以应该在使用前及时初始化 user 的值,否则就会报错。

下面是声明文件中 useState 的定义:

function useState(initialState: S | (() => S)): [S, Dispatch>];
// convenience overload when first argument is omitted
/**
* Returns a stateful value, and a function to update it.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usestate
*/

function useState(): [S | undefined, Dispatch>];
/**
* An alternative to `useState`.
*
* `useReducer` is usually preferable to `useState` when you have complex state logic that involves
* multiple sub-values. It also lets you optimize performance for components that trigger deep
* updates because you can pass `dispatch` down instead of callbacks.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usereducer
*/

可以看到,这里定义两种形式,分别是有初始值和没有初始值的形式。

2. useEffect

useEffect的主要作用就是处理副作用,它的第一个参数是一个函数,表示要清除副作用的操作,第二个参数是一组值,当这组值改变时,第一个参数的函数才会执行,这让我们可以控制何时运行函数来处理副作用:

useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source]
);

当函数的返回值不是函数或者effect函数中未定义的内容时,如下:

useEffect(
() => {
subscribe();
return null;
}
);

TypeScript就会报错:

来看看useEffect在类型声明文件中的定义:

// Destructors are only allowed to return void.
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };

// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);

// TODO (TypeScript 3.0): ReadonlyArray
type DependencyList = ReadonlyArray;

function useEffect(effect: EffectCallback, deps?: DependencyList): void;
// NOTE: this does not accept strings, but this will have to be fixed by removing strings from type Ref
/**
* `useImperativeHandle` customizes the instance value that is exposed to parent components when using
* `ref`. As always, imperative code using refs should be avoided in most cases.
*
* `useImperativeHandle` should be used with `React.forwardRef`.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useimperativehandle
*/

可以看到,useEffect的第一个参数只允许返回一个函数。

3. useRef

当使用 useRef 时,我们可以访问一个可变的引用对象。可以将初始值传递给 useRef,它用于初始化可变 ref 对象公开的当前属性。当我们使用useRef时,需要给其指定类型:

const nameInput = React.useRef(null)

这里给实例的类型指定为了input输入框类型。

当useRef的初始值为null时,有两种创建的形式,第一种:

const nameInput = React.useRef(null)
nameInput.current.innerText = "hello world";

这种形式下,ref1.current是只读的(read-only),所以当我们将它的innerText属性重新赋值时会报以下错误:

Cannot assign to 'current' because it is a read-only property.

那该怎么将current属性变为动态可变得的,先来看看类型声明文件中 useRef 是如何定义的:

function useRef(initialValue: T): MutableRefObject;
// convenience overload for refs given as a ref prop as they typically start with a null value
/**
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
* (`initialValue`). The returned object will persist for the full lifetime of the component.
*
* Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
* value around similar to how you’d use instance fields in classes.
*
* Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type
* of the generic argument.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useref
*/

这段代码的第十行的告诉我们,如果需要useRef的直接可变,就需要在泛型参数中包含'| null',所以这就是当初始值为null的第二种定义形式:

const nameInput = React.useRef(null);

这种形式下,nameInput.current就是可写的。不过两种类型在使用时都需要做类型检查:

nameInput.current?.innerText = "hello world";

那么问题来了,为什么第一种写法在没有操作current时没有报错呢?因为useRef在类型定义式具有多个重载声明,第一种方式就是执行的以下函数重载:

function useRef(initialValue: T|null): RefObject;
// convenience overload for potentially undefined initialValue / call with 0 arguments
// has a default to stop it from defaulting to {} instead
/**
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
* (`initialValue`). The returned object will persist for the full lifetime of the component.
*
* Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
* value around similar to how you’d use instance fields in classes.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useref
*/

从上useRef的声明中可以看到,function useRef的返回值类型化是MutableRefObject,这里面的T就是参数的类型T,所以最终nameInput 的类型就是React.MutableRefObject。

注意,上面用到了HTMLInputElement类型,这是一个标签类型,这个操作就是用来访问DOM元素的。

4. useCallback

先来看看类型声明文件中对useCallback的定义:

function useCallback any>(callback: T, deps: DependencyList): T;
/**
* `useMemo` will only recompute the memoized value when one of the `deps` has changed.
*
* Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in
* the second argument.
*
* ```ts
* function expensive () { ... }
*
* function Component () {
* const expensiveResult = useMemo(expensive, [expensive])
* return ...
* }
* ```
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usememo
*/

useCallback接收一个回调函数和一个依赖数组,只有当依赖数组中的值发生变化时才会重新执行回调函数。来看一个例子:

const add = (a: number, b: number) => a + b;

const memoizedCallback = useCallback(
(a) => {
add(a, b);
},
[b]
);

这里我们没有给回调函数中的参数a定义类型,所以下面的调用方式都不会报错:

memoizedCallback("hello");
memoizedCallback(5)

尽管add方法的两个参数都是number类型,但是上述调用都能够用执行。所以为了更加严谨,我们需要给回调函数定义具体的类型:

const memoizedCallback = useCallback(
(a: number) => {
add(a, b);
},
[b]
);

这时候如果再给回调函数传入字符串就会报错了:

所有,需要注意,在使用useCallback时需要给回调函数的参数指定类型。

5. useMemo

先来看看类型声明文件中对useMemo的定义:

function useMemo(factory: () => T, deps: DependencyList | undefined): T;
/**
* `useDebugValue` can be used to display a label for custom hooks in React DevTools.
*
* NOTE: We don’t recommend adding debug values to every custom hook.
* It’s most valuable for custom hooks that are part of shared libraries.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usedebugvalue
*/

useMemo和useCallback是非常类似的,但是它返回的是一个值,而不是函数。所以在定义useMemo时需要定义返回值的类型:

let a = 1;
setTimeout(() => {
a += 1 分享文章:使用TypeScript编写React的优秀实践!
标题网址:http://www.csdahua.cn/qtweb/news25/288125.html

网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网