当前位置: 首页 > 工具软件 > React Freeze > 使用案例 >

React 18 新特性

颜博达
2023-12-01

这里介绍一下React18的4个新特性:

  • Automatic batching
  • Concurrent APIs
  • SSR for Suspense
  • New Render API

Automatic bacthing

在 React 中使用setState来进行dispatch组件的State变化,当setState在组件中被调用后,并不会立即触发重新渲染。React 会执行全部事件处理函数,然后触发一个单独的re-render,合并所有的更新。
比如在点击+1的例子中,如果方法里连续触发三次setState,最终React会将更新函数放到一个队列里,然后合并队列触发setState的re-render,这就是batching的含义。

这样既可以减少程序数据状态存在中间值导致的不稳定性,也可以提高渲染性能。

在React 18 之前,如果在回调函数的异步调用中,执行setState,由于丢失上下文,无法做合并处理,所以每次setState调用都会触发一次re-render。

function handleClick() {
	// React 18 之前的版本
	(/*...*/).then(() => {
		setCount(c => c + 1); // 立刻重新渲染
		setShow(show => !show); // 立刻重新渲染
	});
}

React 18中,任何情况下都可以合并渲染!
如果仍然希望setState之后立即重新渲染,只需要使用flushSync包裹。

function handleClick() {
	// React 18
	fecth(/*...*/).then(() => {
		ReactDOM.flushSync(() => {
			setCount(c => c + 1); // 立刻重新渲染
			setFlag(f => !f);
		})
	})
}

Automatic batching 机制让我们有能力对渲染顺序和节奏进行一些基础的把控。例如在Canvas画布编辑场景中,我们可以加载完主节点框架之后立刻进行渲染,而每个节点的内容则可以进行合并渲染,尽可能加快用户看到可编辑页面的时间,同时避免http异步函数引起的频繁渲染的性能开销。

Concurrent APIs

官方明确指出了React 18 中并不存在 Concurrent Mode,只有用于并发渲染的并发新特性,开发者希望能够在Web Platform引入并发渲染,来实现多个渲染任务的并行渲染,其中Suspense就是基于此诞生的。

React18支持并发特性的三个API

  • startTransition()
  • useDeferredValue()
  • useTransition()

startTransition()

import { startTransition } from 'react';

// 紧急更新
setInputValue(input)

// 标记回调函数内的更新为 非紧急更新
startTransition(() => {
	setSearchQuery(input)
})

所以,startTransition的作用就是:被startTransition包裹的setState触发的渲染被标记为不紧急渲染,意味着它们可以被其他紧急渲染所抢占,这种渲染优先级的调整手段可以帮助我们解决各种性能伪瓶颈,提升用户体验。

useDeferredValue()

这个hook适用于设置延迟值

function Page() {
	const [filters, mergeFilter] = useMergeState(defaultFilters);
	const deferedFilters = React.useDeferredValue(filters);
	
	return (
		<>
			<Filters filters={filters} />
			<List filters={deferedFilters}>
		</>
	)
}

useDeferredValue() 会将List组件的渲染变得更加平滑,深层次看来是 defered value 引起的渲染会被标记为不紧急渲染,会被filters引起的渲染进行抢占,进而达到用户快速输入搜索等场景下页面抖动或卡顿问题。

SSR for Suspense

早在2018年,React就推出了Suspense的基础版本。可以在客户端配合React.lazy动态加载代码,实现数据拉取和状态控制的关注点分离(即当子组件未加载完成时,父组件填充fallback声明的组件),但是不能在服务器端进行加载。

<Suspense fallback={<Loading />}>
	<Header />
	<Suspense fallback={<ListPlaceholder />}>
		<ListLayout />
	</Suspense>
</Suspense>

在React18中,Suspense可以运行在服务器端,Server Rendering的性能不再受制于性能最差的组件(木桶效应)。
在React 18 之前,Server Rendering的流程就是服务端请求所有数据,然后发送HTML到客户端或者说浏览器,然后由客户端的hydrate内容(可以搜索同构渲染进行学习),每个环节必须按部就班的执行。当Suspense可以在服务器端使用之后,一旦某个组件加载慢,就可以将fallback的内容传输到客户端(例如loading态),保证用户可以尽可能早的可进行交互。
更加优秀的是,hydrate可以通过用户的行为来调整优先级。例如Profile组件和正在Loading的组件同时处于Suspense的流程中,此时用户点击评论组件,React将会优先hydrate评论组件,尽可能优先满足用户交互体验。
回归到代码实现细节,整体框架上服务器和客户端的连接必然趋向于持续性的长连接,因此res.send需要变为res.socket,pipeToNodeWritable替换renderToString并配合Suspense即可。

New Render API

新的更友好的语义化render方式。

const container = document.getElementById('app');

// 旧的render API
ReactDOM.render(<App />, container);

// 新的 createRoot API
const root = ReactDOM.createRoot(container);
root.render(<App />);

Client 端提供了 新 水合 Hydrate API

const root = ReactDOM.createRoot(container, <App tab="home" />)

以及新的 useId() API来为组件生成唯一ID。

由于Suspense和并发渲染在React 18的大规模使用,一些具有External stores的API比如全局变量、document对象如何在并发场景下保持一致性呢?
React 18 提供了useSyncExternalStore这个hook,来保证External stores的一致性。

useSyncExternalStore(
	// 注册回调函数
	subscribe: (callback) => Unsubscribe,
	// 获取快照函数
	getSnapshot: () => state
) => state

具体使用方式例子:

const store = {
	state: { count: 0 },
	setState: (fn) => {
		store.state = fn(store.state); // 需要不可变的更新
		store.listeners.forEach(listener => listender());
	},
	listeners: new Set(),
	subscribe: (callback) => {
		store.listeners.add(callback);
		return () => store.listeners.delete(callback);
	},
	getSnapshot: () => {
		const snap = Object.freeze(store.state);
		return snap;
	}
}

// use external store
const Component = () => {
	const snap = useSyncExternalStore(store.subscribe, store.getSnapshot);
	return <div>{snap.count}</div>
}
 类似资料: