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

React中修改 state 时必须返回一个新对象 (immutable update)

文章目录

  • 前言
      • 核心原因
      • 第一步:你调用 setState
      • 第二步:setState 将更新入队 (enqueueSetState)
      • 第三步:创建更新对象 (createUpdate)
      • 第四步:处理更新 (processUpdateQueue)
      • 第五步:检查状态变化 (checkShouldComponentUpdate)
      • 第六步:浅比较 (shallowEqual) 的原理
        • 关键点总结与你的问题
  • 扩展
        • 核心流程概览
        • 为什么函数组件 useState 也强调返回新对象?
          • 类组件:setState 合并对象后,使用 shallowEqual(浅比较) 比较新旧状态对象 本身 的引用和一级属性的引用/值。
          • 函数组件:useState 直接比较 整个状态值 (newState vs oldState) 使用 Object.is。
        • 源码级总结 (useState 的 "Object.is" 哲学):


前言

从 React 源码层面剖析一下 为什么修改 state 时必须返回一个新对象 (immutable update) 才能真正触发页面刷新 (Re-render)

核心原因

在于:React 使用“浅比较”(shallow comparison) 来检测状态是否变化,以此决定是否需要重新渲染组件。 直接修改现有状态对象的属性不会改变该对象的引用地址,导致浅比较失效。

让我们结合核心源码流程来解释(基于 React 18.2.0 的核心逻辑简化):

第一步:你调用 setState

// 你的代码:在一个类组件中
this.setState({ count: this.state.count + 1 });

第二步:setState 将更新入队 (enqueueSetState)

setState 最终调用 enqueueSetState 函数 (位于 react/src/ReactBaseClasses.js 附近逻辑,实际实现与 Fiber 架构绑定更复杂)。

// React 源码 (简化示意)
Component.prototype.setState = function(partialState) {// ... 其他逻辑this.updater.enqueueSetState(this, partialState);
};

第三步:创建更新对象 (createUpdate)

React 会为这次状态更新创建一个更新对象 (Update),并将你传入的 partialState 保存在其中。

