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

【React Hooks原理 - useSyncExternalStore】

概述

在React项目中说到状态管理,我们第一时间想到的就是使用useState、useReducer这种Hooks来进行状态管理。但是这种是针对React内部的状态,如果有时候我们需要订阅外部的状态并影响React组件的更新的话,那通过这种内部状态管理API显然不能满足了,这时候就需要使用本文的主角useSyncExternalStore这个Hook了。这个API是React18提供的一个内置API,赋予了React能订阅外部状态的能力,当订阅的外部状态发生改变时,会触发React的重新渲染。本文将会从一下几个方面来逐步介绍该API的使用和原理,有需求的同学自行跳转至感兴趣的部分。

  • 基础使用
  • 源码解析
    • Mount首次渲染时
    • Update 更新渲染时

基础使用

函数定义

先看定义,useSyncExternalStore接受三个参数:subscribegetSnapshotgetServerSnapshot然后返回一个数据快照。

export function useSyncExternalStore<T>(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定义:

  • 这里要注意subscribegetSnapshot这两个函数会在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 (<div><h1>Todos</h1><ul>{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 mountSyncExternalStore<T>(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: StoreInstance<T> = {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 pushStoreConsistencyCheck<T>(fiber: Fiber,getSnapshot: () => T,renderedSnapshot: T
    ): void {// 添加一致性校验Flagfiber.flags |= StoreConsistency;// 保存当前状态快照以及获取快照的函数const check: StoreConsistencyCheck<T> = {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绑定了subscribeToStoresubscribe进行处理.

    使用mountEffect订阅是因为其能保证只在挂载和依赖更新时触发,并且会在组件卸载时执行清除函数,mountEffect其实是useEffect这个Hook在首次挂载时执行的函数,具体关于mountEffect函数请查看这篇文章:【React Hooks原理 - useEffect、useLayoutEffect】

  • subscribeToStore函数主要就是当状态改变之后,强制组件重新渲染

    // 对比外部状态和组件内部状态是否一致
    function checkIfSnapshotChanged<T>(inst: StoreInstance<T>): boolean {const latestGetSnapshot = inst.getSnapshot;const prevValue = inst.value;try {const nextValue = latestGetSnapshot();return !is(prevValue, nextValue);} catch (error) {return true;}
    }// 当状态不一致时,强制组件更新
    function subscribeToStore<T>(fiber: Fiber,inst: StoreInstance<T>,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 updateSyncExternalStore<T>(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、Effect,Update主要更新、复用和性能优化。

总结

一句话说明useSyncExternalStore就是赋予React具有访问外部状态并触发更新的能力。具体流程就是通过subscribe来注入React自身的监听器(listener)并订阅外部状态(全部或者部分state),当订阅状态更新时会通过注入的listener来触发React重新渲染,每次渲染之后会执行getSnapshot获取订阅的数据快照并将其结果返回。

其中React主要就是将自身的listener传递给外部状态,然后当更新外部状态时。在外部状态中当监听到状态更新时会执行所有注入的listener,然后React中的listener会获取最新的状态快照并发送更新调度进而实现组件的重现渲染。

题外话

什么是React的hydration阶段?

这个阶段主要指的是当页面使用服务器渲染(SSR)有服务端生成HTML并传回给客户端(浏览器端)显示时,由于SSR传递到客户端是静态的HTML文件,不能进行用户交互(没有事件绑定等),所以React的hydration阶段就是将静态页面转换为可交付的页面的过程。

流程介绍:

  1. 服务端渲染 (SSR):
    ○ 服务器生成 HTML 内容并将其发送到客户端。这一步骤确保页面在浏览器中快速呈现,即使在 JavaScript 尚未完全加载和执行之前,用户也可以看到页面内容。
    ○ 生成的 HTML 包含了所有需要的内容,但这些内容是静态的,无法进行任何交互操作。
  2. 客户端接收 HTML:
    ○ 浏览器接收到服务器发送的 HTML 并立即显示给用户。这时用户看到的是一个完整但静态的页面。
    ○ 由于 HTML 是静态的,用户暂时无法进行诸如点击按钮、填写表单等交互操作。
  3. 加载和执行 JavaScript:
    ○ 浏览器开始加载和执行页面中的 JavaScript 文件。React 的 JavaScript 代码被下载并执行。
    ○ 在这一步,React 会初始化并尝试将组件树与已经存在的 HTML 进行对比和合并。
  4. Hydration 过程:
    ○ React 使用 ReactDOM.hydrate 方法,将客户端的组件树与服务端渲染的 HTML 进行对比。
    ○ 这个过程包括将事件监听器绑定到已有的 DOM 元素上,使得这些元素变得可交互。
  5. 页面变为可交互:
    ○ 一旦 hydration 过程完成,页面就变成了一个完整的、可交互的 React 应用。此时,用户可以正常进行各种交互操作(如点击按钮、输入文本等)。

举例说明

假设有一个简单的计数器应用,服务端渲染了初始计数值为 0 的 HTML:

<div id="root"><div><p>0</p><button>Increment</button></div>
</div>

在客户端,React 会执行以下操作:

  1. React 组件初始化: React 使用相同的组件树进行初始化:
function Counter() {const [count, setCount] = useState(0);return (<div><p>{count}</p><button onClick={() => setCount(count + 1)}>Increment</button></div>);
}
  1. Hydration 过程: React 将组件树与服务端的 HTML 进行对比,确保二者一致:
ReactDOM.hydrate(<Counter />, document.getElementById('root'));
  1. 绑定事件监听器: React 将 button 元素的点击事件绑定到 Increment 按钮上:
<button onClick={() => setCount(count + 1)}>Increment</button>
  1. 应用变为可交互: 现在,用户点击按钮时,计数值会增加,页面内容会更新。

总结:通过SSR传递到客户端的HTML虽然包含所有的内容,立即显示在页面,但是没有绑定事件无法交互,只是单纯的静态页面,需要执行Js文件经过hydration将绑定事件转换为可交互。而hydration 是 React 在客户端将服务端生成的静态 HTML 转变为可交互应用的过程。这个过程确保了初始页面的快速加载,同时通过绑定事件监听器和合并状态,使得页面能够响应用户的交互。

相关文章:

【React Hooks原理 - useSyncExternalStore】

概述 在React项目中说到状态管理&#xff0c;我们第一时间想到的就是使用useState、useReducer这种Hooks来进行状态管理。但是这种是针对React内部的状态&#xff0c;如果有时候我们需要订阅外部的状态并影响React组件的更新的话&#xff0c;那通过这种内部状态管理API显然不能…...

C++STL初阶(7):list的运用与初步了解

在了解了vector之后&#xff0c;我们只需要简单学习List与vector不一样的接口即可 1.list的基本接口 1.1 iterator list中&#xff0c;与vector最大的区别就是迭代器由随机迭代器变成双向迭代器 string和vector中的迭代器都是随机迭代器&#xff0c;支持-等&#xff0c;而LIS…...

el-menu弹出菜单样式不生效

1. 使用 ruoyi 项目时出现的问题。 <template><el-menu:default-active"activeMenu":collapse"false":unique-opened"true"class"container":collapse-transition"true"mode"horizontal"><sideba…...

Springboot 3.x - Reactive programming (2)

三、WebFlux Blocking Web vs. Reactive Web Blocking Web (Servlet) and Reactive Web (WebFlux) have significant differences in several aspects. 1. Front Controller Servlet-Blocking Web: Uses DispatcherServlet as the front controller to handle all HTTP req…...

WPF+Mvvm 项目入门完整教程(一)

WPF+Mvvm 入门完整教程一 创建项目MvvmLight框架安装完善整个项目的目录结构创建自定义的字体资源下载更新和使用字体资源创建项目 打开VS2022,点击创建新项目,选择**WPF应用(.NET Framework)** 创建一个名称为 CommonProject_DeskTop 的项目,如下图所示:MvvmLight框架安装…...

[解决方法]git上传的项目markdown文件的图片无法显示

应该有不少初学者会遇到这种情况 以下是本人摸索出的解决方法 我使用的是typora&#xff0c;首先设置typora的图片设置 文件>偏好设置>图像 如下&#xff1a; 选择这个就会在此文件的同级目录下创建一个assets文件夹来存放此markdown文件的所有图片 然后勾选优先使用相…...

【React】使用 antd 加载组件实现 iframe 的加载效果

文章目录 代码实现&#xff1a; import { Spin } from antd; import { useState } from react;export default function () {const [loading, setLoading] useState(true);return (<div style{{ position: relative, height: 100% }}><Spinstyle{{ position: absolu…...

Python爬虫(1) --基础知识

爬虫 爬虫是什么&#xff1f; spider 是一种模仿浏览器上网过程的一种程序&#xff0c;可以获取一些网页的数据 基础知识 URL 统一资源定位符 uniform resource locator http: 超文本传输协议 HyperText Transfer Protocol 默认端口 80 https: 安全的超文本传输协议 security…...

云原生系列 - Jenkins

Jenkins Jenkins&#xff0c;原名 Hudson&#xff0c;2011 年改为现在的名字。它是一个开源的实现持续集成的软件工具。 官方网站&#xff08;英文&#xff09;&#xff1a;https://www.jenkins.io/ 官方网站&#xff08;中文&#xff09;&#xff1a;https://www.jenkins.io…...

django踩坑(四):终端输入脚本可正常执行,而加入crontab中无任何输出

使用crontab执行python脚本时&#xff0c;有时会遇到脚本无法执行的问题。这是因为crontab在执行任务时使用的环境变量与我们在终端中使用的环境变量不同。具体来说&#xff0c;crontab使用的环境变量是非交互式(non-interactive)环境变量&#xff0c;而终端则使用交互式(inter…...

计算机网络入门 -- 常用网络协议

计算机网络入门 – 常用网络协议 1.分类 1.1 模型回顾 计算机网络细分可以划为七层模型&#xff0c;分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而上三层可以划为应用层中。 1.2 分类 1.2.1 应用层 为用户的应用进程提供网络通信服务&#xff0…...

【LabVIEW作业篇 - 4】:属性节点赋值和直接节点赋值的区别体现

文章目录 属性节点赋值和直接节点赋值的区别体现 属性节点赋值和直接节点赋值的区别体现 创建5个圆形指示灯&#xff0c;然后循环点亮&#xff0c;先给圆形指示灯赋值假变量&#xff0c;然后再进行循环。 运行结果&#xff0c;观察结果&#xff0c;发现刚开始运行时&#xff0…...

【数据库系列】Parquet 文件介绍

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

A Survey on Multimodal Large Language Models综述

论文题目:A Survey on Multimodal Large Language Models 论文地址:https://arxiv.org/pdf/2306.13549 话题:多模态LLMs综述 MLLMs Paper: https://github.com/BradyFU/Awesome-Multimodal-Large-Language-Models 1. 摘要 近期,以GPT-4V为代表的跨模态大型语言模型(MLLM…...

Leetcode3208. 交替组 II

Every day a Leetcode 题目来源&#xff1a;3208. 交替组 II 解法1&#xff1a;环形数组 把数组复制一份拼接起来&#xff0c;和 3101 题一样&#xff0c;遍历数组的同时&#xff0c;维护以 i 为右端点的交替子数组的长度 cnt。 如果 i ≥ n 且 cnt ≥ k&#xff0c;那么 i…...

汇编教程2

本教程主要教大家如何安装32位Linux虚拟机&#xff0c;为后续实验拆炸弹做准备 下载系统映像文件 以Ubuntu14.04.6系统为例 官方网站&#xff1a;下载地址 点击下载图中32位系统 如果官网进不去可以使用镜像网站 清华镜像网站&#xff1a;下载地址 进入之后找到下图中链接…...

使用 git 和 GitHub 互动

本文根据《GitHub入门与实践》整理 创建账户 要想使用GitHub那就必须先有GitHub账号,账号自行注册,不作介绍。 本地生成 SSH Key SSH 提供了一种安全的方式来通过不安全的网络进行通信。当你使用SSH key连接到GitHub时,你的身份是通过密钥对(一个公钥和一个私钥)来验…...

【Spring Boot 中的 `banner.txt` 和 `logback-spring.xml` 配置】

文章目录 一、banner.txt1. 创建自定义 banner.txt2. 配置 banner.txt 的内容 二、logback-spring.xml1. 创建 logback-spring.xml2. 配置 logback-spring.xml 一、banner.txt banner.txt 是 Spring Boot 项目启动时显示的自定义横幅内容。用来展示项目名称、版本信息或者其他…...

Python Linux环境(Centos8)安装minicoda3+jupyterlab

文章目录 安装miniconda安装python环境启动 最近服务器检查&#xff0c;我下面的服务器有漏洞&#xff0c;不得已重装了&#xff0c;正好记录下怎么从零到python写代码。 安装miniconda miniconda是anconda的精简版&#xff0c;就是管理python环境的得力助手。 # 创建一个名…...

Python PDF Magic:合并和拆分随心所欲

大家好&#xff01;小编今天要为大家带来一篇关于Python操作PDF的秘籍——无论是要将PDF合并成一份整体&#xff0c;还是将一个庞大的PDF文件拆分成多个小伙伴&#xff0c;都轻松hold住&#xff01;你准备好了吗&#xff1f;让我们开始这场奇妙的PDF操作之旅吧&#xff01; 准…...

Gmsh应用程序编程接口

Gmsh应用程序编程接口&#xff08;API&#xff09;允许将Gmsh库集成到使用C、C、Python、Julia或Fortran编写的外部应用程序中。从设计上讲&#xff0c;Gmsh API是纯粹功能性的&#xff0c;并且仅使用目标语言的基本类型。 API的结构反映了底层的Gmsh数据模型&#xff08;也请参…...

DP 203 学习笔记

考试内容总览 Learning Objects: 工具 Designing and implementing data storage 1. Storage Azure Synapse Analytics Azure Databricks Azure Data Lake Storage Gen2(ADLS2&#xff0c;可代替Hadoop Distributed File System也就是HDFS) 2. Shard Partition data store …...

SQLite 事务

SQLite 事务 SQLite 是一种轻量级的数据库管理系统,广泛用于各种应用程序中,特别是在移动设备和嵌入式系统中。它支持标准的 SQL 语法,包括事务处理。事务是数据库管理系统中的一个重要概念,它允许将一系列操作作为一个单独的工作单元来处理,以确保数据库的一致性和可靠性…...

LabVIEW和Alicat Scientific质量流量计实现精确流量控制

在现代工业自动化和科研实验中&#xff0c;精确的气体流量控制至关重要。这里将介绍一个使用LabVIEW与Alicat Scientific公司的质量流量计实现流量控制的项目。项目采用Alicat Scientific的质量流量计&#xff08;型号&#xff1a;M-200SCCM-D&#xff09;&#xff0c;通过LabV…...

2024-07-19 Unity插件 Odin Inspector10 —— Misc Attributes

文章目录 1 说明2 其他特性2.1 CustomContextMenu2.2 DisableContextMenu2.3 DrawWithUnity2.4 HideDuplicateReferenceBox2.5 Indent2.6 InfoBox2.7 InlineProperty2.8 LabelText2.9 LabelWidth2.10 OnCollectionChanged2.11 OnInspectorDispose2.12 OnInspectorGUI2.13 OnIns…...

Go操作Redis详解

文章目录 Go操作Redis详解来源介绍Redis支持的数据结构Redis应用场景Redis与Memcached比较准备Redis环境go-redis库 安装连接普通连接连接Redis哨兵模式连接Redis集群基本使用set/get示例zset示例Pipeline事务WatchGo操作Redis详解 来源 https://www.liwenzhou.com/posts/Go/…...

钡铼Modbus TCP耦合器BL200实现现场设备与SCADA无缝对接

前言 深圳钡铼技术推出的Modbus TCP耦合器为SCADA系统与现场设备之间的连接提供了强大而灵活的解决方案&#xff0c;它不仅简化了设备接入的过程&#xff0c;还提升了数据传输的效率和可靠性&#xff0c;是工业自动化项目中不可或缺的关键设备。本文将从Modbus TC、SCADA的简要…...

数据分析入门:用Python和Numpy探索音乐流行趋势

一、引言 音乐是文化的重要组成部分&#xff0c;而音乐流行趋势则反映了社会文化的变迁和人们审美的变化。通过分析音乐榜单&#xff0c;我们可以了解哪些歌曲或歌手正在受到大众的欢迎&#xff0c;甚至预测未来的流行趋势。Python作为一种强大的编程语言&#xff0c;结合其丰…...

数仓工具—Hive语法之替换函数和示例

Hive 替换函数和示例 默认情况下,并没有可用的 Hive 替换函数。如果在处理字符串时需要替换特定值,例如垃圾值,字符串操作函数替换是非常需要的。在本文中,我们将检查 Hive 替换函数 的替代方法,以便在需要时使用。 如前所述,Apache Hive 不提供替换函数的支持。但是,…...

[SUCTF 2019]EasySQL1

这是一个简单的SQL注入题&#xff0c;但是因为我的SQL基础约等于0&#xff0c;所以做起来很难。 首先试试引号是否被过滤 可以看到单引号、双引号都被过滤了&#xff0c;试试其他的盲注都不行&#xff0c;基本上可以确定不能用这种方法。 在测试的过程中发现&#xff0c;输入…...

网站开发和网页设计的区别/百度一下 你就知道首页

自八月初立秋以来&#xff0c;很多地区出现了阴雨绵绵的天气&#xff0c;开车出行面临很多安全隐患。今天名悦集团来给大家聊聊秋雨季节开车有哪些安全隐患&#xff0c;出行有哪些注意事项&#xff0c;希望能帮到您。 挡风玻璃起雾&#xff0c;开暖风更严重 秋季阴雨天气开车…...

access 网站数据库/seo分析工具有哪些

其实每个设计模式都是很重要的一种思想&#xff0c;看上去很熟&#xff0c;其实是因为我们在学到的东西中都有涉及&#xff0c;尽管有时我们并不知道&#xff0c;其实在Java本身的设计之中处处都有体现&#xff0c;像AWT、JDBC、集合类、IO管道或者是Web框架&#xff0c;里面设…...

那个网站可以看高速的建设情况/百度收录是什么意思

Docker环境下DB2的安装与图形化管理 docker是一个一款很轻便的应用容器引擎&#xff0c;为了更好的管理和使用docker&#xff0c;使用web可视化管理工具似乎更符合大多数人的需求。在这里&#xff0c;给大家分享下自己使用过的几款web工具&#xff1a;docker UI、shipyard、po…...

网上学设计哪个网站好/网页开发工具

点击上方蓝字关注我们在射频 /微波/毫米波测试中&#xff0c;除了同轴和波导互连器件之外&#xff0c;还有其他两种与DUT交互的主要器件。它们包括连接到元件引线或微带焊盘的表面探针&#xff0c;以及设计用于测试的天线。测试探针和天线所适用的测试类型差异巨大&#xff0c;…...

福州专业网站建设网络公司/百度应用商店官网

在说LINQ之前必须先说说几个重要的C#语言特性 一&#xff1a;与LINQ有关的语言特性1.隐式类型&#xff08;1&#xff09;源起在隐式类型出现之前,我们在声明一个变量的时候,总是要为一个变量指定他的类型甚至在foreach一个集合的时候&#xff0c;也要为遍历的集合的元素&#x…...

免费一键生成logo网站/模板建站平台

1.昨天&#xff0c;已经将官方驱动程序更新至1.3了&#xff0c;在最新的源代码的下载版本中应该已经包含了最新版本。2.随着1.3驱动的更新&#xff0c;索引系统有了新的更改&#xff0c;最新的索引系统增加了4个索引属性。配合新的索引系统&#xff0c;索引管理器的窗体进行了重…...