前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >React + TypeScript + Hook 带你手把手打造类型安全的应用。

React + TypeScript + Hook 带你手把手打造类型安全的应用。

作者头像
ssh_晨曦时梦见兮
发布于 2020-04-11 13:04:12
发布于 2020-04-11 13:04:12
2K00
代码可运行
举报
运行总次数:0
代码可运行

前言

TypeScript可以说是今年的一大流行点,虽然Angular早就开始把TypeScript作为内置支持了,但是真正在中文社区火起来据我观察也就是没多久的事情,尤其是在Vue3官方宣布采用TypeScript开发以后达到了一个顶点。

社区里有很多TypeScript比较基础的分享,但是关于React实战的还是相对少一些,这篇文章就带大家用React从头开始搭建一个TypeScript的todolist,我们的目标是实现类型安全,杜绝开发时可能出现的任何错误!

本文所使用的所有代码全部整理在了 ts-react-todo 这个仓库里。

分别实现了宽松版和严格版的axios和todolist,其中严格版本的实现会在文件夹加上.strict的后缀,请注意区分。

本文默认你对于TypeScript的基础应用没有问题,对于泛型的使用也大概理解,如果对于TS的基础还没有熟悉的话,可以看我在上面github仓库的Readme的文末附上的几篇推荐。

实战

创建应用

首先使用的脚手架是create-react-app,根据 www.html.cn/create-reac… 的流程可以很轻松的创建一个开箱即用的typescript-react-app。

创建后的结构大概是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
my-app/
  README.md
  node_modules/
  package.json
  public/
    index.html
    favicon.ico
  src/
    App.css
    App.ts
    App.test.ts
    index.css
    index.ts
    logo.svg
复制代码

在src/App.ts中开始编写我们的基础代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useState, useEffect } from "react";
import classNames from "classnames";
import TodoForm from "./TodoForm";
import axios from "../api/axios";
import "../styles/App.css";

type Todo = {
  id: number;
  // 名字
  name: string;
  // 是否完成
  done: boolean;
};

type Todos = Todo[];

const App: React.FC = () => {
  const [todos, setTodos] = useState<Todos>([]);
  
  return (
    <div className="App">
      <header className="App-header">
        <ul>
          <TodoForm />
          {todos.map((todo, index) => {
            return (
              <li
                onClick={() => onToggleTodo(todo)}
                key={index}
                className={classNames({
                  done: todo.done,
                })}
              >
                {todo.name}
              </li>
            );
          })}
        </ul>
      </header>
    </div>
  );
};

export default App;
复制代码

useState

代码很简单,利用type关键字来定义Todo这个类型,然后顺便生成Todos这个类型,用来给React的useState作为泛型约束使用,这样在上下文中,todos这个变量就会被约束为Todos这个类型,setTodos也只能去传入Todos类型的变量。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  const [todos, setTodos] = useState<Todos>([]);
复制代码

当然,useState也是具有泛型推导的能力的,但是这要求你传入的初始值已经是你想要的类型了,而不是空数组。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const [todos, setTodos] = useState({
    id: 1,
    name: 'ssh',
    done: false
  });
复制代码

模拟axios(简单版)

有了基本的骨架以后,就要想办法去拿到数据了,这里我选择自己模拟编写一个axios去返回想要的数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  const refreshTodos = () => {
    // 这边必须手动声明axios的返回类型。
    axios<Todos>("/api/todos").then(setTodos);
  };

  useEffect(() => {
    refreshTodos();
  }, []);
复制代码

注意这里的axios也要在使用时手动传入泛型,因为我们现在还不能根据"/api/todos"这个字符串来推导出返回值的类型,接下来看一下axios的实现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let todos = [
  {
    id: 1,
    name: '待办1',
    done: false
  },
  {
    id: 2,
    name: '待办2',
    done: false
  },
  {
    id: 3,
    name: '待办3',
    done: false
  }
]

// 使用联合类型来约束url
type Url = '/api/todos' | '/api/toggle' | '/api/add'

const axios = <T>(url: Url, payload?: any): Promise<T> | never => {
  let data
  switch (url) {
    case '/api/todos': {
      data = todos.slice()
      break
    }
  }
 default: {
    throw new Error('Unknown api')
 }

  return Promise.resolve(data as any)
}

export default axios
复制代码

重点看一下axios的类型描述

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const axios = <T>(url: Url, payload?: any): Promise<T> | never
复制代码

