为什么hooks不能在循环、条件或嵌套函数中调用

news2024/11/16 15:25:57

hooks不能在循环、条件或嵌套函数中调用

在这里插入图片描述

为什么?

带着疑问一起去看源码吧~

function App() {
  const [num, setNum] = useState(0);
	const [count, setCount] = useState(0);

  const handleClick = () => {
    setNum(num => num + 1)
    setCount(2)
  }
  
  return <p onClick={() => handleClick()}>
      {num}
      {count}
  </p>;
}

Fiber对象

想和大家一起回顾一下Fiber
React从V16开始就支持了hooks,引入了Fiber架构。
在之前的版本function 组件不能做继承,因为 function 本来就没这个特性,所以是提供了一些 api 供函数使用,这些 api 会在内部的一个数据结构上挂载一些函数和值,并执行相应的逻辑,通过这种方式实现了 state 和类似 class 组件的生命周期函数的功能,这种 api 就叫做 hooks,而hooks挂载数据的数据结构就是Fiber

classComponent,FunctionalComponent都会将节点信息存储在FIber对象中

{
  type: any, // 对于类组件,它指向构造函数;对于DOM元素,它指定HTML tag
  key: null | string, // 唯一标识符
  stateNode: any, // 保存对组件的类实例,DOM节点或与fiber节点关联的其他React元素类型的引用
  child: Fiber | null, // 大儿子
  sibling: Fiber | null, // 下一个兄弟
  return: Fiber | null, // 父节点
  tag: WorkTag, // 定义fiber操作的类型, 详见https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactWorkTags.js
  nextEffect: Fiber | null, // 指向下一个节点的指针
  alternate: Fiber | null,
  updateQueue: mixed, // 用于状态更新,回调函数,DOM更新的队列
    
  memoizedState: any, // 用于创建输出的fiber状态,记录内部state对象的属性
    
  pendingProps: any, // 已从React元素中的新数据更新,并且需要应用于子组件或DOM元素的props
  memoizedProps: any, // 在前一次渲染期间用于创建输出的props
  // ……   

}
memorizedState:单向链表

Fiber 对象的上有一个记录内部 State 对象的属性,以便让我们能在下次渲染的时候取到上一次的值,叫做 memoizedState
memorizedState将组件中的hooks依次链接在一起

即使知道他是链表,还是不知道为什么不能在条件里使用?

// 数据结构示例
fiber = {
    // fiber 的 memorizedState 用于存储此函数组件的所有 hooks
    // 在链表的 hooks 实现中就是指向第一个 useXxx 生成的 hook;数组实现中就是一个数组,第一个 hook 存储在索引0中。
    memorizedState: hook1 {  // 第一个 useXxx 生成的 hook
        // useXxx 的数据
        memorizedState: data,
        // next 是个指针,指向下一个 useXxx 生成的 hook
        next: hook2 {
            // hook2 的数据
            memorizedState: data,
            // next 指向第三个 hook
            next: hook3
        }
    }
}
updateQueue:单向链表[Effect类型对象]

是 Update 的队列,同时还带有更新的 dispatch。

const effect: Effect = {
    tag,
    create,
    destroy,
    deps,
    // Circular
    next: (null: any),
  };

回顾完Fiber数据结构后,要开始进入正题啦

useState

源码部分
● currentlyRenderingFiber:指当前渲染组件的 Fiber 对象,在我们的例子中,就是 App 对应的 Fiber 对象
● workInProgressHook:指当前运行到哪个 hooks 了,我们一个组件内部可以有多个 hook,而当前运行的 hook 只有一个。
● currentFiber: 旧的Fiber节点
● workInProgress: 当前正在工作的Fiber节点
● hook 节点:我们每一个 useState 语句,在初始化的时候,都会产生一个对象,来记录它的状态,我们称它为 hook 节点。

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}


function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;

  return ((dispatcher: any): Dispatcher);
}


export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
	// ……省略
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;
	// ……省略
}

从这里可以看出,我们的useState调用的函数分两种情况,mount和update,那么我们就分两个阶段来看源码
在这里插入图片描述

1.mount阶段

首次渲染

