为什么hooks不能在循环、条件或嵌套函数中调用
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 tagkey: 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.jsnextEffect: Fiber | null, // 指向下一个节点的指针alternate: Fiber | null,updateQueue: mixed, // 用于状态更新,回调函数,DOM更新的队列memoizedState: any, // 用于创建输出的fiber状态,记录内部state对象的属性pendingProps: any, // 已从React元素中的新数据更新,并且需要应用于子组件或DOM元素的propsmemoizedProps: 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 生成的 hooknext: hook2 {// hook2 的数据memorizedState: data,// next 指向第三个 hooknext: hook3}}
}
updateQueue:单向链表[Effect类型对象]
是 Update 的队列,同时还带有更新的 dispatch。
const effect: Effect = {tag,create,destroy,deps,// Circularnext: (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
- 创建 hook 对象,并将该 hook 对象加到 hook 链的末尾
- 初始化 hook 对象的状态值,也就是我们传进来的 initState 的值。
- 创建更新队列,这个队列是更新状态值的时候用的,会保存所有的更新行为。
- 绑定 dispatchSetState 函数
function mountState<S>(initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {const hook = mountWorkInProgressHook();if (typeof initialState === 'function') {// $FlowFixMe: Flow doesn't like mixed typesinitialState = 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:
- 创建一个hook节点
- 判断是否当前工作的hook节点workInProgressHook,没有的话,workInProgressHook = hook
- 有的话,workInProgressHook.next = hook, workInProgressHook = workInProgressHook.next
- 反正就是指针指向当前这个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 listcurrentlyRenderingFiber.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
- 创建update对象
- 将所有的 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 时再执行本次的 updatelet 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 时再执行本次的 updateif (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 计算新的 statenewState = reducer(newState, action);}}update = update.next;} while (update !== null && update !== first);if (newBaseQueueLast === null) {// newBaseQueueLast 为 null,说明所有 update 处理完了,更新 baseStatenewBaseState = 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 就是上次生成的 FiberNodeconst current = currentlyRenderingFiber.alternate;// memoizedState 是当前Fiber节点的hooks的链表信息// 我们之前说过 hooks 挂在 FiberNode 的 memoizedState 上,这里拿到第一个 hookif (current !== null) {nextCurrentHook = current.memoizedState;} else {nextCurrentHook = null;}} else {// 不是第一次,则证明已经拿到了 hook,我们只需要用 next 就能找到下一个 hooknextCurrentHook = currentHook.next;}let nextWorkInProgressHook: null | Hook;// workInProgressHook 当前运行到那个hook// workInProgressHook: 正在生成的 FiberNode 结点上的 hook,第一次为空if (workInProgressHook === null) {// currentlyRenderingFiber$1 是当前正在生成的 FiberNode// 所以这里 nextWorkInProgressHook 的值就是当前正在遍历的 hook,第一次让它等于 memoizedStatenextWorkInProgressHook = currentlyRenderingFiber.memoizedState;} else {// 不是第一次,始终让它指向下一个 hook,如果这是最后一个,那么 nextWorkInProgressHook 就会是 nullnextWorkInProgressHook = 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 指向当前 hookworkInProgressHook.next = hook;}workInProgressHook = hook; // 存储当前 hook 用于下次使用
} else {// workInProgressHook 是从第一个 hook 开始的,因为更新是通过 scheduler 来更新的,// 而 scheduler 中对 workInProgressHook 进行了复位操作,即 workInProgressHook = fiber.memorizedState// update 阶段,每个 useXxx 被调用的时候都会走 else 逻辑hook = workInProgressHook;// workInProgressHook 指向下一个 hookworkInProgressHook = hook.next;
}
useState 的 mountState 阶段返回的 setData是绑定了几个参数的 dispatch 函数。执行它会创建 hook.queue 记录更新,然后标记从当前到根节点的 fiber 的 lanes 和 childLanes 需要更新,然后调度下次渲染。
下次渲染执行到 updateState 阶段会取出 hook.queue,根据优先级确定最终的 state,最后返回来渲染。
最后用一哈别的大佬画的图~
为什么?
看到这里你就应该明白为什么 hooks 只能在顶层使用了。核心在于updateWorkInProgressHook这个函数。
因为它会按顺序去拿hook,react也是按顺序来区分不同的 hook 的,它默认你不会修改这个顺序。如果你没有在顶层使用 hook ,打乱了每次 hook 调用的顺序,就会导致 react 无法区分出对应的 hook ,进而导致错误。
相关文章:

为什么hooks不能在循环、条件或嵌套函数中调用
hooks不能在循环、条件或嵌套函数中调用 为什么? 带着疑问一起去看源码吧~ function App() {const [num, setNum] useState(0);const [count, setCount] useState(0);const handleClick () > {setNum(num > num 1)setCount(2)}return <p …...

互联网赚钱项目有哪些?目前最火的互联网项目
互联网是一个神奇的行业,大门不出二门不迈,一根网线一台电脑,甚至一台手机就可以赚钱。它给我们创造了前所未有的商业机会,让成千上万有梦想,敢想敢干的人通过互联网获得了巨大的成功!正因为如此࿰…...

队列、栈专题
队列、栈专题 LeetCode 20. 有效的括号解题思路代码实现 LeetCode 921. 使括号有效的最少添加解题思路代码实现 LeetCode 1541. 平衡括号字符串的最少插入次数解题思路代码实现 总结 不要纠结,干就完事了,熟练度很重要!!ÿ…...

TensorFlow vs PyTorch:哪一个更适合您的深度学习项目?
在深度学习领域中,TensorFlow 和 PyTorch 都是非常流行的框架。这两个框架都提供了用于开发神经网络模型的工具和库,但它们在设计和实现上有很大的差异。在本文中,我们将比较 TensorFlow 和 PyTorch,并讨论哪个框架更适合您的深度…...

大项目环境配置
目录 Linux的龙蜥8是什么? OpenGL是什么? 能讲讲qt是什么吗? 我可以把qt技术理解为c工程师的前端开发手段吗? 我其实一直有些不懂大家所说的这个开发框架啥的,这个该如何理解呢 那现在在我看来,框架意…...

Elasticsearch——》正则regexp
推荐链接: 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…...

五面阿里Java岗,从小公司到阿里的面经总结
面试 笔试常见的问题 面试常见的问题下面给的面试题基本都有。 1 手写代码:手写代码一般会考单例、排序、线程、消费者生产者 排序。 2 写SQL很常考察group by、内连接和外连接 2.面试1-5面总结 1)让你自我介绍 2)做两道算法…...

redis(7)
全局ID生成器: 全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足以下特性 唯一性高可用(随时访问随时生成)递增性安全性(不能具有规律性)高性能(生成ID的速度快) 为了增加ID的安全性,我们不会使用redis自增的数值&am…...

互联网从业者高频单词 300个
测试 (Test) 软件 (Software) 用例 (Test Case) 缺陷 (Defect) 提交 (Submit) 回归测试 (Regression Testing) 验收测试 (Acceptance Testing) 单元测试 (Unit Testing) 集成测试 (Integration Testing) 性能测试 (Performance Testing) 负载测试 (load Testing) 压…...

初始化vue中data中的数据
当组件的根元素使用了v-if的时候, 并不会初始化data中的数据 如果想完全销毁该组件并且初始化数据,需要在使用该组件的本身添加v-if 或者是手动初始化该组件中的数据 初始化化数据的一些方法 Object.assign(this.$data, this.$options.data()) this.$data:当前的da…...

神经网络的建立-TensorFlow2.x
要学习深度强化学习,就要学会使用神经网络,建立神经网络可以使用TensorFlow和pytorch,今天先学习以TensorFlow建立网络。 直接上代码 import tensorflow as tf# 定义神经网络模型 model tf.keras.models.Sequential([tf.keras.layers.Dense…...

python基于卷积神经网络实现自定义数据集训练与测试
注意: 如何更改图像尺寸在这篇文章中,修改完之后你就可以把你自己的数据集应用到网络。如果你的训练集与测试集也分别为30和5,并且样本类别也为3类,那么你只需要更改图像标签文件地址以及标签内容(如下面两图所示&…...

跟着LearnOpenGL学习3--四边形绘制
文章目录 一、前言二、元素缓冲对象三、完整代码四、绘制模式 一、前言 通过跟着LearnOpenGL学习2–三角形绘制一文,我们已经知道了怎么配置渲染管线,来绘制三角形; OpenGL主要处理三角形,当我们需要绘制别的图形时,…...

c#笔记-结构
装箱 结构是值类型。值类型不能继承其他类型,也不能被其他类型继承。 所以它的方法都是确定的,没有虚方法需要在运行时进行动态绑定。 值类型没有对象头,方法调用由编译器直接确定。 但是,如果使用引用类型变量(如接…...

Es分布式搜索引擎
目录 一、什么是ES? 二、什么是elk? 三、什么是倒排索引? 四、正向索引和倒排索引的优缺点对比 五、mysql数据库和es的区别? 六、索引库(es中的数据库表)操作有哪些? 八、ES分片存储原理 …...

open3d 裁剪点云
目录 1. crop_point_cloud 2. crop 3. crop_mesh 1. crop_point_cloud 关键函数 chair vol.crop_point_cloud(pcd) # vol: SelectionPolygonVolume import open3d as o3dif __name__ "__main__":# 1. read pcdprint("Load a ply point cloud, crop it…...

如何对第三方相同请求进行筛选过滤
文章目录 问题背景处理思路注意事项代码实现 问题背景 公司内部多个系统共用一套用户体系库,对外(钉钉)我们是两个客户身份(这里是根据系统来的),例如当第三方服务向我们发起用户同步请求:是一个更新用户操作,它会同时发送一个 d…...

Go RPC
目录 文章目录 Go RPCHTTP RPCTCP RPCJSON RPC Go RPC Go 标准包中已经提供了对 RPC 的支持,而且支持三个级别的 RPC:TCP、HTTP、JSONRPC。但 Go 的 RPC 包是独一无二的 RPC,它和传统的 RPC 系统不同,它只支持 Go 开发的服务器与…...

真正的智能不仅仅是一个技术问题
智能并不是单一的技术问题,而是一个包括技术、人类智慧、社会制度和文化等多个方面的综合体,常常涉及技术变革、系统演变、运行方式创新、组织适应。智能是指人类的思考、判断、决策和创造等高级认知能力,可以通过技术手段来实现增强和扩展。…...

【数据结构】复杂度包装泛型
目录 1.时间和空间复杂度 1.1时间复杂度 1.2空间复杂度 2.包装类 2.1基本数据类型和对应的包装类 2.2装箱和拆箱 //阿里巴巴面试题 3.泛型 3.1擦除机制 3.2泛型的上界 1.时间和空间复杂度 1.1时间复杂度 定义:一个算法所花费的时间与其语句的执行次数成…...

Ae:绘画面板
Ae菜单:窗口/绘画 Paint 快捷键:Ctrl 8 绘画工具(画笔工具、仿制图章工具及橡皮擦工具)仅能工作在图层面板上。在使用绘画工具之前,建议先在绘画 Paint面板中查看或进行相关设置。 说明: 如果要在绘画描边…...

常见的锁和zookeeper
zookeeper 本文由 简悦 SimpRead 转码, 原文地址 zhuanlan.zhihu.com 前言 只有光头才能变强。 文本已收录至我的 GitHub 仓库,欢迎 Star:https://github.com/ZhongFuCheng3y/3y 上次写了一篇 什么是消息队列?以后,本来…...

经验总结:(Redis NoSQL数据库快速入门)
一、Nosql概述 为什么使用Nosql 1、单机Mysql时代 90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题 数据量增加到一定程度,单机数据库就放不下了数据的索引(B Tree),一个机…...

form表单与模板引擎
文章目录 一、form表单的基本使用1、什么是表单2、表单的组成部分3、 <form>标签的属性4、表单的同步提交及缺点(1) 什么是表单的同步提交(2) 表单同步提交的缺点(3) 如何解决表单同步提交的缺点 二、…...

医院检验信息管理系统源码(云LIS系统源码)JQuery、EasyUI
云LIS系统是一种医疗实验室信息管理系统,提供全面的实验室信息管理解决方案。它的主要功能包括样本管理、检测流程管理、报告管理、质量控制、数据分析和仪器管理等。 云LIS源码技术说明: 技术架构:Asp.NET CORE 3.1 MVC SQLserver Redis等…...

React 组件
文章目录 React 组件复合组件 React 组件 本节将讨论如何使用组件使得我们的应用更容易来管理。 接下来我们封装一个输出 “Hello World!” 的组件,组件名为 HelloMessage: React 实例 <!DOCTYPE html> <html> <head> &…...

硕士学位论文的几种常见节奏
摘要: 本文描述硕士学位论文的几种目录结构, 特别针对机器学习方向. 1. 基础版 XX算法及其在YY中的应用 针对情况: 只有一篇小论文支撑. 第 1 章: 引言 ( > 5页) 1.1 背景及意义 (应用背景、研究意义, 2 页) 1.2 研究进展及趋势 (算法方面, 2 页) 1.3 论文结构 (1 页) 第 …...

找兄弟单词
描述 定义一个单词的“兄弟单词”为:交换该单词字母顺序(注:可以交换任意次),而不添加、删除、修改原有的字母就能生成的单词。 兄弟单词要求和原来的单词不同。例如: ab 和 ba 是兄弟单词。 ab 和 ab 则不…...

python字典翻转教学
目录 第1关 创建大学英语四级单词字典 第2关 合并大学英语四六级词汇字典 第3关 查单词输出中文释义 第4关 删除字典中特定字母开头的单词 第5关 单词英汉记忆训练 第1关 创建大学英语四级单词字典 本关任务:编写一个能创建大学英语四级单词字典的小程序。 测…...

sentinel 随笔 3-降级处理
0. 像喝点东西,但不知道喝什么 先来段源码,看一下 我们在dashboard 录入的降级规则,都映射到哪些字段上 package com.alibaba.csp.sentinel.slots.block.degrade;public class DegradeRule extends AbstractRule {public DegradeRule(String…...