泛型T被原封不动的交给了返回值的Promise, 所以外部axios调用时传入的Todos泛型就推断出返回值是了Promise,Ts就可以推断出这个promise去resolve的值的类型是Todos。

在函数的实现中我们把data给resolve出去。

接下来回到src/App.ts 继续补充点击todo,更改完成状态时候的事件,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const App: React.FC = () => {
  const [todos, setTodos] = useState<Todos>([]);
  const refreshTodos = () => {
    // FIXME 这边必须手动声明axios的返回类型。
    axios<Todos>("/api/todos").then(setTodos);
  };

  useEffect(() => {
    refreshTodos();
  }, []);

  const onToggleTodo = async (todo: Todo) => {
    await axios("/api/toggle", todo.id);
    refreshTodos();
  };

  return (
    <div className="App">
      <header className="App-header">
        <ul>
          <TodoForm refreshTodos={refreshTodos} />
          {todos.map((todo, index) => {
            return (
              <li
                onClick={() => onToggleTodo(todo)}
                key={index}
                className={classNames({
                  done: todo.done,
                })}
              >
                {todo.name}
              </li>
            );
          })}
        </ul>
      </header>
    </div>
  );
};
复制代码

再来看一下src/TodoForm组件的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from "react";
import axios from "../api/axios";

interface Props {
  refreshTodos: () => void;
}

const TodoForm: React.FC<Props> = ({ refreshTodos }) => {
  const [name, setName] = React.useState("");

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setName(e.target.value);
  };

  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const newTodo = {
      id: Math.random(),
      name,
      done: false,
    };

    if (name.trim()) {
      // FIXME 这边第二个参数没有做类型约束
      axios("/api/add", newTodo);
      refreshTodos();
      setName("");
    }
  };

  return (
    <form className="todo-form" onSubmit={onSubmit}>
      <input
        className="todo-input"
        value={name}
        onChange={onChange}
        placeholder="请输入待办事项"
      />
      <button type="submit">新增</button>
    </form>
  );
};

export default TodoForm;
复制代码

在axios里加入/api/toggle和/api/add的处理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  switch (url) {
    case '/api/todos': {
      data = todos.slice()
      break
    }
    case '/api/toggle': {
      const todo = todos.find(({ id }) => id === payload)
      if (todo) {
        todo.done = !todo.done
      }
      break
    }
    case '/api/add': {
      todos.push(payload)
      break
    }
    default: {
      throw new Error('Unknown api')
    }
  }
复制代码

其实写到这里,一个简单的todolist已经实现了,功能是完全可用的,但是你说它类型安全吗,其实一点也不安全。

再回头看一下axios的类型签名:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const axios = <T>(url: Url, payload?: any): Promise<T> | never
复制代码

payload这个参数被加上了?可选符,这是因为有的接口需要传参而有的接口不需要,这就会带来一些问题。

这里编写axios只约束了传入的url的限制,但是并没有约束入参的类型,返回值的类型,其实基本也就是anyscript了,举例来说,在src/TodoForm里的提交事件中,我们在FIXME的下面一行稍微改动,把axios的第二个参数去掉,如果以现实情况来说的话,一个add接口不传值,基本上报错没跑了,而且这个错误只有运行时才能发现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const newTodo = {
      id: Math.random(),
      name,
      done: false,
    };

    if (name.trim()) {
      // ERROR!! 这边的第二个参数被去掉了,但是TS不会提示。
      axios("/api/add");
      refreshTodos();
      setName("");
    }
  };

复制代码

在src/App.ts的onToggleTodo事件里也有着同样的问题

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 const onToggleTodo = async (todo: Todo) => {
    // ERROR!! 这边的第二个参数被去掉了,但是TS不会提示。
    await axios("/api/toggle");
    refreshTodos();
  };
复制代码

另外在获取数据时候axios,必须要手动用泛型来定义好返回类型,这个也很冗余。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
axios<Todos>("/api/todos").then(setTodos);
复制代码

接下来我们用一个严格类型版本的axios函数来解决这个问题。

模拟axios(严格版)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// axios.strict.ts
let todos = [
  {
    id: 1,
    name: '待办1',
    done: false
  },
  {
    id: 2,
    name: '待办2',
    done: false
  },
  {
    id: 3,
    name: '待办3',
    done: false
  }
]


export enum Urls {
  TODOS = '/api/todos',
  TOGGLE = '/api/toggle',
  ADD = '/api/add',
}

type Todo = typeof todos[0]
type Todos = typeof todos