const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useInsertionEffect: mountInsertionEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useSyncExternalStore: mountSyncExternalStore,
  useId: mountId,
};
mountState
  1. 创建 hook 对象,并将该 hook 对象加到 hook 链的末尾
  2. 初始化 hook 对象的状态值,也就是我们传进来的 initState 的值。
  3. 创建更新队列,这个队列是更新状态值的时候用的,会保存所有的更新行为。
  4. 绑定 dispatchSetState 函数
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    // $FlowFixMe: Flow doesn't like mixed types
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  // 声明一个链表来存放更新
  // 用于多个 setState 的时候记录每次更新的。
  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;

  // 返回一个dispatch方法用来修改状态,并将此次更新添加update链表中
  const dispatch: Dispatch<BasicStateAction<S>> = 
  (queue.dispatch =
    (dispatchSetState.bind(
      null, 
      currentlyRenderingFiber, 
      queue): any)
  );
  // 返回当前状态和修改状态的方法 
  return [hook.memoizedState, dispatch];
}
1. mountWorkInProgressHook

会初始化创建一个 Hook,然后将其挂载到 workInProgress fiber 的 memoizedState 所指向的 hooks 链表上,以便于下次 update 的时候取出该 Hook:

  1. 创建一个hook节点
  2. 判断是否当前工作的hook节点workInProgressHook,没有的话,workInProgressHook = hook
  3. 有的话,workInProgressHook.next = hook, workInProgressHook = workInProgressHook.next
  4. 反正就是指针指向当前这个hook
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null, // 每次更新完会赋值上一个 update,方便 React 在渲染错误的边缘,数据回溯。
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // 当前workInProgressHook链表为空的话,
    // 将当前Hook作为第一个Hook
    // This is the first hook in the list
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    // 否则将当前Hook添加到Hook链表的末尾
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

每一个hook语句对应一个hook节点

2. mountWorkI15868169523dispatchSetState

useState 执行 setState 后会调用 dispatchSetState

  1. 创建update对象
  2. 将所有的 update 对象串成了一个环形链表,将update赋值给queue的pending属性上
function dispatchSetState<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
): void {

  const lane = requestUpdateLane(fiber);

  // 创建更新对象
  const update: Update<S, A> = {
    lane,
    action, // 值
    hasEagerState: false,
    eagerState: null,
    next: (null: any), // 
  };

  if (isRenderPhaseUpdate(fiber)) { // fiber调度范畴
    enqueueRenderPhaseUpdate(queue, update); // 缓存更新
  } else {
    const alternate = fiber.alternate;
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {

      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
       
        try {
          const currentState: S = (queue.lastRenderedState: any);
          const eagerState = lastRenderedReducer(currentState, action);
        
          update.hasEagerState = true;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
           
            enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
            return;
          }
        } catch (error) {
          // Suppress the error. It will throw again in the render phase.
        }
      }
    }

    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      const eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
  }

  markUpdateInDevTools(fiber, lane, action);
}

function enqueueRenderPhaseUpdate<S, A>(
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
): void {
  didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate =
    true;
  const pending = queue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}
环形链表

初始的 update 对象,用来记录相关的 hook 信息,并将它添加到 queue 中,这里的 queue 的添加你可以发现它形成了一个循环链表,这样 pending 作为链表的一个尾结点,而 pending.next 就能够获取链表的头结点。这样做的目的是,在 setCount 时,我们需要将 update 添加到链表的尾部;而在下面的 updateReducer 中,我们需要获取链表的头结点来遍历链表,通过循环链表能够轻松实现我们的需求。

2. update阶段

updateState
function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}
updateReducer

updateState 做的事情,实际上就是拿到更新队列,循环队列,并根据每一个 update 对象对当前 hook 进行状态更新,返回最终的结果



function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;

  queue.lastRenderedReducer = reducer; // 获取最近一次的reducer函数

  const current: Hook = (currentHook: any);

  let baseQueue = current.baseQueue; // 目前存在的state的更新链表

  const pendingQueue = queue.pending; // 本次hook的state更新链表

  // 把pendingQueue合并到baseQueue上
  if (pendingQueue !== null) 
    if (baseQueue !== null) {
      // 如果 baseQueue 和 pendingQueue 都存在,将 pendingQueue 链接到 baseQueue 尾部
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }

    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }

