如何优雅地在 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 架构模式
相关文章
|
5月前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。
|
1月前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
2月前
|
JavaScript 前端开发 安全
使用 TypeScript 加强 React 组件的类型安全
【10月更文挑战第1天】使用 TypeScript 加强 React 组件的类型安全
39 3
|
4月前
|
JavaScript 前端开发 安全
[译] 使用 TypeScript 开发 React Hooks
[译] 使用 TypeScript 开发 React Hooks
|
4月前
|
开发者 自然语言处理 存储
语言不再是壁垒:掌握 JSF 国际化技巧,轻松构建多语言支持的 Web 应用
【8月更文挑战第31天】JavaServer Faces (JSF) 框架提供了强大的国际化 (I18N) 和本地化 (L10N) 支持,使开发者能轻松添加多语言功能。本文通过具体案例展示如何在 JSF 应用中实现多语言支持,包括创建项目、配置语言资源文件 (`messages_xx.properties`)、设置 `web.xml`、编写 Managed Bean (`LanguageBean`) 处理语言选择,以及使用 Facelets 页面 (`index.xhtml`) 显示多语言消息。通过这些步骤,你将学会如何配置 JSF 环境、编写语言资源文件,并实现动态语言切换。
42 0
|
4月前
|
前端开发 JavaScript 安全
【前端开发新境界】React TypeScript融合之路:从零起步构建类型安全的React应用,全面提升代码质量和开发效率的实战指南!
【8月更文挑战第31天】《React TypeScript融合之路:类型安全的React应用开发》是一篇详细教程,介绍如何结合TypeScript提升React应用的可读性和健壮性。从环境搭建、基础语法到类型化组件、状态管理及Hooks使用,逐步展示TypeScript在复杂前端项目中的优势。适合各水平开发者学习,助力构建高质量应用。
65 0
|
5月前
|
JavaScript 前端开发 IDE
React 项目中有效地使用 TypeScript
React 项目中有效地使用 TypeScript
|
23天前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
59 9
|
2月前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
2月前
|
前端开发 JavaScript API
探索React Hooks:前端开发的革命性工具
【10月更文挑战第5天】探索React Hooks:前端开发的革命性工具