复制代码

首先我们用enum枚举定义好我们所有的接口url,方便后续复用, 然后我们用ts的typeof操作符从todos数据倒推出类型。

接下来用泛型条件类型来定义一个工具类型,根据泛型传入的值来返回一个自定义的key

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Key<U> =
  U extends Urls.TOGGLE ? 'toggle': 
  U extends Urls.ADD ? 'add': 
  U extends Urls.TODOS ? 'todos': 
  'other'
复制代码

这个Key的作用就是,假设我们传入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type K = Key<Urls.TODOS>
复制代码

会返回todos这个字符串类型,它有什么用呢,接着看就知道了。

现在需要把axios的函数类型声明的更加严格,我们需要把入参payload的类型和返回值的类型都通过传入的url推断出来,这里要利用泛型推导:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function axios <U extends Urls>(url: U, payload?: Payload<U>): Promise<Result<U>> | never
复制代码

不要被这长串吓到,先一步步来分解它,

  1. <U extends Urls>首先泛型U用extends关键字做了类型约束,它必须是Urls枚举中的一个,
  2. (url: U, payload?: Payload<U>)参数中,url参数和泛型U建立了关联,这样我们在调用axios函数时,就会动态的根据传入的url来确定上下文中U的类型,接下来用Payload<U>把U传入Payload工具类型中。
  3. 最后返回值用Promise<Result<U>>,还是一样的原理,把U交给Result工具类型进行推导。

接下来重要的就是看Payload和Result的实现了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Payload<U> = {
  toggle: number
  add: Todo,
  todos: any,
  other: any
}[Key<U>]

复制代码

刚刚定义的Key<U>工具类型就派上用场了,假设我们调用axios(Urls.TOGGLE),那么U被推断Urls.TOGGLE,传给Payload的就是Payload<Urls.TOGGLE>,那么Key<U>返回的结果就是Key<Urls.TOGGLE>,即为toggle

那么此时推断的结果是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Payload<Urls.TOGGLE> = {
  toggle: number
  add: Todo,
  todos: any,
  other: any
}['toggle']
复制代码

此时todos命中的就是前面定义的类型集合中第一个toggle: number, 所以此时Payload<Urls.TOGGLE>就这样被推断成了number 类型。

Result也是类似的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Result<U> = {
  toggle: boolean
  add: boolean,
  todos: Todos
  other: any
}[Key<U>]
复制代码

这时候再回头来看函数类型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function axios <U extends Urls>(url: U, payload?: Payload<U>): Promise<Result<U>> | never 
复制代码

是不是就清楚很多了,传入不同的参数会推断出不同的payload入参,以及返回值类型。

此时在来到app.ts里,看新版refreshTodos函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  const refreshTodos = () => {
    axios(Urls.TODOS).then((todos) => {
      setTodos(todos)
    })
  }
复制代码

axios后面的泛型约束被去掉了,then里面的todos依然被成功的推断为Todos类型。

这时候就完美了吗?并没有,还有最后一点优化。

函数重载

写到这里,类型基本上是比较严格了,但是还有一个问题,就是在调用呢axios(Urls.TOGGLE)这个接口的时候,我们其实是一定要传递第二个参数的,但是因为axios(Urls.TODOS)是不需要传参的,所以我们只能在axios的函数签名把payload?设置为可选,这就导致了一个问题,就是ts不能明确的知道哪些接口需要传参,哪些接口不需要传参。

注意下图中的payload是带?的。

要解决这个问题,需要用到ts中的函数重载。

首先把需要传参的接口和不需要传参的接口列出来。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type UrlNoPayload =  Urls.TODOS
type UrlWithPayload = Exclude<Urls, UrlNoPayload>
复制代码

这里用到了TypeScript的内置类型Exclude,用来在传入的类型中排除某些类型,这里我们就有了两份类型,需要传参的Url集合无需传参的Url集合

接着开始写重载

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function axios <U extends UrlNoPayload>(url: U): Promise<Result<U>>
function axios <U extends UrlWithPayload>(url: U, payload: Payload<U>): Promise<Result<U>> | never
function axios <U extends Urls>(url: U, payload?: Payload<U>): Promise<Result<U>> | never {
  // 具体实现
}
复制代码

根据extends约束到的不同类型,来重写函数的入参形式,最后用一个最全的函数签名(一定是要能兼容之前所有的函数签名的,所以最后一个签名的payload需要写成可选)来进行函数的实现。

