原文:https://blog.logrocket.com/upgrading-react-18-typescript/
为了支持React 18,React类型定义进行了升级,其中包含了一些break change。本文将讲述在TypeScript中如何升级到React 18
React 18和Definitely Typed
在alpha和beta测试经历了相当长的一段时间后,React 18 于2022年3月29日正式发布。在第一个alpha版本发布的时候,TypeScript就提供了支持
这是通过Definitely Typed(一个社区维护的各种TypeScript类型定义的库)的类型定义实现的)来使用。感谢Sebastian Silbermann的贡献,他在React18的类型定义工作中投入了大量的精力
目前React 18已经发布并且React 18 的类型定义在 Sebastian 的pr合并后也进行了更新。许多项目会面临一些break change。本文章将介绍会产生哪些break change及如何解决
Definitely Typed和语义版本控制
开发者习惯于在使用的软件中进行语义版本控制。通常来说在主版本的修改是表明有重大更改的。这正是React从v17升级到v18所做的事
Definitely Typed
是不支持语义版本控制的
这不是故意的。因为Definitely Typed
特意将类型定义发布到npm的@types
作用域下。例如,React的类型定义被发布到@types/react
需要注意的是,npm 建立在语义版本控制之上。为了使类型定义的使用更容易,类型定义包的版本将等同于它支持的 npm 包的版本。对于 react
的18.0.0
,对应的类型定义是@types/react
的18.0.0
如果@types/react
类型定义发生breaking change,则会发布新版本而不是增加主要或次要版本号
修改将仅应用于修订号。这样做是为了通过npm维护当前更简单的类型消费模型
React 18: 类型上的breaking change
综上所述,对于那些被广泛使用的类型定义包,都会尽量减少产生breaking change
顺便说一句,Definitely Typed
自动化工具将类型定义分为三类: “深受大家喜爱(Well-liked by everyone)”、“流行(Popular)“和"关键(Critical)”。感谢Andrew Branch的分享。被广泛使用的React被认为是"关键的”
当Sebastian提交了一个pr来升级TypeScript的React类型定义时,就有机会来做一些重大的修改。这些修改可能并不都与React 18有直接关系但会修复React类型定义中长期存在的一些问题
Sebastian pr非常好,我建议你去看一下。以下是重大更改的摘要
- 移除隐式children
- 移除
ReactFragment
中的{}
(related to 1.) this.context
变成unkown
- Using noImplicitAny now enforces a type is supplied with useCallback
noImplicitAny
应用到useCallback
- 删除不推荐使用的类型与React官方保持一致
在上述修改中,移除隐式children是最具破坏性的。Sebastian专门写了一篇博客来解释其原因。他还写了一个codemod来有利于进行这个代码迁移
下面让我们开始将代码库的react升级到18吧!
升级
我将通过升级我阿姨的网站进行演示。这是一个简单的网站,升级的pr
首先在package.json
中升级React
- "react": "^17.0.0",
- "react-dom": "^17.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
然后升级类型定义
代码语言:javascript复制- "@types/react": "^17.0.0",
- "@types/react-dom": "^17.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
升级的时候需要检查lock依赖(yarn.lock / package-lock.json等),确保只有18版本的@types/react
和react
。
现在依赖安装已完成,会看到以下报错
代码语言:javascript复制Property ‘children’ does not exist on type ‘LoadingProps’.ts(2339)
代码如下
代码语言:javascript复制interface LoadingProps {
// 你会注意到这里没有 `children` 属性 - 这就是出现错误的原因
noHeader?: boolean;
}
// if props.noHeader is true then this component returns just the icon and a message
// if props.noHeader is true then this component returns the same but wrapped in an h1
const Loading: React.FunctionComponent<LoadingProps> = (props) =>
props.noHeader ? (
<>
<FontAwesomeIcon icon={faSnowflake} spin /> Loading {props.children} ...
</>
) : (
<h1 className="loader">
<FontAwesomeIcon icon={faSnowflake} spin /> Loading {props.children} ...
</h1>
);
在这里看到的是去除隐式children
的改动。在我们进行升级之前,所有React.Component
和React.FunctionComponent
都有一个children
属性,它允许React
用户在不声明children
的情况下直接使用
升级18后就不一样了。如果有一个带有子组件,则必须显式声明这个组件的类型
在这个例子中,通过直接添加children
属性的声明可以修复这个问题
interface LoadingProps {
noHeader?: boolean;
children: string;
}
但是,当可以让其他方式帮我们写代码的话,为什么还要写代码呢?
我们可以使用Sebastian开发的codemod来替代手动修改代码。使用它直接通过以下的命令就可以:
代码语言:javascript复制npx types-react-codemod preset-18 ./src
执行后,会看到如下提示:
选择a
并让codemod运行。对于这个项目,有37个文件更新了。所有文件都需要进行相同的修改。在每种情况下,组件的props
都被React.PropsWithChildren
包起来。例如Loading
组件如下
-const Loading: React.FunctionComponent<LoadingProps> = (props) =>
const Loading: React.FunctionComponent<React.PropsWithChildren<LoadingProps>> = (props) =>
PropsWithChildren
仅仅是将children
属性添加,如下
type PropsWithChildren<P> = P & { children?: ReactNode | undefined };
这就解决了上面遇到的编译问题,没有类型问题报错了
总结
通过本文我们已经学习到React 18是如何出现类型的破坏性更改,并知道可以使用codemod快速进行升级