// 下次渲染执行到 updateState 阶段会取出 hook.queue,根据优先级确定最终的 state,最后返回来渲染。
  if (baseQueue !== null) {
    // We have a queue to process.
    const first = baseQueue.next;
    let newState = current.baseState;
		// 如果当前的 update 优先级低于 render 优先级,下次 render 时再执行本次的 update

    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast: Update<S, A> | null = null;
    let update = first;
    do {
      const updateLane = removeLanes(update.lane, OffscreenLane);
      const isHiddenUpdate = updateLane !== update.lane;

      const shouldSkipUpdate = isHiddenUpdate
        ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
        : !isSubsetOfLanes(renderLanes, updateLane);


      // 如果当前的 update 优先级低于 render 优先级,下次 render 时再执行本次的 update
      if (shouldSkipUpdate) {
        // Priority is insufficient. Skip this update. If this is the first
        // skipped update, the previous update/state is the new base
        // update/state.
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: (null: any),
        };
        if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }

        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {


        if (newBaseQueueLast !== null) {
          // newBaseQueueLast 不为 null,说明此前有跳过的 update
          // update 之间可能存在依赖,将后续 update 都连接到 newBaseQueue 中留到下次 render 执行
          const clone: Update<S, A> = {
            lane: NoLane,
            action: update.action,
            hasEagerState: update.hasEagerState,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }


        const action = update.action;

        if (update.hasEagerState) {
          newState = ((update.eagerState: any): S);
        } else {
          // 根据 state 和 action 计算新的 state
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null && update !== first);


    if (newBaseQueueLast === null) {
      // newBaseQueueLast 为 null,说明所有 update 处理完了,更新 baseState
      newBaseState = newState;
    } else {
       // 未处理完留到下次执行
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }

		// 如果新的 state 和之前的 state 不相等,标记需要更新
    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }

    // 将新的 state 和 baseQueue 保存到 hook 中
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;


    queue.lastRenderedState = newState;
  }


  if (baseQueue === null) {
    queue.lanes = NoLanes;
  }


  const dispatch: Dispatch<A> = (queue.dispatch: any);
	// 再次渲染的时候执行,会取出 hook.queue,根据优先级确定最终的 state 返回
  return [hook.memoizedState, dispatch];
}
1. updateWorkInProgressHook

当 react 重新渲染时,会生成一个新的 fiber 树,而这里会根据之前已经生成的 FiberNode ,拿到之前的 hook ,再复制一份到新的 FiberNode 上,生成一个新的 hooks 链表。
而这个 hook 是怎么拿的?是去遍历 hooks 链表拿的,所以每次都会按顺序拿下一个 hook ,然后复制到新的 FiberNode 上。可以理解为这个 updateWorkInProgressHook 每次都会按顺序返回下一个 hook 。

nextCurrentHook,nextWorkInProgressHook两个hook对象分别对应的是oldFiber和当前workFiber

