You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// ❌ // Type 'Promise<void>' provides no match // for the signature '(): void | undefined'useEffect(async()=>{constuser=awaitgetUser()setUser(user)},[])
前言
一直以来,ssh 身边都有很多小伙伴对 TS 如何在 React 中运用有很多困惑,他们开始慢慢讨厌 TS,觉得各种莫名其妙的问题降低了开发的效率。
其实如果运用熟练的话,TS 只是在第一次开发的时候稍微多花一些时间去编写类型,后续维护、重构的时候就会发挥它神奇的作用了,还是非常推荐长期维护的项目使用它的。
其实我一直知道英文版有个不错的备忘录,本来想直接推荐给小伙伴,奈何很多人对英文比较头痛,而它中文翻译的版本点进去竟然是这个景象:
既然如此,就自己动手。结合英文原版里的一些示例进行一些扩展,总结成这篇备忘录。
前置基础
阅读本文的前提条件是:
也就是说,这篇文章侧重点在于 「React 和 TypeScript 的结合」,而不是基础知识,基础知识阅读文档即可学习。
也推荐看我 初中级前端的高级进阶指南 这篇文章中的 React 和 TypeScript 章节,这里不多赘述。
工具
选择你觉得比较中意的调试工具即可。
组件 Props
先看几种定义 Props 经常用到的类型:
基础类型
对象类型
函数类型
React 相关类型
函数式组件
最简单的:
包含 children 的:
利用
React.FC
内置类型的话,不光会包含你定义的AppProps
还会自动加上一个 children 类型,以及其他组件上会出现的类型:Hooks
@types/react
包在 16.8 以上的版本开始对 Hooks 的支持。useState
如果你的默认值已经可以说明类型,那么不用手动声明类型,交给 TS 自动推断即可:
如果初始值是 null 或 undefined,那就要通过泛型手动传入你期望的类型。
这样也可以保证在你直接访问
user
上的属性时,提示你它有可能是 null。通过
optional-chaining
语法(TS 3.7 以上支持),可以避免这个错误。useReducer
需要用 Discriminated Unions 来标注 Action 的类型。
「Discriminated Unions」一般是一个联合类型,其中每一个类型都需要通过类似
type
这种特定的字段来区分,当你传入特定的type
时,剩下的类型payload
就会自动匹配推断。这样:
type
匹配到decrement
的时候,TS 会自动推断出相应的payload
应该是string
类型。type
匹配到increment
的时候,则payload
应该是number
类型。这样在你
dispatch
的时候,输入对应的type
,就自动提示你剩余的参数类型啦。useEffect
这里主要需要注意的是,useEffect 传入的函数,它的返回值要么是一个方法(清理函数),要么就是undefined,其他情况都会报错。
比较常见的一个情况是,我们的 useEffect 需要执行一个 async 函数,比如:
虽然没有在 async 函数里显式的返回值,但是 async 函数默认会返回一个 Promise,这会导致 TS 的报错。
推荐这样改写:
或者用自执行函数?不推荐,可读性不好。
useRef
这个 Hook 在很多时候是没有初始值的,这样可以声明返回对象中
current
属性的类型:以一个按钮场景为例:
当
onButtonClick
事件触发时,可以肯定inputEl
也是有值的,因为组件是同级别渲染的,但是还是依然要做冗余的非空判断。有一种办法可以绕过去。
null!
这种语法是非空断言,跟在一个值后面表示你断定它是有值的,所以在你使用inputEl.current.focus()
的时候,TS 不会给出报错。但是这种语法比较危险,需要尽量减少使用。
在绝大部分情况下,
inputEl.current?.focus()
是个更安全的选择,除非这个值真的不可能为空。(比如在使用之前就赋值了)useImperativeHandle
推荐使用一个自定义的
innerRef
来代替原生的ref
,否则要用到forwardRef
会搞的类型很复杂。结合刚刚
useRef
的知识,使用是这样的:很完美,是不是?
可以在线调试 useImperativeHandle 的例子。
也可以查看这个useImperativeHandle 讨论 Issue,里面有很多有意思的想法,也有使用 React.forwardRef 的复杂例子。
自定义 Hook
如果你想仿照 useState 的形式,返回一个数组给用户使用,一定要记得在适当的时候使用
as const
,标记这个返回值是个常量,告诉 TS 数组里的值不会删除,改变顺序等等……否则,你的每一项都会被推断成是「所有类型可能性的联合类型」,这会影响用户使用。
对了,如果你在用 React Hook 写一个库,别忘了把类型也导出给用户使用。
React API
forwardRef
函数式组件默认不可以加 ref,它不像类组件那样有自己的实例。这个 API 一般是函数式组件用来接收父组件传来的 ref。
所以需要标注好实例类型,也就是父组件通过 ref 可以拿到什么样类型的值。
由于这个例子里直接把 ref 转发给 button 了,所以直接把类型标注为
HTMLButtonElement
即可。父组件这样调用,就可以拿到正确类型:
鸣谢
本文大量使用 react-typescript-cheatsheets 中的例子,加上自己的润色和例子补充,英文好的同学也可以读这个原文扩展学习。
The text was updated successfully, but these errors were encountered: