如何优雅地在 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 架构模式
相关文章
|
2月前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。
|
29天前
|
JavaScript 前端开发 安全
[译] 使用 TypeScript 开发 React Hooks
[译] 使用 TypeScript 开发 React Hooks
|
2月前
|
JavaScript 前端开发 IDE
React 项目中有效地使用 TypeScript
React 项目中有效地使用 TypeScript
|
2月前
|
前端开发 JavaScript 开发者
React 和 TypeScript
React 和 TypeScript
|
3月前
|
前端开发 JavaScript 开发者
React和TypeScript各自以其独特的优势赢得了广大开发者的青睐
【6月更文挑战第12天】React和TypeScript是前端开发的强强联合。TypeScript提供静态类型检查和面向对象特性,增强代码健壮性和团队协作效率;React凭借组件化、高性能和丰富生态系统引领UI构建。两者结合,能精确定义React组件类型,提升代码组织和维护性,通过安装TypeScript、配置、编写及构建步骤,可在React项目中实现这一优势。这种结合为前端开发带来进步,未来应用将更加广泛。
48 1
|
4月前
|
JavaScript 前端开发 开发者
【TypeScript技术专栏】TypeScript与React的完美结合
【4月更文挑战第30天】React和TypeScript在前端开发中备受推崇。React以其组件化、高性能和灵活的生态系统引领UI构建,而TypeScript通过静态类型检查和面向对象特性增强了代码的健壮性和可维护性。两者结合,能提升开发效率,降低错误,使React组件结构更清晰。通过安装TypeScript,配置tsconfig.json,然后用TypeScript编写和打包代码,可实现两者的无缝集成。这种结合为前端开发带来更强的代码质量和团队协作效果,随着技术发展,其应用将更加广泛。
70 0
|
4月前
|
前端开发 JavaScript 安全
【亮剑】探讨了在React TypeScript应用中如何通过道具(props)传递CSS样式,以实现模块化、主题化和动态样式
【4月更文挑战第30天】本文探讨了在React TypeScript应用中如何通过道具(props)传递CSS样式,以实现模块化、主题化和动态样式。文章分为三部分:首先解释了样式传递的必要性,包括模块化、主题化和动态样式以及TypeScript集成。接着介绍了内联样式的基本用法和最佳实践,展示了一个使用内联样式自定义按钮颜色的例子。最后,讨论了使用CSS模块和TypeScript接口处理复杂样式的方案,强调了它们在组织和重用样式方面的优势。结合TypeScript,确保了样式的正确性和可维护性,为开发者提供了灵活的样式管理策略。
61 0
|
JavaScript 前端开发 中间件
TypeScript在react项目中的实践
前段时间有写过一个TypeScript在node项目中的实践。 在里边有解释了为什么要使用TS,以及在Node中的一个项目结构是怎样的。 但是那仅仅是一个纯接口项目,碰巧赶上近期的另一个项目重构也由我来主持,经过上次的实践以后,尝到了TS所带来的甜头,毫不犹豫的选择用TS+React来重构这个项目。
2291 0
|
4月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
308 0
|
4月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
101 0