升级18
18有哪些更新
- root节点的处理
// 旧
ReactDOM.render(<App />, document.getElementById('root'));
// 新
const root = createRoot(document.getElementById("root"));
root.render(App);
render中移除了回调函数
// 旧
const container = document.getElementById('app');
render(<App tab="home" />, container, () => {
console.log('rendered');
});
// 新
function AppWithCallbackAfterRender() {
useEffect(() => {
console.log('rendered');
});
return <App tab="home" />
}
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<AppWithCallbackAfterRender />);
- Transitions,用于区分高优更新和非高优更新的新概念。
- 高优的更新/渲染:包括鼠标点击、打字等对实时交互性要求很高的更新场景,卡顿时会影响用户的交互行为,使用户明显感到整个页面卡顿。
- 非高优的更新/渲染:普通的 UI 更新,不与用户的交互相关,一些对更新实时性要求没那么高的场景。
import { startTransition } from "react";
const handleChange=()=>{
/* 高优先级任务 —— 改变搜索条件 */
setInputValue(e.target.value)
/* 低优先级任务 —— 改变搜索过滤后列表状态 */
startTransition(()=>{
setSearchQuery(e.target.value)
})
}
useTransition:除了能提供 startTransition 以外,还能提供一个变量来跟踪当前渲染的执行状态:
import { useTransition } from "react";
const [isPending, startTransition] = useTransition();
return isPending && <Spinner />;
-
批处理
从使用 createRoot 的 React 18 开始,无论来自于哪里,所有的更新都会自动批量处理。也就是说批处理支持处理的操作范围扩大了:Promise,setTimeout,native event handlers 等这些非 React 原生的事件内部的更新也会得到合并。 -
Suspense
在老版本里,我们通常在路由懒加载中配合 Lazy 组件一起使用。
const Test = lazy(() => import('@/views/Test'))
<Suspense fallback={<div>loading</div>}>
<Test />
</Suspense>
React 18 中,增加了对服务端(SSR)的 Suspense 支持,并使用并发渲染特性扩展了其功能。
SSR可以从服务器上的 React 组件生成 HTML,并将该 HTML 发送回来。SSR 允许用户在 JavaScript 包加载和运行之前查看页面的内容。
React 中的 SSR 总是发生在几个步骤中:
- 在服务器端,获取整个应用程序的数据。
- 在服务器端,将整个应用程序呈现为 HTML 字符串并将其发送到客户端。
- 在客户端,加载整个应用程序的JavaScript 代码。
- 在客户端,将JavaScript 逻辑连接到整个应用程序的服务器生成的HTML(这就是“hydration”)。
关键部分是,在下一步开始之前,整个应用程序的每个步骤都必须立即完成。如果其中一个环节比其他部分慢,将会影响整体的加载时间。
React18中,可以使用 将应用程序分解成更小的独立单元,每个模块都是独自异步加载,并不会影响其余部分。即便是应用程序中最慢的模块也不会拖累较快的模块。因此,用户也将更快地看到内容,并更快的开始与之交互。
- 新增的hooks
除了上述的useTransition外,还有如下- useDeferredValue
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
它用于延迟获取某个值,并且在延迟获取之间将会返回旧的值。通俗解释就是useDeferredValue传入一个参数,这个参数是一个任意类型的值,例如我们就传入一个使用useState定义的变量value,value的初始值是字符串’abc’,当我们修改value时,他就会延迟返回一个最新的value值,类似防抖节流函数。
但与防抖节流函数不同,useDeferredValue 更适合优化渲染,因为它与 React 自身深度集成,它不需要选择任何固定延迟时间。如果用户的设备很快(比如性能强劲的笔记本电脑),延迟的重渲染几乎会立即发生并且不会被察觉。如果用户的设备较慢,那么列表会相应地“滞后”于输入,滞后的程度与设备的速度有关。
- useId
生成一个在整个应用程序中唯一 id的hook函数 - useDebugValue
useDebugValue 是一个 React Hook,可以让你在 React 开发工具 中为自定义 Hook 添加标签。 - useSyncExternalStore
订阅外部 store 的 React Hook。
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
export default function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
return (
<>
<button onClick={() => todosStore.addTodo()}>Add todo</button>
<hr />
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
);
}
// 这是一个第三方 store 的例子,
// 你可能需要把它与 React 集成。
// 如果你的应用完全由 React 构建,
// 我们推荐使用 React state 替代。
let nextId = 0;
let todos = [{ id: nextId++, text: 'Todo #1' }];
let listeners = [];
export const todosStore = {
addTodo() {
todos = [...todos, { id: nextId++, text: 'Todo #' + nextId }]
emitChange();
},
subscribe(listener) {
listeners = [...listeners, listener];
return () => {
listeners = listeners.filter(l => l !== listener);
};
},
getSnapshot() {
return todos;
}
};
function emitChange() {
for (let listener of listeners) {
listener();
}
}
- useInsertionEffect
是为 CSS-in-JS 库的作者特意打造的,可以在布局副作用触发之前将元素插入到 DOM 中。
import { useInsertionEffect } from 'react';
// 在你的 CSS-in-JS 库中
function useCSS(rule) {
useInsertionEffect(() => {
// ... 在此注入 <style> 标签 ...
});
return rule;
}
- 升级踩坑
- 根节点警告以及render回调,按照1修改
- 服务端渲染,需要将 hydrate 升级到 hydrateRoot
- 根节点警告以及render回调,按照1修改
// 之前
import { hydrate } from 'react-dom';
const container = document.getElementById('app');
hydrate(<App tab="home" />, container);
// 现在
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = hydrateRoot(container, <App tab="home" />);
// 和 createRoot 不一样,在这里你不需要单独的 root.render()。
-
- 如果项目使用了 TypeScript,你需要更新 @types/react 和 @types/react-dom 依赖到最新版。
- 第一次更新测试环境使用 createRoot,你可能在测试环境的控制台看到这个警告,直接配置文件globalThis.IS_REACT_ACT_ENVIRONMENT = true即可
React DOM Server
renderToString:当在服务端挂起时,它不再会报错。而是会为最接近的 边界发射后备 HTML,然后在客户端尝试渲染同样的内容。我们仍然推荐你切换到像 renderToPipeableStream 或者 renderToReadableStream 这样的流式 API。
renderToStaticMarkup:当在服务端挂起时,它不再会报错。而是会为最接近的 边界发射后备 HTML。