diff --git a/posts/what_changes_in_react_v19_01_zh.md b/posts/what_changes_in_react_v19_01_zh.md new file mode 100644 index 0000000..f8e6e45 --- /dev/null +++ b/posts/what_changes_in_react_v19_01_zh.md @@ -0,0 +1,140 @@ +--- +title: React 19 有哪些变化 - 1 +published_at: 2024-12-09T15:00:00.000Z +snippet: React V19 change log 解读. +--- + +React 19 就这么突然的发布了, 其实在去年的 React 开发者大会上就有预告, 这个版本会带来一些重大的变化, 目前我们公司的技术栈已经从 Vue 迁移到了 React, 了解新的 React 变化对我来说还是挺重要的, 接下来几天我阅读 React 19 的官方博客, 把变化记录下来, 也方便大家了解. + +## React 19 新特性总结 + +React 19 带来了很多新特性, 官方文档第一部分就是新特性的总结: + +## 新功能:Actions + +在 React 应用中,常见的需求是执行数据变更并更新状态,例如用户提交表单后修改名称。在 React 19 中,引入了 **Actions** 来自动管理这些操作,从而简化开发流程: + +- **异步过渡**:支持使用 `useTransition` 自动管理 `isPending` 状态。 +- **挂起状态**:Actions 提供了请求开始到结束的挂起状态。 +- **乐观更新**:通过新钩子 `useOptimistic` 支持即时反馈。 +- **错误处理**:Actions 提供内置的错误处理机制。 +- **表单支持**:表单的 `action` 和 `formAction` 属性可以直接传递函数,自动提交并重置表单。 + +使用示例: + +```javascript +function ChangeName({ name, setName }) { + const [error, submitAction, isPending] = useActionState( + async (previousState, formData) => { + const error = await updateName(formData.get("name")); + if (error) return error; + redirect("/path"); + return null; + }, + null + ); + + return ( +
+ + + {error &&

{error}

} +
+ ); +} +``` + +--- + +## 新钩子:`useActionState` + +`useActionState` 用于简化 Actions 的常见用例: + +- 接受一个异步函数并返回错误状态、提交函数和挂起状态。 +- 适用于处理乐观更新和表单提交。 + +--- + +## 新钩子:`useOptimistic` + +`useOptimistic` 支持在请求进行时显示乐观状态,操作完成后自动回退到真实状态。例如: + +```javascript +function ChangeName({ currentName, onUpdateName }) { + const [optimisticName, setOptimisticName] = useOptimistic(currentName); + + const submitAction = async (formData) => { + const newName = formData.get("name"); + setOptimisticName(newName); + const updatedName = await updateName(newName); + onUpdateName(updatedName); + }; + + return ( +
+ + +
+ ); +} +``` + +--- + +## 新 API:`use` + +React 19 提供了 `use` API,用于在渲染期间读取资源(例如 Promise 或 Context),支持挂起并自动切换状态: + +```javascript +function Comments({ commentsPromise }) { + const comments = use(commentsPromise); // 自动挂起直到数据加载完成 + return comments.map((comment) =>

{comment}

); +} +``` + +注意事项: + +- 不支持在渲染过程中创建的 Promise。 +- 支持条件调用,未来可能扩展更多资源类型。 + +--- + +## React DOM 静态 API + +新增两个静态站点生成 API: + +- `prerender` +- `prerenderToNodeStream` + +这些 API 支持等待数据加载并生成静态 HTML,适用于 Node.js 和 Web 流环境。 + +示例: + +```javascript +import { prerender } from "react-dom/static"; + +async function handler(request) { + const { prelude } = await prerender(, { + bootstrapScripts: ["/main.js"], + }); + return new Response(prelude, { headers: { "content-type": "text/html" } }); +} +``` + +--- + +## React Server Components + +**服务器组件**允许在客户端应用或 SSR 服务器之外的环境中提前渲染组件: + +- 稳定版本可用,但底层 API 尚未完全固定。 +- 支持全栈 React 架构。 + +--- + +## Server Actions + +**服务器操作**支持从客户端组件调用在服务器上执行的异步函数: + +- 通过 `"use server"` 指令创建服务器操作。 +- 服务器组件不需要特殊指令。 diff --git a/posts/what_changes_in_react_v19_02_zh.md b/posts/what_changes_in_react_v19_02_zh.md new file mode 100644 index 0000000..df9af1f --- /dev/null +++ b/posts/what_changes_in_react_v19_02_zh.md @@ -0,0 +1,141 @@ +--- +title: React 19 有哪些变化 - 2 +published_at: 2024-12-10T15:00:00.000Z +snippet: React V19 change log 解读. +--- + +这篇文章在第二部分介绍了一些对之前特性的升级. + +# React 19 新特性更新 + +## ref 作为 props 使用 + +在 React 19 中,函数组件可以直接将 `ref` 作为 `props` 访问,无需使用 `forwardRef`: + +```javascript +function MyInput({ placeholder, ref }) { + return ; +} + +// 使用方式 +; +``` + +### 改进点: + +- 新的函数组件无需再使用 `forwardRef`。 +- React 将发布一个代码迁移工具(codemod)来自动更新现有代码。 +- 在未来版本中,`forwardRef` 将会被废弃。 + +**注意**: +传递给类组件的 `ref` 不会作为 `props` 传递,因为它们引用的是组件实例。 + +--- + +## 改进的 Hydration 错误报告 + +React 19 改进了在 `react-dom` 中的 hydration 错误报告。 + +### 旧错误日志: + +在开发模式下,错误信息通常分散且缺乏具体上下文: + +```console +Warning: Text content did not match. Server: “Server” Client: “Client” +... +``` + +### 新错误日志: + +现在,React 会输出一条包含详细差异的错误信息: + +```console +Uncaught Error: Hydration failed because the server rendered HTML didn’t match the client... + + ++ Client +- Server +``` + +### 可能的原因: + +- 使用了客户端/服务器分支代码(如 `typeof window !== 'undefined'`)。 +- 变量输入(如 `Date.now()` 或 `Math.random()`)。 +- 本地化的日期格式化不一致。 +- 没有同步 HTML 数据快照。 +- 无效的 HTML 标签嵌套。 +- 浏览器扩展导致的 HTML 修改。 + +更多信息请参阅 [hydration-mismatch 文档](https://react.dev/link/hydration-mismatch)。 + +--- + +## 作为 Provider 使用 + +在 React 19 中,可以直接使用 `` 渲染 Context 提供者,而不再需要 ``: + +```javascript +const ThemeContext = createContext(""); + +function App({ children }) { + return {children}; +} +``` + +### 改进点: + +- `` 可以直接作为提供者。 +- 将发布迁移工具以转换现有 `` 实现。 +- 在未来版本中,`` 将被废弃。 + +--- + +## refs 的清理函数 + +React 19 支持在 `ref` 回调中返回清理函数,用于在元素从 DOM 中移除时重置 `ref`: + +```javascript + { + // 创建 ref + return () => { + // 清理 ref + }; + }} +/> +``` + +### 改进点: + +- 清理函数适用于 DOM refs、类组件 refs 以及 `useImperativeHandle`。 +- React 不再在卸载组件时调用带 `null` 的 ref 函数。 + +**注意**:由于清理函数的引入,TypeScript 现在会拒绝从 ref 回调中返回非清理函数的值。 + +修复示例: + +```diff +-
(instance = current)} /> ++
{ instance = current; }} /> +``` + +可使用 `no-implicit-ref-callback-return` 的迁移工具修复该模式。 + +--- + +## `useDeferredValue` 初始值 + +React 19 为 `useDeferredValue` 添加了 `initialValue` 选项: + +```javascript +function Search({ deferredValue }) { + const value = useDeferredValue(deferredValue, ""); // 初始值为 '' + return ; +} +``` + +### 特性: + +- 初次渲染时返回 `initialValue`,然后在后台重新渲染并使用 `deferredValue`。 + +更多信息请参阅 [useDeferredValue 文档](https://react.dev/link/useDeferredValue)。 diff --git a/posts/what_changes_in_react_v19_03_zh.md b/posts/what_changes_in_react_v19_03_zh.md new file mode 100644 index 0000000..64210f8 --- /dev/null +++ b/posts/what_changes_in_react_v19_03_zh.md @@ -0,0 +1,156 @@ +--- +title: React 19 有哪些变化 - 3 +published_at: 2024-12-11T15:00:00.000Z +snippet: React V19 change log 解读. +--- + +这篇文章在第二部分介绍了一些对之前特性的升级. + +## 文档元数据支持 + +在 HTML 中,像 ``、`<link>` 和 `<meta>` 等文档元数据标签通常需要放在 `<head>` 部分。React 过去需要通过副作用或库(如 react-helmet)手动插入这些标签,并在服务器渲染时需要谨慎处理。 + +在 React 19 中,这些元数据标签现在可以直接在组件中渲染,并自动提升到文档的 `<head>` 部分: + +```javascript +function BlogPost({ post }) { + return ( + <article> + <h1>{post.title}</h1> + <title>{post.title} + + + +

E=mc²...

+ + ); +} +``` + +### 改进点: + +- 标签如 ``、`<link>` 和 `<meta>` 将自动提升到 `<head>`。 +- 支持客户端应用、流式服务器渲染(SSR)和服务器组件。 + +**注意**:复杂的元数据需求仍可使用专用库(如 react-helmet)。 + +更多信息请参阅 `<title>`、`<link>` 和 `<meta>` 的文档。 + +--- + +## 样式表支持 + +React 19 对样式表支持进行了增强,使样式表与组件的依赖关系更紧密,并优化了客户端和服务器渲染中的样式加载: + +### 特性: + +- 使用 `precedence` 属性指定样式表的优先级,React 将根据优先级管理样式表插入顺序。 +- 服务器渲染中,React 会确保样式表在 `<head>` 中并在内容呈现前加载。 +- 客户端渲染中,React 将等待样式表加载完成后再提交渲染。 +- 重复引用的样式表不会被多次插入。 + +示例: + +```javascript +function ComponentOne() { + return ( + <Suspense fallback="loading..."> + <link rel="stylesheet" href="foo" precedence="default" /> + <link rel="stylesheet" href="bar" precedence="high" /> + <article class="foo-class bar-class">...</article> + </Suspense> + ); +} + +function ComponentTwo() { + return ( + <div> + <p>...</p> + <link rel="stylesheet" href="baz" precedence="default" /> + </div> + ); +} +``` + +更多信息请参阅 `<link>` 和 `<style>` 的文档。 + +--- + +## 异步脚本支持 + +React 19 增强了对异步脚本的支持,可以在组件树中任何位置渲染异步脚本,而无需手动管理脚本的位置和去重。 + +示例: + +```javascript +function MyComponent() { + return ( + <div> + <script async={true} src="..." /> + Hello World + </div> + ); +} + +function App() { + return ( + <html> + <body> + <MyComponent /> + <MyComponent /> {/* 不会重复加载脚本 */} + </body> + </html> + ); +} +``` + +### 特性: + +- 异步脚本在所有渲染环境中都会被去重,只加载一次。 +- 服务器渲染中,异步脚本将被包含在 `<head>` 中并优先于非关键资源。 + +更多信息请参阅 `<script>` 的文档。 + +--- + +## 资源预加载支持 + +React 19 提供了一组新 API 来优化资源加载,通过更早告知浏览器需要加载的资源来提升性能。 + +### 示例: + +```javascript +import { prefetchDNS, preconnect, preload, preinit } from "react-dom"; + +function MyComponent() { + preinit("https://.../path/to/some/script.js", { as: "script" }); // 提前加载并执行脚本 + preload("https://.../path/to/font.woff", { as: "font" }); // 预加载字体 + preload("https://.../path/to/stylesheet.css", { as: "style" }); // 预加载样式表 + prefetchDNS("https://..."); // 提前解析 DNS + preconnect("https://..."); // 提前建立连接 +} +``` + +上述代码将生成以下 HTML: + +```html +<html> + <head> + <link rel="prefetch-dns" href="https://..." /> + <link rel="preconnect" href="https://..." /> + <link rel="preload" as="font" href="https://.../path/to/font.woff" /> + <link rel="preload" as="style" href="https://.../path/to/stylesheet.css" /> + <script async="" src="https://.../path/to/some/script.js"></script> + </head> + <body> + ... + </body> +</html> +``` + +### 特性: + +- 优化初始页面加载,通过更早发现资源提升性能。 +- 支持客户端更新,通过预加载提升导航速度。 + +更多信息请参阅资源预加载 API 的文档。 diff --git a/posts/what_changes_in_react_v19_04_zh.md b/posts/what_changes_in_react_v19_04_zh.md new file mode 100644 index 0000000..a2ef526 --- /dev/null +++ b/posts/what_changes_in_react_v19_04_zh.md @@ -0,0 +1,72 @@ +--- +title: React 19 有哪些变化 - 4 +published_at: 2024-12-12T15:00:00.000Z +snippet: React V19 change log 解读. +--- + +这篇文章在第二部分介绍了一些对之前特性的升级. + +## 与第三方脚本和扩展的兼容性 + +React 19 改进了 Hydration,能够更好地处理第三方脚本和浏览器扩展引起的问题。 + +### 特性: + +- 当 Hydration 过程中,客户端渲染的元素与服务器 HTML 不匹配时,React 会强制客户端重新渲染修复内容。 +- 如果 `<head>` 和 `<body>` 中存在意外的第三方标签,React 会跳过这些标签以避免不必要的错误。 +- 如果发生非相关的 Hydration 错误导致整个文档需要重新渲染,React 会保留由第三方脚本或扩展插入的样式表。 + +--- + +## 更好的错误报告 + +React 19 改进了错误处理,减少了重复错误日志,并为捕获和未捕获的错误提供了更多选项: + +### 示例: + +以前: + +```console +Uncaught Error: hit + at Throws + at renderWithHooks + … +Uncaught Error: hit <-- Duplicate + at Throws + at renderWithHooks + … +``` + +现在: + +```console +Error: hit + at Throws + at renderWithHooks + … + +React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. +``` + +### 新增选项: + +- `onCaughtError`:当 React 在 Error Boundary 中捕获错误时调用。 +- `onUncaughtError`:当错误未被 Error Boundary 捕获时调用。 +- `onRecoverableError`:当错误被自动恢复时调用。 + +更多信息请参阅 `createRoot` 和 `hydrateRoot` 的文档。 + +--- + +## 自定义元素支持 + +React 19 对自定义元素提供了全面支持,并通过了 Custom Elements Everywhere 的所有测试。 + +### 特性: + +- **服务器渲染**: + - 如果 `props` 是字符串、数字或 `true` 等原始值,会作为属性渲染。 + - 如果是对象、函数、符号或 `false`,则会被忽略。 +- **客户端渲染**: + - 匹配自定义元素实例属性的 `props` 将作为属性分配。 + - 其他 `props` 会作为 HTML 属性分配。 diff --git a/posts/what_changes_in_react_v19_05_zh.md b/posts/what_changes_in_react_v19_05_zh.md new file mode 100644 index 0000000..c3eb5cd --- /dev/null +++ b/posts/what_changes_in_react_v19_05_zh.md @@ -0,0 +1,279 @@ +--- +title: React 19 有哪些变化 - 5 +published_at: 2024-12-13T15:00:00.000Z +snippet: React V19 change log 解读. +--- + +这是另外一篇升级指南, 因为内容比较多, 先选择了最重要的 breaking changes, 以后再补充其他内容. + +## 不兼容的更改 + +### 渲染中的错误不再重新抛出 + +在 React 的早期版本中,渲染中抛出的错误会被捕获并重新抛出。在开发模式下,还会记录到 `console.error`,导致重复的错误日志。 + +在 React 19 中,改进了错误处理方式以减少重复: + +- **未捕获的错误**:未被 Error Boundary 捕获的错误会报告给 `window.reportError`。 +- **已捕获的错误**:被 Error Boundary 捕获的错误会记录到 `console.error`。 + +如果您的生产环境依赖于错误被重新抛出进行错误报告,可能需要更新错误处理逻辑。可以通过 `createRoot` 和 `hydrateRoot` 提供的新方法实现: + +```javascript +const root = createRoot(container, { + onUncaughtError: (error, errorInfo) => { + // ... 记录错误报告 + }, + onCaughtError: (error, errorInfo) => { + // ... 记录错误报告 + }, +}); +``` + +--- + +### 移除已弃用的 React API + +#### 函数组件的 `propTypes` 和 `defaultProps` + +`propTypes` 自 2017 年 4 月(v15.5.0)开始被废弃。在 React 19 中,React 包中不再包含 `propTypes` 检查,相关代码将被忽略。 + +`defaultProps` 也已从函数组件中移除,建议使用 ES6 默认参数代替。但类组件仍支持 `defaultProps`。 + +**迁移示例**: + +```javascript +// 之前 +import PropTypes from "prop-types"; + +function Heading({ text }) { + return <h1>{text}</h1>; +} +Heading.propTypes = { + text: PropTypes.string, +}; +Heading.defaultProps = { + text: "Hello, world!", +}; + +// 之后 +interface Props { + text?: string; +} +function Heading({ text = "Hello, world!" }: Props) { + return <h1>{text}</h1>; +} +``` + +--- + +#### 移除旧版 Context API + +旧版 Context(`contextTypes` 和 `getChildContext`)自 2018 年 10 月(v16.6.0)开始被废弃。在 React 19 中,这些 API 已被移除,建议迁移到新的 Context API: + +```javascript +// 之前 +import PropTypes from "prop-types"; + +class Parent extends React.Component { + static childContextTypes = { + foo: PropTypes.string.isRequired, + }; + + getChildContext() { + return { foo: "bar" }; + } + + render() { + return <Child />; + } +} + +class Child extends React.Component { + static contextTypes = { + foo: PropTypes.string.isRequired, + }; + + render() { + return <div>{this.context.foo}</div>; + } +} + +// 之后 +const FooContext = React.createContext(); + +class Parent extends React.Component { + render() { + return ( + <FooContext.Provider value="bar"> + <Child /> + </FooContext.Provider> + ); + } +} + +class Child extends React.Component { + static contextType = FooContext; + + render() { + return <div>{this.context}</div>; + } +} +``` + +--- + +#### 移除字符串 refs + +字符串 refs 自 2018 年 3 月(v16.3.0)开始被废弃。在 React 19 中,字符串 refs 已被移除。建议迁移到回调 refs: + +```javascript +// 之前 +class MyComponent extends React.Component { + componentDidMount() { + this.refs.input.focus(); + } + + render() { + return <input ref="input" />; + } +} + +// 之后 +class MyComponent extends React.Component { + componentDidMount() { + this.input.focus(); + } + + render() { + return <input ref={(input) => (this.input = input)} />; + } +} +``` + +--- + +#### 移除模块模式工厂 + +模块模式工厂自 2019 年 8 月(v16.9.0)开始被废弃。在 React 19 中,已不再支持模块模式工厂,建议迁移到常规函数: + +```javascript +// 之前 +function FactoryComponent() { + return { + render() { + return <div />; + }, + }; +} + +// 之后 +function FactoryComponent() { + return <div />; +} +``` + +--- + +#### 移除 `React.createFactory` + +`createFactory` 自 2020 年 2 月(v16.13.0)开始被废弃。在 React 19 中已移除,建议迁移到 JSX: + +```javascript +// 之前 +import { createFactory } from "react"; + +const button = createFactory("button"); + +// 之后 +const button = <button />; +``` + +--- + +#### 移除 React Test Renderer 的浅渲染支持 + +在 React 18 中,`react-test-renderer/shallow` 已被更新为重新导出 `react-shallow-renderer`。在 React 19 中,直接移除浅渲染,建议直接安装该包: + +```bash +npm install react-shallow-renderer --save-dev +``` + +```javascript +// 之前 +import ShallowRenderer from "react-test-renderer/shallow"; + +// 之后 +import ShallowRenderer from "react-shallow-renderer"; +``` + +建议将测试迁移到 `@testing-library/react` 或 `@testing-library/react-native`。 + +--- + +#### 移除 React DOM 的旧 API + +- **移除 `ReactDOM.render`**: + - React 19 移除了 `ReactDOM.render`,需要迁移到 `ReactDOM.createRoot`。 + +```javascript +// 之前 +import { render } from "react-dom"; +render(<App />, document.getElementById("root")); + +// 之后 +import { createRoot } from "react-dom/client"; +const root = createRoot(document.getElementById("root")); +root.render(<App />); +``` + +- **移除 `ReactDOM.hydrate`**: + - React 19 移除了 `ReactDOM.hydrate`,需要迁移到 `ReactDOM.hydrateRoot`。 + +```javascript +// 之前 +import { hydrate } from "react-dom"; +hydrate(<App />, document.getElementById("root")); + +// 之后 +import { hydrateRoot } from "react-dom/client"; +hydrateRoot(document.getElementById("root"), <App />); +``` + +- **移除 `unmountComponentAtNode`**: + - React 19 移除了 `unmountComponentAtNode`,需要迁移到 `root.unmount()`。 + +```javascript +// 之前 +unmountComponentAtNode(document.getElementById("root")); + +// 之后 +root.unmount(); +``` + +- **移除 `ReactDOM.findDOMNode`**: + - React 19 移除了 `findDOMNode`,建议迁移到 DOM refs: + +```javascript +// 之前 +import { findDOMNode } from "react-dom"; + +function AutoselectingInput() { + useEffect(() => { + const input = findDOMNode(this); + input.select(); + }, []); + + return <input defaultValue="Hello" />; +} + +// 之后 +function AutoselectingInput() { + const ref = useRef(null); + useEffect(() => { + ref.current.select(); + }, []); + + return <input ref={ref} defaultValue="Hello" />; +} +```