此时如果再空参数调用toggle,就会直接报错,因为只有在请求todos的情况下才可以不传参数。

后记

到此我们就实现了一个严格类型的React应用,写这篇文章的目的不是让大家都要在公司的项目里去把类型推断做到极致,毕竟一切的技术还是为业务服务的。

但是就算是写宽松版本的TypeScript,带来的收益也远远比裸写JavaScript要高很多,尤其是在别人需要复用你写的工具函数或者组件时。

而且TypeScript也可以在开发时就避免很多粗心导致的错误,详见: TypeScript 解决了什么痛点? - justjavac的回答 - 知乎 www.zhihu.com/question/30…

本文涉及到的所有代码都在 github.com/sl1673495/t… 中,有兴趣的同学可以拉下来自己看看。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年11月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
React + TypeScript + Hook 带你手把手打造类型安全的应用。
TypeScript 可以说是今年的一大流行点,虽然 Angular 早就开始把 TypeScript 作为内置支持了,但是真正在中文社区火起来据我观察也就是没多久的事情,尤其是在 Vue3 官方宣布采用 TypeScript 开发以后达到了一个顶点。
ssh_晨曦时梦见兮
2024/01/26
2050
React + TypeScript + Hook 带你手把手打造类型安全的应用。
各流派 React 状态管理对比和原理实现
在 React 诞生之初,Facebook 宣传这是一个用于前端开发的界面库。在大型应用中,如何处理好 React 组件通信和状态管理就显得非常重要。
尹光耀
2022/03/22
3.1K0
各流派 React 状态管理对比和原理实现
🔖TypeScript 备忘录:如何在 React 中完美运用?
一直以来,ssh 身边都有很多小伙伴对 TS 如何在 React 中运用有很多困惑,他们开始慢慢讨厌 TS,觉得各种莫名其妙的问题降低了开发的效率。
ssh_晨曦时梦见兮
2022/03/09
3K0
🔖TypeScript 备忘录:如何在 React 中完美运用?
[译]全栈 Todolist-client 篇(React Typescript)
写在最前面 如果没看前面的 node server篇 和 mongoDB database篇 ,可以先看看,这篇是结合上面两篇一起学习的文章 您可以按照顺序阅读 全栈 Todolist-server 篇 Node(server) React(client) MongoDB(database) Typescript Todolist-database 篇(Cloud MongoDB) Todolist-client 篇(React Typescript) 1、创建一个 react app(源码代码参考) 接着
西南_张家辉
2021/02/02
5950
React + TypeScript 实践
需要添加额外的配置:"allowSyntheticDefaultImports": true
公众号@魔术师卡颂
2021/05/08
6.7K0
TypeScript基础看腻了?进阶实现智能类型推导的简化版Vuex,手把手带你实现。
React + TypeScript + Hook 带你手把手打造类型安全的应用。
ssh_晨曦时梦见兮
2020/04/11
8640
TypeScript基础看腻了?进阶实现智能类型推导的简化版Vuex,手把手带你实现。
你不知道的 TypeScript 泛型(万字长文,建议收藏)
泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。
学前端
2020/07/24
4.7K0
你不知道的 TypeScript 泛型(万字长文,建议收藏)
React、TypeScript、NodeJS 和 MongoDB 搭建 Todo App
在本教程中,我们将在服务器和客户端使用 TypeScript、React、NodeJS、Express 和 MongoDB 从头开始构建一个 Todo 应用程序。
前端迷
2020/08/28
17.5K0
React、TypeScript、NodeJS 和 MongoDB 搭建 Todo App
使用React Hooks + 自定义Hook封装一步一步打造一个完善的小型应用。
Reack Hooks自从16.8发布以来,社区已经有相当多的讨论和应用了,不知道各位在公司里有没有用上这个酷炫的特性~
ssh_晨曦时梦见兮
2020/04/10
5.3K0
推荐十一个React Hook库
在React开发中,保持干净的代码风格,可读性,可维护性,更少的代码行以及可重用性至关重要。本篇文章将向您介绍应立即开始使用的十一个React Hook库。不用再拖延了,让我们开始吧。
用户3806669
2021/07/06
4.4K0
推荐十一个React Hook库
类型即正义:TypeScript 从入门到实践(三):类型别名和类
学习了注解函数,又了解了类型运算如联合类型和交叉类型,接下来我们来了解一些 TS 中独有的类型别名,它类似 JS 变量,是类型变量,接着我们还会学习 TS 中内容非常庞杂的内容之一:类,了解 TS 中类的独有特性,以及如何注解类,甚至用类去注解其他内容。
一只图雀
2020/04/17
2.9K0
类型即正义:TypeScript 从入门到实践(三):类型别名和类
React+TypeScript使用规范
一个采用 parameterName is Type的形式返回 boolean 值的函数,但 parameterName 必须是当前函数的参数名
用户4619307
2023/05/04
4.8K0
类型即正义:TypeScript 从入门到实践(一)
JavaScript 已经占领了世界上的每一个角落,能访问网页的地方,基本上就有 JavaScript 在运作,然而 JavaScript 因为其动态、弱类型、解释型语言的特性、出错的调用栈隐蔽,使得开发者不仅在调试错误上花费大把时间,在团队协作开发时理解队友编写代码也极其困难。TypeScript 的出现极大的解决了上面的问题,TypeScript -- 一个 JavaScript 的超集,它作为一门编译型语言,提供了对类型系统和最新 ES 语法的支持,使得我们可以在享受使用 ES 最新语法的编写代码的同时,还能在写代码的过程中就规避很多潜在的语法、语义错误;并且其提供的类型系统使得我们可以在团队协作编写代码时可以很容易的了解队友代码的含义:输入和输出,大大提高了团队协作编写大型业务应用的效率。在现代 JavaScript 世界中,已经有很多大型库在使用 TypeScript 重构,包括前端三大框架:React、Vue、Angular,还有知名的组件库 antd,material,在很多公司内部的大型业务应用也在用 TypeScript 开发甚至重写现有的应用,所以如果你想编写大型业务应用或库,或者想写出更利于团队协作的代码,那么 TypeScript 有十足的理由值得你学习!本文是 TypeScript 系列教程的第一篇,主要通过使用 antd 组件库实战演练一个 TypeScript 版本 React TodoList 应用来讲解 TypeScript 的语法,使得你能在学会语法的同时还能完成一个实际可运行的项目。
一只图雀
2020/04/15
2.7K0
类型即正义:TypeScript 从入门到实践(一)
Vue3 Typescript + Axios 全栈开发教程:手把手教你写「待办清单」APP
本文完整版:《Vue3 Typescript + Axios 全栈开发教程:手把手教你写「待办清单」APP》
蒋川@卡拉云
2022/05/30
1.7K0
Vue3 Typescript + Axios 全栈开发教程:手把手教你写「待办清单」APP
TypeScript基础看腻了?进阶实现智能类型推导的简化版Vuex,手把手带你实现。
React + TypeScript + Hook 带你手把手打造类型安全的应用。
ssh_晨曦时梦见兮
2024/01/26
2080
TypeScript基础看腻了?进阶实现智能类型推导的简化版Vuex,手把手带你实现。
【案例】使用React+redux实现一个Todomvc
❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…
且陶陶
2024/07/25
2070
【案例】使用React+redux实现一个Todomvc
关于 vue3 + typescript 项目中常用的知识点汇总
在实际项目开发中,常常会遇到这么一个场景,某一个路由是不需要渲染到侧边栏导航上的,此时我们可以给该路由添加一个hidden属性来实现。
前端达人
2021/07/19
1.6K0
关于 vue3 + typescript 项目中常用的知识点汇总
React-Redux 100行代码简易版探究原理。(面试热点,React Hook + TypeScript实现)
各位使用react技术栈的小伙伴都不可避免的接触过redux + react-redux的这套组合,众所周知redux是一个非常精简的库,它和react是没有做任何结合的,甚至可以在vue项目中使用。
ssh_晨曦时梦见兮
2020/04/11
2.2K0
React-Redux 100行代码简易版探究原理。(面试热点,React Hook + TypeScript实现)
TypeScript 类型体操,无非是语法过度嵌套而已
写这篇文章的初衷,是因为又有一个粉丝朋友被 TypeScript 的类型体操逼疯了。他跟我吐槽了一通,然后问我是不是他使用 TS 的姿势不对,为什么感觉到的全是痛苦。
用户6901603
2023/12/28
3600
TypeScript 类型体操,无非是语法过度嵌套而已
你要的react+ts最佳实践指南
本文根据日常开发实践,参考优秀文章、文档,来说说 TypeScript 是如何较优雅的融入 React 项目的。
xiaofeng123aa
2022/10/03
3.2K0
推荐阅读
相关推荐
React + TypeScript + Hook 带你手把手打造类型安全的应用。
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验