TS、Vue、React、SSR、Node、Deno、Bun:回顾2022,展望2023
本文约 4700 字,预计阅读需要 14 分钟。
大家好,我是 CUGGZ。祝大家新年快乐呀~
本文将回顾并总结 2022 年 JavaScript 生态系统中最重要的发展以及 2023 年的发展趋势!
大纲:
TypeScript React Vue 服务端渲染(SSR) Node.js、Deno、Bun
1. TypeScript
回顾 2022 年,事实证明,即使在这样一个成熟稳定的生态系统中,也会发生一些有趣的事情。
类型会出现在 JavaScript 中吗?
3 月初,TypeScript 背后的公司微软准备了一份 JavaScript 标准提案。它的内容侧重于使用类似于 TypeScript 中已知类型的类型来丰富语法。为了保持向后兼容性并且不改变语言的基础,微软建议解释器将类型视为与注释相同(完全忽略它们)。这会将类型排除在应用逻辑之外,但会通过将语言服务器和类型检查器连接到 IDE 来提高代码的可读性和开发效率。
上面显示的行为可能与当前的 JSDoc 行为相似。但是,新提案比该解决方案具有显着优势。首先,Microsoft 建议的格式比 JSDoc 更简洁和可读。其次,JSDoc 的功能受到严重限制(至少很难使用它定义泛型)。
// —-----------------------------------
// JSDoc
// —-----------------------------------
/**
* @param {string} p1 - 字符串参数.
* @param {string=} p2 - 可选参数(闭包语法)
* @param {string} [p3] - 可选参数(JSDoc语法)
* @param {string} [p4="test"] - 带默认值的可选参数
* @return {string} - 结果
*/
function stringsStringStrings(p1, p2, p3, p4) {
/* ... */
}
// —-----------------------------------
// 带有类型注释的JavaScript
// —-----------------------------------
function stringsStringStrings(p1: string, p2?: string, p3?: string, p4 = "test"): string {
/* ... */
}
那JavaScript 中的类型是否意味着 TypeScript 的终结呢?在早期阶段,JavaScript 中的类型可能比 JavaScript 中已知的类型更原始。TypeScript 还提供基于代码转换的功能,例如枚举。在这方面,JavaScript 不太可能很快赶上 TypeScript。
从 2022 年 3 月开始,该提案的工作进展不大,一直停留在 Stage 1(整个过程分为 4 个阶段)。但是,天正在由微软处理,所以该提案应该会在 2023 年回归。
TypeScript 的新功能
在过去的一年里,TypeScript 发布了 3 个新的版本:4.7、4.8、4.9。TypeScript 团队的开发人员花费了大量时间来优化性能并更好地处理更多边缘情况。在这些新特性中,有一个新功能令人眼前一亮:satisfies 操作符。
下面来通过官方文档中的示例来解释新运算符的工作原理,假设需要在以下代码中添加类型:
// 每个属性可以是字符串或 RGB 元组
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
// 在“red”上使用数组方法
const redComponent = palette.red.at(0);
// 在“green”上使用字符串方法
const greenNormalized = palette.green.toUpperCase();
我们可能想到的就是定义颜色的类型并使用 Record 类型。在这种情况下,就被迫执行危险的转换操作:
type Color = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
type Palette = Record<Color, string | RGB>
const palette: Palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
// 在“red”上使用数组方法
const redComponent = palette.red.at(0);
// 在“green”上使用字符串方法
const greenNormalized = palette.green.toUpperCase();
此问题的解决方法就是使用新的 satisfies 运算符,它将在赋值时验证类型,但不会影响 TypeScript 推断的类型。这听起来很复杂,下面来通过一个简单的例子来说明:
type Color = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
type Palette = Record<Color, string | RGB>
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Palette;
// 这两种方法仍然可用
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();
// —-----------------------------------
// 通过 satisfies 捕获错误的示例
// —-----------------------------------
const spelloPalette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255] // 捕获到错别字
} satisfies Palette;
// 捕获到缺少属性
const missingColorPalette = {
red: [255, 0, 0],
bleu: [0, 0, 255]
} satisfies Palette;
const wrongColorTypePalette = {
red: [255, 0], // 捕获到缺少元素
green: "#00ff00",
bleu: [0, 0, 255]
} satisfies Palette;
TypeScript 的未来
2023 年 3 月初将发布 TypeScript 5.0。值得注意的是,TypeScript 并不遵循语义版本控制。这意味着新的主要版本应该带来一系列激动人心的变化,但不一定是重大变化。
查看 TypeScript 存储库,可以瞥见一些即将推出的功能。首先,TypeScript 被重写为模块(之前是基于命名空间),减少了 25% 的包大小,提高了 10% 的编译速度。TypeScript 还致力于为任何文件类型提供原生 import 支持。种种迹象都表明,2023 年对于 TypeScript 来说,肯定会是比 2022 年更有趣的一年。
2. React
在 React 17 发行两年后,Meta 终于发布了下一个主要版本:18.0。与以前的版本不同,从API 的角度来看,上一版本没有引入显著的新颖之处,而这一版本充满了令人兴奋的功能。
React 18 和并发模式
这里说的并发是关于对渲染进行排队、排定优先级以及添加中止正在进行的渲染的能力的。
可以从并发模式中获益的典型示例就是与搜索字段进行交互。当用户按下键盘上的一个键时,即使是更新文本字段的最轻微延迟也会让用户感觉到有问题。当涉及到搜索结果时,稍微延迟甚至是比较自然的。在这种情况下,显然有优先级和非优先级渲染器需要处理。此外,如果用户能够比 React 渲染组件输入的更快,那么渲染中间搜索状态是不可取的。在这种情况下,渲染取消就可以发挥作用,只要出现更新版本的组件,就会取消之前的渲染。
那使用防抖函数是不是也可以达到类似的效果呢?React 18 之前的技术与并发模式之间的区别在于,之前,渲染是同步完成的,用户无法在渲染期间与页面进行交互。在并发模式下,当队列中出现优先级较高的渲染时,优先级较低的渲染将被中断。通过中止优先级较低的渲染,页面应该会响应更快。
使用此功能,可以以低优先级在屏幕外呈现组件(以便可以缓存已访问的页面,或者提前渲染用户最有可能访问的页面)。由于使用了优先级,这些操作不会影响界面的响应性,因为它们只会以较低的优先级执行。
下面来看一个实际的例子,低优先级更新必须包装在 startTransition
方法中,可以通过 useTransition()
Hooks 访问它:
export default function App() {
const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);
function handleClick() {
startTransition(() => {
setCount((c) => c + 1);
});
}
const advancedCounter = useMemo(
() => <AdvancedCounter count={count} />,
[count]
);
return (
<>
{/*点击按钮后,直到低优先级渲染完成,div才会改变不透明度*/}
<div style={{ opacity: isPending ? 0.8 : 1 }}>
{/*只有在所有高优先级渲染完成后,下面的组件才会更新。*/}
{/*若用户连续点击增加按钮,则只有最后一次渲染对用户可见。*/}
{advancedCounter}
<button onClick={handleClick}>+1</button>
</div>
</>
);
}
useDeferredValue
Hook 也已添加到 API 中。如果在高优先级渲染中调用此 Hook,它将返回前一个渲染的值。这样引用就不会改变,这将使渲染更快,因为不会重新渲染虚拟 DOM 树的一部分。完成高优先级渲染后,将安排低优先级渲染,其中 Hook 将返回新值并渲染更新的组件。
export default function App() {
const [count, setCount] = useState(0);
function handleClick() {
setCount((c) => c + 1);
}
const deferredCount = useDeferredValue(count);
const advancedCounter = useMemo(
() => <AdvancedCounter count={deferredCount} />,
[deferredCount]
);
return (
<>
<div>
{/*只有在所有高优先级渲染完成后,下面的组件才会更新。*/}
{/*若用户连续点击增加按钮,则只有最后一次渲染对用户可见。*/}
{advancedCounter}
<button onClick={handleClick}>Increase counter</button>
</div>
</>
);
}
React 的未来
我们不太可能在 2023 年看到 React 新的主要版本。可以预见,有些功能不会等到 React 19,而是会进入 React 18 的下一版本。
React 团队目前正在开发的最大和最受期待的功能可能是 React 服务端组件。这并不是目前正在开发的唯一功能。React 团队正在开发一个 <Offscreen />
组件,它将允许在不将元素附加到 DOM 的情况下渲染屏幕。另一个正在开发的功能是一个编译器,它可以在需要的地方自动添加 useMemo
和 useCallback
。
3. Vue
2022 年,Vue 只发布了一个主要版本。令人惊讶的是,它不是另一个 Vue 3,而是 Vue 2 的最终版本。此外,我们终于可以通过 Nuxt 3 获得服务端渲染。
Vue 2.7
Vue 2.7 是 Vue 2 的最新版本,将支持到 2023 年底。简而言之,这个版本将 Vue 3 中最重要的功能添加到 Vue 2 中。通过这种方式,增量迁移应该会变得更加简单。
Vue 2.7 中最重要的功能是 Composition API,许多人称之为 Vue 的 React Hooks。该 API 由 3 个组件组成:Reactivity API(ref 和 reactive)、Lifecycle Hooks(onMounted/onUnmounted)和依赖注入(provide/inject)。重要的是,所有新功能的行为和类型都与 Vue 3 100% 兼容。
// 旧的 Options API
<script>
export default {
data() {
return {
name: 'John',
};
},
mounted() {
console.log(`Hello ${this.name}`);
},
};
</script>
<template>
<p>Hello {{ name }}!</p>
</template>
// 新的 Composition API
<script setup>
const name = ref(0);
onMounted(() => {
console.log(`Hello ${name.value}`);
});
</script>
<template>
<p>Hello {{ name }}!</p>
</template>
Vue 3.3
Vue 3.3 原本应该在 2022 年底发布,但它的发布已经推迟到 2023 年。不过,可以看看 Vue 2023 年会发布什么。
第一个期待已久的功能是 <Suspense />
。它将启用临时组件的渲染,直到加载完树中的所有异步组件。
<Suspense>
<!-- 具有嵌套异步依赖项的组件 -->
<Dashboard />
<!-- 通过 #fallback 插槽加载状态 -->
<template #fallback>
Loading...
</template>
</Suspense>
第二个备受期待的功能是 Reactivity Transform,这是对 Composition API 的一系列改进。Reactive API 中的所有方法(包括 ref 和 computed)都将获得以 $
开头的版本。这些将是允许更轻松地访问数据的宏,但需要额外的编译步骤。
// Vue 3.3 之前
<script setup>
import { ref } from 'vue'
let count = ref(0)
console.log(count.value)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
// Vue 3.3
<script setup>
let count = $ref(0)
console.log(count)
function increment() {
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
在 Vue Amsterdam 上,尤雨溪分享了团队的长期计划。目前,这些计划还处于起步阶段,仍有可能发生变化。Vue 3.3 完成后,团队正在考虑采用与 SolidJS 类似的新编译策略。SolidJS 是一个类似于 React 的框架,但由于放弃了虚拟 DOM,它拥有更好的性能。可选地从 Vue 中删除虚拟 DOM 可以显著缩小包大小,并节省存储虚拟组件结构所需的内存。
4. 服务端渲染(SSR)
过去的一年,几乎每个主要框架都有关于服务端渲染的较大更新,或者有一个具有大量新功能的全新库(Vue – Nuxt 3、Svelte – SvelteKit、React – React Server Components、Remix、Next.js 13)。市场上也有很多创新的解决方案,与现有的框架无关。这里不得不提的就是 Qwik,它试图彻底改变水合方法,还有 Astro,它推广了创新的 Dynamic Islands 架构。
React 服务端组件和 Next.js 13
自 Dan Abramov 介绍 React 服务端组件以来已经两年多了。简而言之,React 将允许在服务端渲染单个组件并将 HTML 代码发送到客户端。这种方法非常类似于PHP,因为可以直接从 React 组件进行数据库查询。
// Note.js - Server Component
import db from 'db';
async function Note(props) {
const note = await db.posts.get(props.id);
return (
<div>
<h2>{note.title}</h2>
<section>{note.body}</section>
</div>
);
}
然而,React 服务端组件也有其局限性。这种类型的组件不能存储状态(禁止使用 useState),不能被客户端渲染的组件导入,并且需要像 Next.js 或 Gatsby 这样的元框架才能工作。
尽管 React 服务端组件仍处于 alpha 阶段,但使用它们的框架(Next.js、Gatsby)已经发布。特别是,Next.js 13 引起了社区的共鸣,因为它的新架构完全基于 React Server Components。这种架构的一个有趣的副作用是,只要不显式定义客户端组件,整个应用将只在服务端渲染。
Qwik
Qwik 通过破坏水合过程的合法性,颠覆了所有现代服务端渲染工具的范式。水合是一个客户端过程,在此期间服务端渲染的应用加载 JavaScript 代码,然后重复所有初始化逻辑。只有在水合过程完成后,应用才会响应。由于这种策略,用户可以立即看到目标内容,过一会儿就可以与之交互。
Qwik 提供的替代方案是重新执行。使用下面这种方式保存服务端的应用状态,以使客户端可以立即恢复代码执行。听起来很复杂?幸运的是,复杂的逻辑被隐藏在了可访问的 API 中:
那为什么未来 Qwik 是值得关注的呢?其有一个真正的全明星团队负责其开发——Miško Hevery(anguar.js 的创建者)、Manu Almeida(Gin 和 Stencil 的创建者)和 Adam Bradley(Ionic 和 Stencil 的创建者)。如果有人想彻底改变前端,那就是一群这样的老手。
Astro
Astro 是一个推广所谓 island 架构的框架。它假设在我们创建的页面上,在静态内容的海洋(例如导航菜单、文章)中,有交互式组件的小岛(例如表单)。这种方法允许我们在服务端渲染整个应用,并且只将一小部分组件岛代码发送到客户端。
Astro 配备了一个非常先进的机制来定义何时应该下载动态组件的代码以及何时应该将它们混合。在可用的选项中,只有当组件在屏幕上可见时才需要下载,或者只有当满足适当的媒体查询时才需要水合。
Astro 在竞争中脱颖而出还因为它独立于框架。这是很长一段时间以来第一个真正不支持任何解决方案的库,最重要的是其可以与它们相互混合。
—
import HomeLayout from '../layouts/HomeLayout.astro'
import MyReactComponent from '../components/MyReactComponent.jsx';
import MySvelteComponent from '../components/MySvelteComponent.svelte';
const data = await fetch('API_URL').then(r => r.json());
---
<HomeLayout>
<MyReactComponent client:load name={data.name}>
<MySvelteComponent avatar={data.avatar}/>
</MyReactComponent>
</HomeLayout>
5. Node.js、Deno、Bun
一年前,JavaScript 运行时的情况似乎比较稳定。Node 凭借强大的实力占据主导地位,而 Deno 正在精心扩大其市场。然而,在 2022 年年中,当 Bun 以 700 万美元的资金进入游戏时,发生了一场真正的地震。
Bun
Bun 是 Node.js 和 Deno 的替代品。Deno 将其营销重点放在解决 Node.js 问题上,而 Bun 则强调性能。根据 Bun 作者发布的基准测试,其优势非常显着。
Bun 的表现主要由两个因素导致。首先,与其竞争对手不同,它不是用 C++ 编写的,而是用 Zig 语言编写的,Zig 是 C++ 和 Rust 的一个相当小众的替代品。
影响 Bun 性能的第二个因素是使用了 Apple 的 JavaScriptCore 引擎。事实证明,JavaScriptCore 比 V8 更快。差异相对较小,但绝对存在。
Bun 还对 Node.js 进行了很多改进。与Deno一样,TypeScript 是一种原生支持的语言。Bun 也是 npm 的替代品,其速度快 100 倍。Bun 提供了用于创建宏的 API(在编译过程中生成的代码,并访问AST树和键入元数据)。为了打包应用,Bun 使用它自己的打包工具/转换器,这是非常快的。
Deno
在 Bun 的 alpha 版本发布几周后,Deno 背后的团队宣布了 npm 兼容性和具有许多性能改进的新 http 服务器。
支持 npm 是一个特别有争议的决定,因为 Deno 作者声称这是其生态系统许多问题的根源。需要注意的是,npm 与 Deno 结合使用的工作方式与Node.js结合使用的情况。首先,Deno 不需要运行 npm install。其次,Deno 不会创建 node_modules 目录,下载的包会全局存储。第三,npm 导入将标有特殊的 npm: prefix
:
import { chalk } from "npm:chalk@5";
Node.js
Node.js 的发展速度不如 Bun 或 Deno。但我们更看重的是它的稳定性和可预测性,而不是改变世界的功能。尽管如此,Node.js 2022 年还提供了一些有趣的新功能。
一个有趣的新特性就是内置的 Test Runner,这意味着将不再需要 Jest 或 Vitest 等库来测试 Node 应用:
test('synchronous passing test', (t) => {
// 测试通过,因为它没有抛出异常
assert.strictEqual(1, 1);
});
test('asynchronous passing test', async (t) => {
// 测试通过,因为异步返回了 Promise
// 函数没有被拒绝
assert.strictEqual(1, 1);
});
参考文章:https://vived.io/javascript-wrapped-2022-frontend-weekly-vol-119/
往期推荐:
聊聊前端字符编码:ASCII、Unicode、Base64、UTF-8、UTF-16、UTF-32
谈谈JS二进制:File、Blob、FileReader、ArrayBuffer、Base64