如何优雅地在 React 中使用TypeScript,看这一篇就够了!(1)

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: 毕业已有3月有余,工作用的技术栈主要是React hooks + TypeScript。其实在单独使用 TypeScript 时没有太多的坑,不过和React结合之后就会复杂很多。本文就来聊一聊TypeScript与React一起使用时经常遇到的一些类型定义的问题。阅读本文前,希望你能有一定的React和TypeScript基础

毕业已有3月有余,工作用的技术栈主要是React hooks + TypeScript。其实在单独使用 TypeScript 时没有太多的坑,不过和React结合之后就会复杂很多。本文就来聊一聊TypeScript与React一起使用时经常遇到的一些类型定义的问题。阅读本文前,希望你能有一定的React和TypeScript基础。文章内容较多,建议先收藏再学习!


一、组件声明


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


1. 类组件


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


interface IProps {
  name: string;
}
interface IState {
  count: number;
}
class App extends React.Component<IProps, IState> {
  state = {
    count: 0
  };
  render() {
    return (
      <div>
        {this.state.count}
        {this.props.name}
      </div>
    );
  }
}
export default App;
复制代码


React.PureComponent<P, S={} SS={}> 也是差不多的:

class App extends React.PureComponent<IProps, IState> {}
复制代码


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


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


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

import React, {PureComponent, Component} from "react";
class App extends PureComponent<IProps, IState> {}
class App extends Component<IProps, IState> {}
复制代码


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

// 定义组件
class MyComponent<P> extends React.Component<P> {
  internalProp: P;
  constructor(props: P) {
    super(props);
    this.internalProp = props;
  }
  render() {
    return (
       <span>hello world</span>
    );
  }
}
// 使用组件
type IProps = { name: string; age: number; };
<MyComponent<IProps> name="React" age={18} />;          // Success
<MyComponent<IProps> name="TypeScript" age="hello" />;  // Error
复制代码


2. 函数组件


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

interface IProps {
  name: string
}
const App = (props: IProps) => {
  const {name} = props;
  return (
    <div className="App">
      <h1>hello world</h1>
      <h2>{name}</h2>
    </div>
  );
}
export default App;
复制代码


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

type React.FC<P = {}> = React.FunctionComponent<P>
复制代码


最终的定义形式如下:

interface IProps {
  name: string
}
const App: React.FC<IProps> = (props) => {
  const {name} = props;
  return (
    <div className="App">
      <h1>hello world</h1>
      <h2>{name}</h2>
    </div>
  );
}
export default App;
复制代码


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


import Child1 from "./child1";
import Child2 from "./child2";
interface IProps {
  name: string;
}
const App: React.FC<IProps> = (props) => {
  const { name } = props;
  return (
    <Child1 name={name}>
      <Child2 name={name} />
      TypeScript
    </Child1>
  );
};
export default App;
复制代码


Child1组件结构如下:

interface IProps {
  name: string;
}
const Child1: React.FC<IProps> = (props) => {
  const { name, children } = props;
  console.log(children);
  return (
    <div className="App">
      <h1>hello child1</h1>
      <h2>{name}</h2>
    </div>
  );
};
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<P>(props: P) {
  return (
    <span>
      {props}
    </span>
  );
}
// 使用组件
type IProps = { name: string; age: number; };
<MyComponent<IProps> name="React" age={18} />;          // Success
<MyComponent<IProps> name="TypeScript" age="hello" />;  // Error
复制代码


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

const MyComponent = <P>(props: P) {
  return (
    <span>
      {props}
    </span>
  );
}
复制代码


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

const MyComponent = <P extends any>(props: P) {
  return (
    <span>
      {props}
    </span>
  );
}
复制代码


二、React内置类型


1. JSX.Element


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

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> { }
  }
}
复制代码


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

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

const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");
复制代码


2. React.ReactElement


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

interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
   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<ReactNode> {}
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 <div>hello world</div>
    }
}
// 正确
const component: React.ReactNode<MyComponent> = <MyComponent />;
// 错误
const component: React.ReactNode<MyComponent> = <OtherComponent />;
复制代码


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

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


4. CSSProperties


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

export interface CSSProperties extends CSS.Properties<string | number> {
  /**
   * 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 => (
  <div id="mySidenav" className={classNames.sidenav} style={divStyle(props)}>
    {props.children}
  </div>
);
复制代码


这里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属性上使用:

<div style={divStyle} />
复制代码


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

style?: CSSProperties | undefined;


相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
4月前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。
|
13天前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
1月前
|
JavaScript 前端开发 安全
使用 TypeScript 加强 React 组件的类型安全
【10月更文挑战第1天】使用 TypeScript 加强 React 组件的类型安全
37 3
|
3月前
|
JavaScript 前端开发 安全
[译] 使用 TypeScript 开发 React Hooks
[译] 使用 TypeScript 开发 React Hooks
|
3月前
|
开发者 自然语言处理 存储
语言不再是壁垒:掌握 JSF 国际化技巧,轻松构建多语言支持的 Web 应用
【8月更文挑战第31天】JavaServer Faces (JSF) 框架提供了强大的国际化 (I18N) 和本地化 (L10N) 支持,使开发者能轻松添加多语言功能。本文通过具体案例展示如何在 JSF 应用中实现多语言支持,包括创建项目、配置语言资源文件 (`messages_xx.properties`)、设置 `web.xml`、编写 Managed Bean (`LanguageBean`) 处理语言选择,以及使用 Facelets 页面 (`index.xhtml`) 显示多语言消息。通过这些步骤,你将学会如何配置 JSF 环境、编写语言资源文件,并实现动态语言切换。
39 0
|
3月前
|
前端开发 JavaScript 安全
【前端开发新境界】React TypeScript融合之路:从零起步构建类型安全的React应用,全面提升代码质量和开发效率的实战指南!
【8月更文挑战第31天】《React TypeScript融合之路:类型安全的React应用开发》是一篇详细教程,介绍如何结合TypeScript提升React应用的可读性和健壮性。从环境搭建、基础语法到类型化组件、状态管理及Hooks使用,逐步展示TypeScript在复杂前端项目中的优势。适合各水平开发者学习,助力构建高质量应用。
55 0
|
4月前
|
JavaScript 前端开发 IDE
React 项目中有效地使用 TypeScript
React 项目中有效地使用 TypeScript
|
2月前
|
JavaScript
typeScript进阶(9)_type类型别名
本文介绍了TypeScript中类型别名的概念和用法。类型别名使用`type`关键字定义,可以为现有类型起一个新的名字,使代码更加清晰易懂。文章通过具体示例展示了如何定义类型别名以及如何在函数中使用类型别名。
39 1
typeScript进阶(9)_type类型别名
|
27天前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
42 0
|
27天前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