// React 源码 (fiber 相关逻辑,react-reconciler 包中,简化示意)
function enqueueSetState(instance, partialState) {const fiber = getFiber(instance);const update = createUpdate(); // 关键点:创建一个新的更新对象update.payload = partialState; // 把你的 partialState ({count: newValue}) 存进去enqueueUpdate(fiber, update); // 将更新对象加入 fiber 的更新队列scheduleUpdateOnFiber(fiber); // 安排后续的渲染工作

第四步:处理更新 (processUpdateQueue)

在后续的渲染阶段 (调度器决定何时执行),React 会遍历 Fiber 节点上的更新队列。

// React 源码 (fiber 更新处理,react-reconciler 包中,极度简化)
function processUpdateQueue(workInProgress) {const queue = workInProgress.updateQueue;let newState = workInProgress.memoizedState; // 当前 fiber 的旧状态while (queue.firstUpdate !== null) {const update = queue.firstUpdate;const partialState = update.payload; // 取出你之前传的 partialState// 关键合并逻辑:使用 Object.assign 合并状态 (浅层合并)!!!if (partialState ! null && partialState ! undefined) {newState = Object.assign({}, newState, partialState); // 核心合并!!!
// ... 移动队列指针等操作// 最终得到 newStateworkInProgress.memoizedState = newState; // 设置新的状态到 fiber

第五步:检查状态变化 (checkShouldComponentUpdate)

在组件准备重新渲染前,React 会检查状态或 Props 是否变化。核心检查发生在 checkShouldComponentUpdate 或类似逻辑中 (最终调用 shallowEqual)。

// React 源码 (判断是否更新, react-reconciler 或 ReactFiberClassComponent, 简化)
function checkShouldComponentUpdate(workInProgress, newProps, newState) {const instance = workInProgress.stateNode;const oldProps = workInProgress.memoizedProps;const oldState = workInProgress.memoizedState;// 1. 先检查生命周期 shouldComponentUpdate (如果有)if (typeof instance.shouldComponentUpdate === 'function') {return instance.shouldComponentUpdate(newProps, newState, nextContext);
// 2. PureComponent 或默认行为:浅比较 Props 和 Statereturn (!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)   // 这里是关键!!!);

第六步:浅比较 (shallowEqual) 的原理

shallowEqual 是核心关键 (代码通常在 shared/shallowEqual.js)。

// React 源码 (shared/shallowEqual.js)
function shallowEqual(objA, objB) {if (is(objA, objB)) { // is() 就是 Object.is (处理基础值和对象引用)return true; // 相同引用或相同的原始值 => 没变化
// 如果其中一个不是对象或是 null,返回 false (有变化)if (typeof objA ! 'object' |objA = nulltypeof objB ! 'object'
| objB = null) {return false;
const keysA = Object.keys(objA);const keysB = Object.keys(objB);if (keysA.length !== keysB.length) {return false; // 属性个数不同 => 有变化
// 关键循环:只比较最外层属性的值是否引用相等 (或基本值相等)for (let i = 0; i < keysA.length; i++) {const key = keysA[i];if (!hasOwnProperty.call(objB, key) || // B 没有 A 的属性?!is(objA[key], objB[key])        // 关键!!! 属性值引用是否相等? (浅比较)) {return false; // 存在不一致 => 有变化
}return true; // 浅层判断相等 => 没变化
关键点总结与你的问题

合并状态: processUpdateQueue 中,React 将你的 partialState (如 { count: newValue }) 浅合并 (Object.assign({}, oldState, partialState)) 到旧状态 (oldState) 的一个新对象副本上,生成 newState。

对象引用对比 (浅比较核心): shallowEqual 函数在检查状态 (state) 是否变化时:

先检查 oldState 和 newState 这两个对象本身是不是同一个引用 (Object.is(oldState, newState))。

如果是同一个引用,它会立即认为状态没有变化。

如果不是同一个引用,它才会进行下一步浅层属性比对。
错误方式的问题:

    // 错误!! 直接修改旧 state 对象this.state.count = this.state.count + 1;// 然后调用 setState (此时传入的 partialState 是空对象或没变化的对象)this.setState({}); // 或 this.setState(this.state)

this.state.count = … 修改了 this.state 内部的属性值,但 this.state 对象本身的引用地址没有变!它还是原来的那个对象。

在 processUpdateQueue 中,你传入的 partialState 是空对象 {}。Object.assign({}, oldState, {}) 返回的 newState 其实就是 oldState 的一个副本。但是这个副本操作使用的是 Object.assign,它进行的是浅拷贝。

浅拷贝意味着:newState 是一个新对象 (引用地址和 oldState 不同了),但它内部属性 count 指向的值地址还是修改前的值地址吗?不,在这个错误代码示例中,有微妙点:

你 之前 已经直接修改了 oldState.count(原对象的属性)。Object.assign 创建了一个新对象 newState,并将 oldState 的所有 可枚举自身属性 复制到这个新对象上。所以,newState.count 的值会是你修改后的值(假设你是 this.state.count = newValue; this.setState({}))。此时:

newState !== oldState(引用不同,所以浅比较 shallowEqual 的第一步引用比较返回 false)。

shallowEqual 会继续检查属性:属性数量相同吗?相同。属性名都存在吗?存在。那么比较属性值:is(oldState.count, newState.count)

但是,在错误示例中,oldState.count 和 newState.count 都是同一个数字(基本类型,值相等,Object.is 比较为 true)。

所以,shallowEqual 会返回 true,认为 oldState 和 newState 浅层相等?! 这似乎与预期的修改矛盾?那为什么有时这样写能看到 UI 更新?这个错误方式的行为在 React 中是不可靠的 (Unreliable) 且被禁止的,原因如下:

批量更新问题: React 的更新可能是批量的。setState({}) 可能不会立刻触发 processUpdateQueue 和后续比较。如果你在同一个事件循环中多次 this.state.count = …; this.setState({});,由于直接修改的是同一 this.state 对象,每次的修改都会覆盖上一次的状态,只有最后一次修改会“可能”被保留到 newState 中(因为浅拷贝发生在processUpdateQueue时),导致状态丢失和不一致。你无法依赖中间状态。

PureComponent 失效: PureComponent 和 React.memo 默认依赖浅比较来优化。如果像上面错误示例中,恰好 newState.count === oldState.count(对于原始值)且其他属性也没变,即使你修改了 oldState.count,浅比较也会认为相等而跳过更新。但如果 newState.count 是新计算的值(比如 oldState.count + 1),并且它与修改后的 oldState.count 相等(数值相等),浅比较可能通过(原始值比较),组件可能更新。但这种更新是巧合和不可预测的,依赖于值比较的结果。

异步状态访问: React 的 this.state 在状态更新真正提交 (commit 阶段) 前,指向的始终是旧的 fiber.memoizedState。直接修改 this.state 会立即污染这个值(旧状态),导致组件内部访问 this.state 得到的是修改后的值,但这个值并不一定会在后续提交中被真正应用(因为批处理和状态队列),造成组件逻辑混乱和难以调试。

并发模式 (Concurrent Mode) 下行为未定义: 在 React 18 的并发渲染模式下,直接修改可变状态会导致在渲染过程中状态被外部突变,破坏渲染的“一致性快照”原则,导致 UI 错误和崩溃的风险极高。

结论Object.assign 浅拷贝创建的 newState 对象 引用 改变了(触发浅比较第一步 false),但属性值可能是基本值(浅比较第二步 is 比较值相等)或者是被修改的旧对象的属性引用(对于对象/数组)。浅比较在第二步属性值比较时,对比的是 值的引用或基本值本身。在错误示例中,如果你修改的是基本值(如数字 count),且 Object.assign 复制后新对象属性值与旧状态属性值(这个值已经被你之前突变改变了)在基本值层面相等,那么浅比较最终 仍然会返回 true ,认为状态没变化!但如果你的 state 包含嵌套对象,而你只修改了嵌套对象内部的属性,那么浅比较在第二步比较该嵌套对象属性的 引用 时,发现引用没变(还是同一个嵌套对象),同样会返回 true。无论如何,直接修改 this.state 并调用 setState 传递旧对象或其引用是危险的、不可预测的、违背 React 不可变原则的,并且会导致潜在的 bug 和性能问题(不必要的渲染或漏渲染)。
正确方式的原因:

        // 正确方式:传入一个包含变化属性的新对象this.setState({ count: this.state.count + 1 });

在 processUpdateQueue 中,Object.assign({}, oldState, { count: newValue }) 会创建一个全新的状态对象 (newState)。

这个 newState 对象的引用 (ref) 一定不同于 oldState 的引用。

shallowEqual(oldState, newState) 在第一步判断两个对象的引用是否相同时 (Object.is) 就立即返回了 false(因为 newState 是新对象)!

既然 shallowEqual 返回 false(状态改变),React 就会确定这个组件需要重新渲染。

即使 oldState 和 newState 内部的 count 属性值在数值上是相同的(比如 this.state.count + 1 恰好等于原来的值,概率极低但理论上可能),shallowEqual 也不会进入属性比较阶段,因为在第一步对象引用比较时就已经返回 false 了。 这样保证了只要有 setState 传入非空对象,只要对象引用发生变化(这是创建新对象的必然结果),状态改变就会被检测到。

图解流程:

你的代码:setState({ count: newValue })
vReact: enqueueSetState(fiber, { count: newValue })
v                      创建更新对象 (Update)创建 update: { payload: { count: newValue } }
venqueueUpdate(fiber, update) -> 加入到 fiber 的更新队列
vscheduleUpdateOnFiber(fiber) -> 安排渲染
| (调度器决定何时处理)processUpdateQueue(fiber):newState = Object.assign({}, oldState, update.payload) // 创建一个全新的状态对象
vcheckShouldComponentUpdate(fiber):shallowEqual(oldState, newState) // 比较引用
v因为 newState !== oldState (新对象引用) -> shallowEqual 返回 false -> 需要更新!
v标记组件需要重新渲染 (Re-render)

结论:

从源码层面看,React 强制要求状态更新必须是不可变的 (immutable),通过在 processUpdateQueue 中合并状态时创建一个新对象 (Object.assign 或类似方式),并在后续通过 shallowEqual 进行非常快速的引用检查 来决定是否更新。这既保证了简单场景下高效的更新判断(只需比较对象引用),也避免了开发者直接修改状态带来的潜在混乱和错误,为性能优化(如 PureComponent、React.memo)提供了基础保障。直接修改 this.state 会使 shallowEqual 在第一步引用比较就认为状态没变化(除非你特意生成了一个新对象给 setState),从而导致组件不更新或更新行为不可预测且易出错。

扩展

想更深入看函数组件 useState 的 Hooks 实现吗?其核心的浅比较逻辑是相通的。

重点剖析 useState 在函数组件中如何管理状态以及如何触发重新渲染。基于 React 18.2.0 源码(主要关注 react-reconciler 包),我们来拆解它如何工作的。

核心流程概览
  1. useState 调用: 你的函数组件执行,调用 useState(initialValue)。
  2. 挂载 (mount) / 更新 (update) 分支: React 根据组件是首次渲染还是后续渲染,走不同的逻辑。
  3. Hook 对象链表: Hooks 的状态存储在 Fiber 节点的 memoizedState 属性上,是一个链表结构。每个 Hook
    (useState, useEffect 等) 对应链表中的一个节点。
  4. 挂载阶段 (mount): 创建 Hook 对象,初始化状态,绑定 dispatch 函数。
  5. 更新阶段 (update): 获取更新队列,计算新状态,标记 Fiber 需要更新。
  6. 状态更新 (dispatch): 当你调用 setState(newValue) (即 dispatch 函数)
    时,将更新加入队列,并调度渲染。
  7. 渲染阶段: React 处理队列中的更新,计算新状态,进行新旧值比较 (Object.is)
  8. 重新渲染决策: 如果新状态与旧状态引用不同(或基本值不等),则标记组件需要重新渲染。

深入源码(极度简化,突出关键路径)

文件:react-reconciler/src/ReactFiberHooks.js
useState 函数入口

function useState(initialState) {// 当前执行的函数组件对应的 Fiber 节点const fiber = currentlyRenderingFiber;// 当前 Hook 在链表中的索引const hookIndex = workInProgressHookIndex++;// 判断是首次渲染(mount)还是更新渲染(update)if (currentlyRenderingFiber.memoizedState === null) {// mount 阶段: 初始化 Hook 对象链表const hooks = mountWorkInProgressHook();currentlyRenderingFiber.memoizedState = hooks;
else {// update 阶段: 获取已存在的 Hook 链表const hooks = currentlyRenderingFiber.memoizedState;
// 根据是 mount 还是 update 执行对应的逻辑if (isMount) {return mountState(initialState);
else {return updateState();
}

挂载阶段 (mountState) - 首次渲染

function mountState(initialState) {// 1. 创建新的 Hook 节点 (链表中的一个节点)const hook = mountWorkInProgressHook();// 2. 确定初始状态值let initialStateValue;if (typeof initialState === 'function') {initialStateValue = initialState(); // 支持函数式初始值
else {initialStateValue = initialState;
// 3. 将初始状态保存在 Hook 节点的 memoizedState 和 baseState 上hook.memoizedState = hook.baseState = initialStateValue;// 4. 初始化 Hook 的更新队列 (queue)const queue = {pending: null,   // 指向最新的未处理更新 (环状链表)dispatch: null,  // 即将赋值的 dispatch 函数 (用于 setState)lastRenderedReducer: basicStateReducer, // 用于计算新状态的状态计算函数lastRenderedState: initialStateValue,   // 上一次渲染时使用的状态};hook.queue = queue;// 5. 创建并返回 dispatch 函数 (setState) 的引用const dispatch = (queue.dispatch = dispatchAction.bind(null,currentlyRenderingFiber, // 绑定当前 Fiberqueue                    // 绑定该 Hook 的更新队列));// 6. 返回初始状态和 setState 函数return [hook.memoizedState, dispatch];dispatchAction - 执行 setState(newState)

这是触发状态更新的核心函数。当你调用 const [state, setState] = useState(0); setState(1) 时,实际执行的是 dispatchAction(fiber, queue, action),其中 action 是你传入的值或函数(1 或 prev => prev + 1)。

function dispatchAction(fiber, queue, action) {// 1. 创建一个更新对象 (update)const update = {action,         // 你传入的值或函数 (newValue 或 updaterFunction)next: null,     // 用于指向下一个更新 (构成环状链表)};// 2. 将更新加入队列 (pending 是一个环状链表,指向最新的update)const pending = queue.pending;if (pending === null) {// 第一个更新: 自身构成环update.next = update;
else {// 插入到队列尾部 (环状链表的插入操作)update.next = pending.next;pending.next = update;
queue.pending = update; // 更新队列的 pending 指针指向最新的 update// 3. 关键调度: 标记 Fiber 需要更新,并加入调度器scheduleUpdateOnFiber(fiber);

scheduleUpdateOnFiber 是 React 协调过程的核心入口,它会将这次更新加入调度器的队列。调度器(如 ReactDOM 默认的调度器)会根据优先级、浏览器空闲时间等因素决定何时开始协调(Reconciliation)过程。
更新阶段 (updateState) - 后续渲染

当函数组件因为状态或属性变化需要重新执行时,再次调用 useState() 会进入 updateState 逻辑。

function updateState() {// 1. 获取当前 Fiber 上对应的 Hook 对象const hook = updateWorkInProgressHook();// 2. 准备计算新状态const queue = hook.queue;let newState = hook.baseState; // 基于 baseState (初始状态或上次被接受的基状态)let pendingQueue = queue.pending;// 3. 关键: 处理更新队列if (pendingQueue !== null) {// 重置更新队列(计算完成后)queue.pending = null;// 将环状队列展开成单向链表let firstUpdate = pendingQueue.next;let update = firstUpdate;// 4. 循环遍历队列中的每个更新,应用它们do {const action = update.action;// 应用当前更新: 使用 reducer 计算新状态 (对于 useState 是 basicStateReducer)// 如果 action 是函数 (updater function), 则执行它: action(newState)// 如果 action 是值,则直接使用它: newState = actionnewState = queue.lastRenderedReducer(newState, action);update = update.next;
while (update ! null && update ! firstUpdate); // 遍历整个队列// 更新 Hook 的 memoizedState 和 baseState (新的基状态)hook.memoizedState = newState;hook.baseState = newState;queue.lastRenderedState = newState; // 记录这次渲染使用的状态
// 5. 关键比较与决策: Bail out?const dispatch = queue.dispatch;// React 18 的核心优化: 即使状态计算完成,也可能跳过渲染!// 比较: newState vs hook.memoizedState (当前渲染前的状态) vs currentHook.memoizedState (current树上的状态)?// 最终决策依赖于 reconcileChildren 和 checkScheduledUpdateOrContext// 但最核心的比较还是 Object.is(newState, currentlyCommittedState)// 6. 返回新状态和 dispatch 函数return [hook.memoizedState, dispatch];// basicStateReducer - useState 的状态计算函数
function basicStateReducer(state, action) {// 如果 action 是函数 (updater function),调用它并传入当前状态// 返回计算结果作为新状态if (typeof action === 'function') {return action(state);
// 否则,直接返回 action 作为新状态return action;

useState 如何判断是否需要重新渲染?(核心)

在协调过程 (beginWork/completeWork) 中,React 会检查每个函数组件 Fiber 节点是否真正需要重新渲染。这个过程发生在 renderWithHooks 和后续的工作中。关键比较点有两个:
在 updateFunctionComponent -> renderWithHooks 流程中:

在计算 useState 等 Hook 的新状态后(如上一步 updateState 完成),React 会比较这次计算出的 Hook.memoizedState(新状态)与 Fiber current 树(前一次提交时对应的树)上对应 Hook 的 memoizedState(旧状态)。

比较方式是严格相等 (Object.is)。

如果 Object.is(newState, oldState) === true,那么这个 Hook 本身的状态就被认为 没有变化。

React 还会检查是否有 useContext 订阅的上下文变化了。

如果该 Fiber 的 所有 Hook 状态 (memoizedState) 通过 Object.is 比较都未变,并且没有上下文变化 (didReceiveUpdate = false),React 会尝试执行 “bailout” 优化——跳过该组件的子树的协调和渲染!直接复用上次渲染的结果。这是 React 18 优化性能的关键手段。

如果 Object.is(newState, oldState) === false,或者有上下文变化,或者父组件强制更新等原因(didReceiveUpdate = true),React 就会 进入协调/渲染流程。
在应用单个更新计算新状态时 (basicStateReducer / updateState 循环中):

即使 Object.is(newState, oldState) 在全局 bailout 检查中比较失败导致组件被渲染了,React 在处理更新队列时,还是会依次应用每一个更新(无论新计算出的中间状态是否与某个之前的状态 Object.is 相等)。

目的:保证每个更新(setState 调用)都被执行并计算状态,即使最终渲染结果可能相同(如果最终计算出的状态和上次一样,bailout 仍然可能发生)。这在 action 是函数 (prevState => …) 时尤其重要,必须执行所有传入的更新函数来保证计算流程的正确性,即使最终值未变。

为什么函数组件 useState 也强调返回新对象?

比较基础不同:

类组件:setState 合并对象后,使用 shallowEqual(浅比较) 比较新旧状态对象 本身 的引用和一级属性的引用/值。
函数组件:useState 直接比较 整个状态值 (newState vs oldState) 使用 Object.is。

基本值 (number, string, boolean): Object.is(5, 5) -> true; Object.is(5, 6) -> false。没问题。

对象/数组: Object.is({}, {}) -> false (因为它们是两个不同的对象,引用地址不同)。Object.is([], []) -> false。
结论:

对于对象或数组状态:

错误方式: 直接修改对象的属性 (state.someProp = 123) 或数组的项 (state[0] = ‘a’),然后调用 setState(state)(或 setState(oldObj)):

你传入的新状态值 action 是同一个旧对象的引用。

在更新队列计算新状态时,newState = action(直接等于旧对象引用)。

新旧状态比较 (Object.is(oldStateRef, newStateRef)):true (引用相同) -> React 会认为状态没变 -> 组件可能被 bailout,不渲染!(除非其他原因导致 didReceiveUpdate = true)

正确方式: 总是返回一个新对象/数组:

       setState(prevState => ({ ...prevState, someProp: 123 }));setState(newArray); // e.g., [...oldArray, newItem]

你传入的新状态值是一个全新的对象/数组引用。

新旧状态比较 (Object.is(oldRef, newRef)):false (引用不同) -> React 知道状态变了 -> 触发重新渲染!

对于基本值状态:直接赋值即可 (setState(123)),因为 Object.is 会比较值本身,引用比较在此不适用。

源码级总结 (useState 的 “Object.is” 哲学):
  1. 状态存储: 函数组件的状态(每个 useState Hook)存储在 Fiber 节点的 memoizedState 链表上的 Hook
    对象中。
  2. 更新触发: dispatchAction 创建更新对象并入队,然后调度协调过程 (scheduleUpdateOnFiber)。
  3. 状态计算: 在更新阶段 (updateState),React 会遍历并应用队列中的所有更新,使用
    basicStateReducer(处理值或函数式更新)计算最新的单一状态值 (newState)。
  4. 渲染决策: 在全局协调流程中,React 核心决策点是:用 Object.is 比较 Fiber current 树上的 旧状态
    (memoizedState) 和刚刚计算出的 新状态 (memoizedState) (来自 workInProgress 树上的
    Hook)。
Object.is(newState, oldState) === true -> 可能 bailout (复用上次渲染结果)。Object.is(newState, oldState) === false -> 需要重新协调和渲染。
  1. 不可变要求: 对于对象/数组状态,必须创建新引用以改变其引用地址,才能使 Object.is 比较结果为
    false,从而确保状态变化能被检测到并触发重新渲染。直接修改旧对象的属性不会改变其引用地址,导致 Object.is 结果为 true
    和潜在的 bailout 优化(意外跳过渲染)。这就是函数组件也必须遵守不可变更新的根本原因。

理解 useState 的源码关键在于把握两点:
1)Hooks 通过 Fiber 节点上的链表管理状态;
2)Object.is 作为状态变化检测和 bailout 优化的基石。

相关文章:

  • 基于数据库实现配置管理和定时任务启停
  • 常见Modbus协议面试问题
  • 强化微调技术与GRPO算法(1):简介
  • 农田实时监测与管理系统开发
  • map()函数
  • IteraJudge-增量多维评判框架解读
  • 第十节 高频代码题-类型推断题
  • NXP S32K146 T-Box 携手 SDNAND(贴片式 TF 卡):驱动汽车智能革新的黄金组合
  • 接口测试不再难:智能体自动生成 Postman 集合
  • 计算机系统(6)
  • 翻译核心词汇
  • 软考-计算机硬件组成
  • 微信小程序渗透测试指北(附案例)
  • 小结:Spring AOP 切点表达式
  • python-生日悖论
  • OpenCV——图像金字塔
  • std::make_shared简化智能指针 `std::shared_ptr` 的创建过程,并提高性能(减少内存分配次数,提高缓存命中率)
  • 第30节 Node.js C/C++ 插件
  • ARXML可视化转换工具使用说明
  • C#实现无声视频的配音与字幕生成器
  • 深圳品牌学校网站建设/青岛百度快速排名优化
  • 中国建设企业协会网站/推广电话
  • 灵犀科技-网站开发/杭州seo排名
  • 网站建站需要什么软件/兰州seo外包公司
  • 山西省财政厅门户网站三基建设专栏/百度福州分公司
  • 专业seo优化费用/搜索引擎优化通常要注意的问题有