function updateWorkInProgressHook(): Hook {
  
  let nextCurrentHook: null | Hook;
  // currentHook: 已经生成的 fiber 树上的 hook,第一次是空
  if (currentHook === null) {
    // currentlyRenderingFiber$1: 正在生成的 FiberNode 结点, alternate 上挂载的是上一次已经生成完的 fiber 结点
    // 所以 current 就是上次生成的 FiberNode
    const current = currentlyRenderingFiber.alternate;
    //  memoizedState 是当前Fiber节点的hooks的链表信息
    // 我们之前说过 hooks 挂在 FiberNode 的 memoizedState 上,这里拿到第一个 hook
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
  // 不是第一次,则证明已经拿到了 hook,我们只需要用 next 就能找到下一个 hook
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook;

  // workInProgressHook 当前运行到那个hook
  // workInProgressHook: 正在生成的 FiberNode 结点上的 hook,第一次为空
  if (workInProgressHook === null) {
    // currentlyRenderingFiber$1 是当前正在生成的 FiberNode
    // 所以这里 nextWorkInProgressHook 的值就是当前正在遍历的 hook,第一次让它等于 memoizedState
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
  // 不是第一次,始终让它指向下一个 hook,如果这是最后一个,那么 nextWorkInProgressHook 就会是 null
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    // rerender场景下会走到这个逻辑,
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;
    currentHook = nextCurrentHook;
  } else {
    // Clone from the current hook.
    // 不存在的话会根据上一次的 hook 克隆一个新的 hook,挂在新的链表、FiberNode上。
    if (nextCurrentHook === null) {
      const currentFiber = currentlyRenderingFiber.alternate;
      if (currentFiber === null) {
        // This is the initial render. This branch is reached when the component
        // suspends, resumes, then renders an additional hook.
        const newHook: Hook = {
          memoizedState: null,
          baseState: null,
          baseQueue: null,
          queue: null,

          next: null,
        };
        nextCurrentHook = newHook;
      } else {
        // This is an update. We should always have a current hook.
        throw new Error('Rendered more hooks than during the previous render.');
      }
    }

    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null,
    };

    if (workInProgressHook === null) {
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}

workInProgressHook的伪代码

// 指向 hook 的指针
let workInProgressHook = null;
if (isMount) {
    // useState, useEffect, useRef 这些 hooks 都是创建一个 hook 对象,然后用 memorizedState 存储 hook 的数据
    hook = {
        memorizedState: initState,  // 当前 hook 数据
        next: null,  // 指向下一个 hook 的指针
    }
    if (!fiber.memorizedState) {
        fiber.memorizedState = hook;  // 不存在则是第一调用 useXxx,将 fiber.memorizedState 指向这第一个 hook
    } else {
        // fiber.memorizedState 存在则是多次调用 useXxx,将上个 hook 的 next 指向当前 hook
        workInProgressHook.next = hook;
    }
    workInProgressHook = hook;  // 存储当前 hook 用于下次使用
} else {
    // workInProgressHook 是从第一个 hook 开始的,因为更新是通过 scheduler 来更新的,
    // 而 scheduler 中对 workInProgressHook 进行了复位操作,即 workInProgressHook = fiber.memorizedState
  	// update 阶段,每个 useXxx 被调用的时候都会走 else 逻辑
    hook = workInProgressHook;
    // workInProgressHook 指向下一个 hook
    workInProgressHook = hook.next;
}

useState 的 mountState 阶段返回的 setData是绑定了几个参数的 dispatch 函数。执行它会创建 hook.queue 记录更新,然后标记从当前到根节点的 fiber 的 lanes 和 childLanes 需要更新,然后调度下次渲染。
下次渲染执行到 updateState 阶段会取出 hook.queue,根据优先级确定最终的 state,最后返回来渲染。

最后用一哈别的大佬画的图~
在这里插入图片描述

为什么?

看到这里你就应该明白为什么 hooks 只能在顶层使用了。核心在于updateWorkInProgressHook这个函数。
因为它会按顺序去拿hook,react也是按顺序来区分不同的 hook 的,它默认你不会修改这个顺序。如果你没有在顶层使用 hook ,打乱了每次 hook 调用的顺序,就会导致 react 无法区分出对应的 hook ,进而导致错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/509771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML + CSS + JavaScript【实战案例】 实现动画导航栏效果

​Hello~ 咱们今天一起来学习一个动画导航的小项目 Part 1 HTML结构 <body><nav class"active" id"nav"><ul><li><a href"#">Home</a></li><li><a href"#">Works</a>&…

MySQL---多表联合查询(上)(多表关系、外键约束、学生成绩多表关系、交叉连接查询)

1. 多表关系 MySQL多表之间的关系可以概括为&#xff1a; 一对一&#xff1a; 比如&#xff1a;一个学生只有一张身份证&#xff1b;一张身份证只能对应一学生。 实现原则&#xff1a;在任一表中添加唯一外键&#xff0c;指向另一方主键&#xff0c;确保一对一关系。 一般一对…

JumpServer Harbor CCE ELK

Jumpserver是一款开源的堡垒机&#xff0c;可使系统的管理员和开发人员安全的连接到企业内部服务器上执行操作&#xff0c;并且支持大部分操作系统&#xff0c;是一款非常安全的远程连接工具 安装JumpServer jumpserver.org官网去下载安装&#xff0c;有一键安装&#xff08;里…

克服田间果园环境下非结构化背景挑战的果实检测优化策略

文章目录 摘要复杂的背景因素和消极影响照明条件水果遮挡现象不同成熟度的水果 参考 摘要 由于世界粮食和环境危机的持续影响&#xff0c;对智能农业的需求正在增加。以水果检测为重点&#xff0c;随着目标检测技术的快速发展&#xff0c;现在可以在水果检测系统中实现高精度。…

OpenGL(十三)——世界光照

目录 一、前言 二、平行光 2.1 片段着色器 2.2 app渲染 三、点光源 3.1 距离衰减 3.2 衰减片段着色器 四、聚光 4.1 片段着色器 4.2 光照入射方向 4.3 平滑边缘 一、前言 Light Caster &#xff1a;光投射&#xff08;Cast&#xff09;到物体的光源。现实世界中通常多…

面对复杂的系统与众多的插件,如何确保Jenkins项目的安全性?

CloudBees在Jenkins/CBCI生态系统上建立了一个专门的安全团队。关于该团队的公开信息可以在从此链接中找到&#xff1a;https://www.jenkins.io/security/。由于所涉及的系统复杂且插件数量众多&#xff08;见下文&#xff09;&#xff0c;许多扫描提供的信息缺少有价值的上下文…

1015. 可被 K 整除的最小整数(leetcode)取模技巧题-------------------c++实现

1015. 可被 K 整除的最小整数&#xff08;leetcode&#xff09;取模技巧题-------------------c实现 题目表述 给定正整数 k &#xff0c;你需要找出可以被 k 整除的、仅包含数字 1 的最 小 正整数 n 的长度。 返回 n 的长度。如果不存在这样的 n &#xff0c;就返回-1。 注…

看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动EPIT定时器中断《按键消抖》) 2023.5.10

前言 首先我们在使用开发板进行开发时&#xff0c;自然而然会使用到定时器这个外设&#xff0c;因为我们需要它来完成精准的定时功能&#xff0c;但是说到精准&#xff0c;我会在下一篇文章中使用其他的定时器来完成这个功能即GPT定时器。在本文章中我们会利用定时器中断来解决…

LeetCode2. 两数相加

写在前面&#xff1a; 题目链接&#xff1a;LeetCode2两数相加 编程语言&#xff1a;C 题目难度&#xff1a;中等 一、题目描述 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 …

光学液氮恒温器T9120-4W的技术参数

液氮型低温恒温器&#xff0c;利用液氮作为降温媒介&#xff0c;标准恒温器可实现快速降温至液氮温度&#xff08;约20min&#xff09;&#xff0c;其工作原理是在恒温器内部液氮腔内装入液氮&#xff0c;通过调整控温塞与冷指的间隙来保持冷指的漏热稳定在一定值上&#xff0c…

lua实战(1)

目录 IDELua中的名称Lua是一种区分大小写的语言 Lua 是一个小巧的脚本语言。它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开…

vue3+electron开发桌面软件(9)——选中多个文件,右键上传

系列文章目录 系列第一篇&#xff1a; vue3electron开发桌面软件入门与实战&#xff08;0&#xff09;——创建electron应用 文章目录 系列文章目录前言一、我们如何思考二、解决问题1.选择方案2. 发现electron多开窗口监听3.查找可使用的官方参数4.示例代码 总结 前言 从本系…

Docker 安全及日志管理

Docker 安全及日志管理 Docker 容器与虚拟机的区别隔离与共享性能与损耗 Docker 存在的安全问题Docker 自身漏洞Docker 源码问题Docker 架构缺陷与安全机制Docker 安全基线标准 容器相关的常用安全配置方法容器最小化Docker 远程 API 访问控制重启 Docker在宿主机的 firewalld …

ASEMI代理ADI亚德诺ADXL345BCCZ-RL7车规级芯片

编辑-Z ADXL345BCCZ-RL7特点&#xff1a; 超低功率&#xff1a;在测量模式下低至23A 在VS2.5 V的待机模式下为0.1A&#xff08;典型&#xff09; 功耗会随带宽自动调整 用户可选分辨率 固定的10位分辨率 全分辨率&#xff0c;其中分辨率随着g范围的增加而增加&#xff0…

00后才是内卷之王,被卷的头皮发麻....

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;前段时间我们公司来了个00年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。…

Redis主从复制(搭建集群的一种方式)【故障转移,内存,回收】

做一个伪集群 配置文件&#xff1a; daemonize yes port 7777 logfile .redis-7777.log dir ./ bind 0.0.0.0启动6666 and 7777 现在设置主从表 但是有个问题我把服务器停掉 关系就会解除 还可以手动解除 slaveof no one 命令 配置Sentinel&#xff08;哨兵&#…

基于络达SOC AB1562A TWS蓝牙耳机设计

V hezkz17进数字音频答疑 一 原理框图 二 电子电路设计 (1)SOC主芯片 (2) 最小系统晶振电路设计26MHZ (3) 电池电路设计 4 充电电路与充电保护设计 5 LED输出电路设计</

hexo stellar设置目录跳转记录

1. 使用hexo-toc插件 一开始使用的是hexo-toc的插件&#xff1a;参考hexo安装toc插件 详细的可以看github的项目&#xff1a; github-hexo-toc 更加详细的配置&#xff1a; Hexo添加Toc支持&#xff0c;生成文章目录 2. 官网的方式&#xff08;推荐&#xff09; stellar博…

flink cdc原理与使用

flink cdc原理与使用 1 cdc 介绍1.1 cdc简介与对比1.2 基于日志的 CDC 方案介绍 2 基于 Flink SQL CDC 的数据同步方案实践2.1 案例 1 : Flink SQL CDC JDBC Connector2.2 案例 2 : CDC Streaming ETL2.3 案例 3 : Streaming Changes to Kafka 3 Flink SQL CDC 的更多应用场景…

Java EE企业级应用开发(SSM)第10章

第10章MyBatis核心配置及动态SQL 一.预习笔记 1.第九章的细节处理 1-1.mappers标签中的配置 1-2.jdbc属性文件的配置 1-3.包的别名配置 2.Mybatis核心配置文件 2-1&#xff1a;settings标签&#xff08;P145-146中的表10-1&#xff09; 2-2.类型别名 3.Mybatis映射文件 3-1…