怎么做网站的内链外链qq官方网页版登录

当前位置: 首页 > news >正文

怎么做网站的内链外链,qq官方网页版登录,网站建设基本流程,如何做网站页面概述 在React项目中说到状态管理#xff0c;我们第一时间想到的就是使用useState、useReducer这种Hooks来进行状态管理。但是这种是针对React内部的状态#xff0c;如果有时候我们需要订阅外部的状态并影响React组件的更新的话#xff0c;那通过这种内部状态管理API显然不能…概述 在React项目中说到状态管理我们第一时间想到的就是使用useState、useReducer这种Hooks来进行状态管理。但是这种是针对React内部的状态如果有时候我们需要订阅外部的状态并影响React组件的更新的话那通过这种内部状态管理API显然不能满足了这时候就需要使用本文的主角useSyncExternalStore这个Hook了。这个API是React18提供的一个内置API赋予了React能订阅外部状态的能力当订阅的外部状态发生改变时会触发React的重新渲染。本文将会从一下几个方面来逐步介绍该API的使用和原理有需求的同学自行跳转至感兴趣的部分。 基础使用源码解析 Mount首次渲染时Update 更新渲染时
基础使用 函数定义 先看定义useSyncExternalStore接受三个参数subscribe、getSnapshot、getServerSnapshot然后返回一个数据快照。 export function useSyncExternalStoreT(subscribe: (() void) () void,getSnapshot: () T,getServerSnapshot?: () T, ): T {const dispatcher resolveDispatcher();return dispatcher.useSyncExternalStore(subscribe,getSnapshot,getServerSnapshot,); }详细说明如下 subscribe 接收一个订阅函数并返回一个取消订阅函数该函数主要用于订阅外部store状态订阅外部store的哪些状态取决于subscribe函数的实现。当订阅的状态值变化时会触发组件的重现渲染。getSnapshot一个用于获取当前状态快照的函数React组件每次渲染都会执行该函数然后获得当前订阅状态的快照并将这个快照作为useSyncExternalStore函数返回值getServerSnapshot: 返回订阅状态的初始快照它只会在服务端渲染(SSR)时以及在客户端浏览器端进行服务端渲染内容的 hydration 时被用到。返回值是一个数据快照可以理解为就是getSnapshot的执行结果 PS 由于getSnapshot会在每次组件渲染时都会执行所以一般将这个函数定义在组件外部或者使用useCallback包裹避免不必要的渲染。 想了解为什么使用useCallback包裹能避免重复渲染的可以查看这篇文章【React Hooks原理 - useCallback、useMemo】 由于服务端渲染的是静态页面不能进行动态交互所以在React项目中会通过hydration(水合阶段)将其赋予动态交互能力具体流程本文最后题外话会简单介绍。 语法使用 上面主要介绍了useSyncExternalStore函数定义这小节主要通过举例Demo的形式让我们初步了解该函数的运用并方便下面源码解析的理解。由于第三个参数getServerSnapshot用于SSR所以本文不做介绍有兴趣的同学可以查看其他文章了解 外部第三方Store定义 这里要注意subscribe、getSnapshot这两个函数会在useSyncExternalStoreHook中调用并注入React自己的listener函数所以这两个函数必须在第三方Store中暴露否则不能正确使用useSyncExternalStore功能。 const store {// 外部状态state: {todos: [],user: { name: Alice, age: 30 }},// 监听状态变化listeners: new Set(),// 订阅状态函数subscribe(listener) {this.listeners.add(listener);return () this.listeners.delete(listener);},// 获取快照函数getSnapshot() {return this.state;},// 状态更新后通知所有注入的监听器并执行notify() {this.listeners.forEach(listener listener());},// 更新todos状态updateTodos(newTodos) {this.state.todos newTodos;this.notify();},// 更新user状态updateUser(newUser) {this.state.user newUser;this.notify();} }; React项目代码 import React from react; import { useSyncExternalStore } from react;const todosStore {subscribe(listener) {return store.subscribe(() {const { todos } store.getSnapshot();listener(todos);});},getSnapshot() {const { todos } store.getSnapshot();return todos;} };const TodosComponent () {const todos useSyncExternalStore(todosStore.subscribe.bind(todosStore),todosStore.getSnapshot.bind(todosStore));return (divh1Todos/h1ul{todos.map((todo, index) (li key{index}{todo}/li))}/ul/div); }; 由于外部状态暴露的subscribe只注入了监听器并返回一个销毁注册的函数默认是订阅所有外部状态在这里我们demo主要是订阅其中部分状态即todos所以在React项目中自定义了subscribe并解析todos进行订阅以及自定义getSnapshot函数只获取todos的状态信息即useSyncExternalStoreHook返回的数据。 可以理解为我们在React项目 - 外部状态的连接中添加了一个节点用于处理自定义订阅和消费状态即React项目 - CustomFn(todosStore) - 外部状态。 至此基本使用我们已经介绍差不多了接下来进入源码解析阶段由于下面会引用这个Demo例子帮助理解所以还请务必先浏览器下Demo。 源码解析 同大多数Hooks一样该Hook也主要从首次挂载Mount、以及更新渲染Update两个阶段来介绍。 Mount首次渲染 通过上面介绍我们知道useSyncExternalStoreHook接收三个参数并当前当前的数据快照下面从mountSyncExternalStore这个入口函数出发 mountSyncExternalStore函数 function mountSyncExternalStoreT(subscribe: (() void) () void,getSnapshot: () T,getServerSnapshot?: () T ): T {// 获取当前fiber即workInProgress中的fiber节点const fiber currentlyRenderingFiber;// 创建Hook并挂载在fiber.memoizedState上const hook mountWorkInProgressHook();// 保存数据快照let nextSnapshot;// 是否是水合阶段SSRconst isHydrating getIsHydrating();if (isHydrating) {// 如果是SSR的水合阶段则getServerSnapshot参数必传否则报错if (getServerSnapshot undefined) {throw new Error(Missing getServerSnapshot, which is required for server-rendered content. Will revert to client rendering.);}// 由于此时是首次挂载SSR时使用getServerSnapshot服务器的初始值作为返回状态nextSnapshot getServerSnapshot();} else {// 客户端渲染时执行第二个参数获取当前外部状态快照nextSnapshot getSnapshot();// 获取当前渲染fiber的根root节点const root: FiberRoot | null getWorkInProgressRoot();if (root null) {throw new Error(Expected a work-in-progress root. This is a bug in React. Please file an issue.);}// 获取root节点的优先级const rootRenderLanes getWorkInProgressRootRenderLanes();// 判断当前构建的fiber树是否是阻断性渲染// 当阻塞渲染时跳过一致性校验是处于性能考虑避免后续复杂的对比计算if (!includesBlockingLane(root, rootRenderLanes)) {// 一致性检查是为了保证当前渲染的状态和外部状态是一致的如果外部状态更新当一致性检查不一致时会重新渲染组件// 这个函数只是将新旧状态保存在了updateQueue.stores中后续在协调阶段的isRenderConsistentWithExternalStores函数中进行处理pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);}}// 保存当前的数据快照更新渲染是会通过memoizedState获取上一次的值hook.memoizedState nextSnapshot;// 记录本次渲染的状态值和获取最新状态的函数用于后面新旧值对比更新const inst: StoreInstanceT {value: nextSnapshot,getSnapshot,};// 将状态信息保存到hook链表中后续更新hook.queue inst;// subscribeToStore处理subscribe逻辑mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);// 添加flagfiber.flags | PassiveEffect;// 将副作用添加到fiber的更新队列updateQueue中pushEffect(HookHasEffect | HookPassive,updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),createEffectInstance(),null);// 返回当前快照return nextSnapshot; }从上面代码及注释中相比我们初步了解了mountSyncExternalStore的作用: 主要就是创建了hook、effect、updateQueue等初始化工作。在其中引入了其他几个函数这里我们展开说说 mountWorkInProgressHook函数在其他介绍其他Hook的时候已经介绍所以这里不再赘述作用就是创建一个初始Hook并绑定到fiber上 getIsHydrating函数主要是判断当前是否是SSR的水合阶段这里也不做介绍 getWorkInProgressRoot函数用于返回当前渲染的workInProgress树的根节点 export function getWorkInProgressRoot(): FiberRoot | null {return workInProgressRoot; }getWorkInProgressRootRenderLanes函数返回当前渲染树的渲染优先级 export function getWorkInProgressRootRenderLanes(): Lanes {return workInProgressRootRenderLanes; }Scheduler关于优先级设置可以查看这篇文章:【React源码 - 调度任务循环EventLoop】 React 使用“lanes”来表示不同的优先级它们分为以下几种类型 Blocking Lane代表需要尽快完成的任务但可以稍微延迟的更新。Urgent Lane需要立即处理的任务如用户输入。Normal Lane普通优先级的任务。Idle Lane低优先级的任务可以在空闲时间处理。 通过if (!includesBlockingLane(root, rootRenderLanes))根据优先级判断是否需要进行一致性主要是为了优化性能出于以下考虑 性能考虑阻塞优先级的任务需要尽快完成跳过一致性检查可以减少渲染过程中的开销。状态一致性非阻塞优先级的任务需要确保渲染的状态与外部状态一致所以需要进行一致性检查。 pushStoreConsistencyCheck函数用于对比新旧状态的一致性校验当前渲染会非阻塞级渲染时则会进行一致性校验如果外部状态更新则会重新渲染组件以便显示最新的状态。 function pushStoreConsistencyCheckT(fiber: Fiber,getSnapshot: () T,renderedSnapshot: T ): void {// 添加一致性校验Flagfiber.flags | StoreConsistency;// 保存当前状态快照以及获取快照的函数const check: StoreConsistencyCheckT {getSnapshot,value: renderedSnapshot,};// 获取fiber.updateQueue更新队列let componentUpdateQueue: null | FunctionComponentUpdateQueue (currentlyRenderingFiber.updateQueue: any);// 当前没有更新任务时创建更新队列并将保存值的对象check以数组形式绑定在storesif (componentUpdateQueue null) {componentUpdateQueue createFunctionComponentUpdateQueue();currentlyRenderingFiber.updateQueue (componentUpdateQueue: any);componentUpdateQueue.stores [check];} else {// 有更新任务时直接绑定storesconst stores componentUpdateQueue.stores;if (stores null) {componentUpdateQueue.stores [check];} else {stores.push(check);}} }这个函数只是将新旧状态保存在了updateQueue.stores中后续在协调阶段的isRenderConsistentWithExternalStores函数中会进行处理。协调阶段中进入fiber构造前 performConcurrentWorkOnRoot - isRenderConsistentWithExternalStores在isRenderConsistentWithExternalStores中会循环遍历每个fiber节点并根据flag是否包含StoreConsistency进行一致性校验如果两次对比不一致则调用 scheduleUpdateOnFiber 函数发起更新调度申请 if (node.flags StoreConsistency) { // 获取更新任务const updateQueue: FunctionComponentUpdateQueue | null (node.updateQueue: any);if (updateQueue ! null) {// 获取上面保存的新旧外部状态const checks updateQueue.stores;if (checks ! null) {for (let i 0; i checks.length; i) {const check checks[i];const getSnapshot check.getSnapshot;const renderedValue check.value;try {// 执行getSnapshot函数获取最新的外部状态并和组件内的状态对比if (!is(getSnapshot(), renderedValue)) {// Found an inconsistent store.return false;}} catch (error) {// If getSnapshot throws, return false. This will schedule// a re-render, and the error will be rethrown during render.return false;}}}} }mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); 这个代码就是处理subscribe来传入React自身的listener用于状态更新时执行触发组件更新的函数。在这里通过bind绑定了subscribeToStore对subscribe进行处理. 使用mountEffect订阅是因为其能保证只在挂载和依赖更新时触发并且会在组件卸载时执行清除函数mountEffect其实是useEffect这个Hook在首次挂载时执行的函数具体关于mountEffect函数请查看这篇文章:【React Hooks原理 - useEffect、useLayoutEffect】 subscribeToStore函数主要就是当状态改变之后强制组件重新渲染 // 对比外部状态和组件内部状态是否一致 function checkIfSnapshotChangedT(inst: StoreInstanceT): boolean {const latestGetSnapshot inst.getSnapshot;const prevValue inst.value;try {const nextValue latestGetSnapshot();return !is(prevValue, nextValue);} catch (error) {return true;} }// 当状态不一致时强制组件更新 function subscribeToStoreT(fiber: Fiber,inst: StoreInstanceT,subscribe: (() void) () void ): any {const handleStoreChange () {// 对比外部状态是否改变if (checkIfSnapshotChanged(inst)) {// 外部状态改变和组件内不一致则强制刷新类似我们使用的forceUpdate()forceStoreRerender(fiber);}};// Subscribe to the store and return a clean-up function.return subscribe(handleStoreChange); }// 直接调用scheduleUpdateOnFiber发起调度 function forceStoreRerender(fiber: Fiber) {const root enqueueConcurrentRenderForLane(fiber, SyncLane);if (root ! null) {scheduleUpdateOnFiber(root, fiber, SyncLane); // 发送更新调度申请scheduleUpdateOnFiber 负责标记需要更新的 Fiber 节点并将其放入调度队列中等待调度器Scheduler来确定何时执行这些更新。} }至此在首次挂载阶段的所有源码流程我们都介绍完了在这里简单小节一下在使用useSyncExternalStore时React就会通过subscribe来订阅外部状态并对subscribe进行处理传递一个对比状态更新组件的函数handleStoreChange一旦外部状态更新对比组件内部和通过getSnapshot获取最新外部状态React中通过一致性判断不一致时就会发起更新调度。 Update更新渲染 当外部状态改变时比如通过store.updateTodos(newValue)修改订阅的数据之后外部Store会通知执行所有添加的listener即在Mount阶段注入的handleStoreChange函数并触发组件更新进入更新渲染阶段。在更新阶段是从updateSyncExternalStore入口函数开始的下面看代码 function updateSyncExternalStoreT(subscribe: (() void) () void,getSnapshot: () T,getServerSnapshot?: () T, ): T {// 获取渲染的fiberconst fiber currentlyRenderingFiber;// 复用现有链表性能优化减少重复创建Hook链表的性能消耗复用workInProgress中的链表或者克隆current当前页面显示的fiber链表// 并将本次更新对象添加到更新队列中const hook updateWorkInProgressHook();let nextSnapshot;const isHydrating getIsHydrating();// 是否是SSR水合阶段if (isHydrating) {// getServerSnapshot不传则报错if (getServerSnapshot undefined) {throw new Error(Missing getServerSnapshot, which is required for server-rendered content. Will revert to client rendering.,);}nextSnapshot getServerSnapshot();} else {nextSnapshot getSnapshot();}// 通过hook.memoizedState获取上一次渲染缓存的值const prevSnapshot (currentHook || hook).memoizedState;// 通过Object.is判断状态是否改变const snapshotChanged !is(prevSnapshot, nextSnapshot);if (snapshotChanged) {// 如果改变就更新换成值然后触发组件更新hook.memoizedState nextSnapshot;markWorkInProgressReceivedUpdate();}// 获取hook的更新队列链表的头节点const inst hook.queue;// 组件更新渲染时执行updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe,]);// 当getSnapshot or subscribe改变时就需要提交更新这里getSnapshot比较的是函数引用对应上面提到了避免多次渲染将其放在组件外或者使用useCallback包裹if (inst.getSnapshot ! getSnapshot ||snapshotChanged ||// 过滤一些异常情况性能优化(workInProgressHook ! null workInProgressHook.memoizedState.tag HookHasEffect)) {// 添加flagfiber.flags | PassiveEffect;// 添加副作用到fiber中后续在协调时会遍历fiber并处理其副作用列表pushEffect(HookHasEffect | HookPassive,updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),createEffectInstance(),null,);// 获取root节点const root: FiberRoot | null getWorkInProgressRoot();if (root null) {throw new Error(Expected a work-in-progress root. This is a bug in React. Please file an issue.,);}// 对于非阻塞渲染需要进行一致性校验if (!isHydrating !includesBlockingLane(root, renderLanes)) {pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);}}// 返回当前数据快照return nextSnapshot; }从代码能看出其中有一些代码和首次渲染时mountSyncExternalStore函数一致相信经过上面的介绍和代码注释相比已经能理解更新过程了 所以在这里小节一下虽然 Mount 和 Update 阶段的逻辑非常相似但它们的主要区别在于 初始化 vs 更新 Mount 阶段初始化各种 Hook 对象和状态Update 阶段复用现有的 Hook 对象和状态。订阅和副作用 Mount 阶段使用 mountEffect 注册订阅外部状态的副作用Update 阶段使用 updateEffect 更新订阅副作用。一致性检查 两个阶段都会进行一致性检查但在 Update 阶段会优先比较当前快照与之前的快照并在不一致时进行副作用更新、一致性校验、更新缓存值等操作否则会跳过更新阶段直接返回快照。 总之就是一句话 Mount主要初始化创建Hook、updateQueue、EffectUpdate主要更新、复用和性能优化。 总结 一句话说明useSyncExternalStore就是赋予React具有访问外部状态并触发更新的能力。具体流程就是通过subscribe来注入React自身的监听器(listener)并订阅外部状态(全部或者部分state),当订阅状态更新时会通过注入的listener来触发React重新渲染每次渲染之后会执行getSnapshot获取订阅的数据快照并将其结果返回。 其中React主要就是将自身的listener传递给外部状态然后当更新外部状态时。在外部状态中当监听到状态更新时会执行所有注入的listener然后React中的listener会获取最新的状态快照并发送更新调度进而实现组件的重现渲染。 题外话 什么是React的hydration阶段 这个阶段主要指的是当页面使用服务器渲染(SSR)有服务端生成HTML并传回给客户端浏览器端显示时由于SSR传递到客户端是静态的HTML文件不能进行用户交互(没有事件绑定等)所以React的hydration阶段就是将静态页面转换为可交付的页面的过程。 流程介绍 服务端渲染 (SSR) ○ 服务器生成 HTML 内容并将其发送到客户端。这一步骤确保页面在浏览器中快速呈现即使在 JavaScript 尚未完全加载和执行之前用户也可以看到页面内容。 ○ 生成的 HTML 包含了所有需要的内容但这些内容是静态的无法进行任何交互操作。客户端接收 HTML ○ 浏览器接收到服务器发送的 HTML 并立即显示给用户。这时用户看到的是一个完整但静态的页面。 ○ 由于 HTML 是静态的用户暂时无法进行诸如点击按钮、填写表单等交互操作。加载和执行 JavaScript ○ 浏览器开始加载和执行页面中的 JavaScript 文件。React 的 JavaScript 代码被下载并执行。 ○ 在这一步React 会初始化并尝试将组件树与已经存在的 HTML 进行对比和合并。Hydration 过程 ○ React 使用 ReactDOM.hydrate 方法将客户端的组件树与服务端渲染的 HTML 进行对比。 ○ 这个过程包括将事件监听器绑定到已有的 DOM 元素上使得这些元素变得可交互。页面变为可交互 ○ 一旦 hydration 过程完成页面就变成了一个完整的、可交互的 React 应用。此时用户可以正常进行各种交互操作如点击按钮、输入文本等。 举例说明 假设有一个简单的计数器应用服务端渲染了初始计数值为 0 的 HTML div idrootdivp0/pbuttonIncrement/button/div /div在客户端React 会执行以下操作 React 组件初始化 React 使用相同的组件树进行初始化 function Counter() {const [count, setCount] useState(0);return (divp{count}/pbutton onClick{() setCount(count 1)}Increment/button/div); }Hydration 过程 React 将组件树与服务端的 HTML 进行对比确保二者一致 ReactDOM.hydrate(Counter /, document.getElementById(root));绑定事件监听器 React 将 button 元素的点击事件绑定到 Increment 按钮上 button onClick{() setCount(count 1)}Increment/button应用变为可交互 现在用户点击按钮时计数值会增加页面内容会更新。 总结通过SSR传递到客户端的HTML虽然包含所有的内容立即显示在页面但是没有绑定事件无法交互只是单纯的静态页面需要执行Js文件经过hydration将绑定事件转换为可交互。而hydration 是 React 在客户端将服务端生成的静态 HTML 转变为可交互应用的过程。这个过程确保了初始页面的快速加载同时通过绑定事件监听器和合并状态使得页面能够响应用户的交互。