React 中state与props更新深入解析

目录
  • 正文
  • 组件的 updater
  • 处理 ClickCounter Fiber 的 update
    • beginWork
    • Reconciling children for the ClickCounter Fiber
  • 处理 Span Fiber 的 update
    • Reconciling children for the span fiber
  • Effects list
  • commit 阶段
    • 应用 effects
    • DOM updates
    • 调用 Post-mutation 生命周期

正文

在这篇文章中,我使用下面这样的应用程序作为例子

class ClickCounter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {count: 0};
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        this.setState((state) => {
            return {count: state.count + 1};
        });
    }
    componentDidUpdate() {
        // todo
    }
    render() {
        return [
            <button key="1" onClick={this.handleClick}>Update counter</button>,
            <span key="2">{this.state.count}</span>
        ]
    }
}

我给 ClickCounter 组件添加了 componentDidUpdate 钩子,这个钩子会在 commit 阶段被调用。

在之前我写了一篇深入介绍 React Fiber的文章,在那篇文章中我介绍了 React 团队为什么要重新实现 reconciliation 算法、fiber 节点与 react element 的关系、fiber 节点的字段以及 fiber 节点是如何被组织在一起的。在这篇文章中我将介绍 React 如何处理 state 更新以及 React 如何创建 effects list,我也会介绍在 render 阶段和 commit 阶段调用的函数。

组件的 updater

当我们点击按钮之后 handleClick 方法会被调用,这导致 state.count 值加 1

class ClickCounter extends React.Component {
    ...
    handleClick() {
        this.setState((state) => {
            return {count: state.count + 1};
        });
    }
}

每个 React 组件都有一个相关联的 updater,它作为组件与 React core 之间的桥梁,这允许 ReactDOM、React Native、服务器端渲染和测试工具以不同的方式实现 setState。这篇文章我们只讨论在 ReactDOM 中 updater 的实现,ClickCounter 组件的 updater 是一个 classComponentUpdater,它负责检索 fiber 实例,队列更新和工作调度。

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);
    ...
  },
  enqueueReplaceState(inst, payload, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);
    ...
    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
  enqueueForceUpdate(inst, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);
    ...
    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
};

当 update 发生时,它们被添加到 fiber 节点上的 updateQueue 中处理。在我们的例子中,ClickCounter 组件的 fiber 节点的结构如下:

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    updateQueue: {
         baseState: {count: 0}
         firstUpdate: {
             next: {
                 payload: (state) => { return {count: state.count + 1} }
             }
         },
         ...
     },
     ...
}

如果你仔细观察,你会发现 updateQueue.firstUpdate.next.payload 的值是我们在 ClickCounter 组件中传递给 setState 的参数。它代表在 render 阶段需要处理的第一个 update。

处理 ClickCounter Fiber 的 update

我在上一篇文章中介绍了 nextUnitOfWork 变量的作用,nextUnitOfWork 保存了对workInProgress树中 fiber 节点的引用,当 React 遍历 fiber 树时,它使用这个变量来判断是否有其他未完成工作的 fiber 节点。

在调用 setState 方法之后,React 把我们传递给 setState 的参数添加到 ClickCounter fiber 的 updateQueue 属性上并且进行工作调度。React 进入 render 阶段,它使用renderRoot函数从 fiber 树最顶层的 HostRoot 开始遍历 fiber,在遍历的过程中 React 会跳过已经处理完的 fiber 直到遇到没有处理的 fiber。在 render 阶段,fiber 节点的所有工作都是在 fiber 的 alternate 字段上进行的。如果还没有创建 alternate,React 会在处理 update 之前在createWorkInProgress 函数中创建 alternate。

在这里我们假设 nextUnitOfWork 变量中保存的是 ClickCounter fiber 的 alternate。

beginWork

在处理 update 时,首先会调用 beginWork 函数。

由于 fiber 树上的每一个 fiber 都会执行 beginWork 函数,如果你想 debug render 阶段,你可以在 beginWork 函数中打断点。

beginWork 函数基本上包含了一个大的 switch 语句,switch 通过判断 fiber 的 tag 来确定 fiber 需要做哪些工作

function beginWork(current, workInProgress, ...) {
    ...
    switch (workInProgress.tag) {
        ...
        case FunctionalComponent: {...}
        case ClassComponent:
        {
            ...
            return updateClassComponent(current$$1, workInProgress, ...);
        }
        case HostComponent: {...}
        case ...
}

由于 ClickCounter 是一个类组件,所以 React 会执行updateClassComponent函数,updateClassComponent 函数大概如下:

function updateClassComponent(current, workInProgress, Component, ...) {
    ...
    const instance = workInProgress.stateNode;
    let shouldUpdate;
    if (instance === null) {
        ...
        // In the initial pass we might need to construct the instance.
        constructClassInstance(workInProgress, Component, ...);
        mountClassInstance(workInProgress, Component, ...);
        shouldUpdate = true;
    } else if (current === null) {
        // In a resume, we'll already have an instance we can reuse.
        shouldUpdate = resumeMountClassInstance(workInProgress, Component, ...);
    } else {
        shouldUpdate = updateClassInstance(current, workInProgress, ...);
    }
    return finishClassComponent(current, workInProgress, Component, shouldUpdate, ...);
}

在 updateClassComponent 函数中 React 会判断组件是否是第一次 render、是否是恢复工作或者是否是 update,不同的情况做的事情不一样。

在上面的例子中,当我们点击按钮调用 setState 方法时,我们已经有 ClickCounter 组件实例了,所以 React 会调用updateClassInstance方法,在 updateClassInstance 函数中会按下面的顺序执行很多函数:

  • 调用 UNSAFE_componentWillReceiveProps 钩子(deprecated)
  • 处理 updateQueue 中的 update 并生成新的 state
  • 使用这个新 state 调用 getDerivedStateFromProps 并获得组件最终的 state
  • 调用 shouldComponentUpdate 钩子去确定组件是否需要更新;如果不需要更新就跳过整个 render 阶段(不调用组件和组件 children 的 render 方法);否则继续更新
  • 调用 UNSAFE_componentWillUpdate 钩子(deprecated)
  • 添加触发 componentDidUpdate 钩子的 effect
  • 更新组件实例的 state 和 props

虽然 componentDidUpdate 钩子的 effect 是在 render 阶段被添加的,但是 componentDidUpdate 钩子会在 commit 阶段执行

组件的 state 和 props 会在调用 render 方法之前被更新,因为 render 方法的输出依赖于 state 和 props 的值。

下面是 updateClassInstance 函数的简化版本,我删除了一些辅助代码

function updateClassInstance(current, workInProgress, ctor, newProps, ...) {
    const instance = workInProgress.stateNode;
    const oldProps = workInProgress.memoizedProps;
    instance.props = oldProps;
    if (oldProps !== newProps) {
        callComponentWillReceiveProps(workInProgress, instance, newProps, ...);
    }
    let updateQueue = workInProgress.updateQueue;
    if (updateQueue !== null) {
        processUpdateQueue(workInProgress, updateQueue, ...);
        newState = workInProgress.memoizedState;
    }
    applyDerivedStateFromProps(workInProgress, ...);
    newState = workInProgress.memoizedState;
    const shouldUpdate = checkShouldComponentUpdate(workInProgress, ctor, ...);
    if (shouldUpdate) {
        if (typeof instance.componentWillUpdate === 'function') {
            instance.componentWillUpdate(newProps, newState, nextContext);
        }
        if (typeof instance.componentDidUpdate === 'function') {
          workInProgress.effectTag |= Update;
        }
        if (typeof instance.getSnapshotBeforeUpdate === 'function') {
          workInProgress.effectTag |= Snapshot;
        }
    }
    instance.props = newProps;
    instance.state = newState;
    return shouldUpdate;
}

在调用生命周期钩子或添加生命周期钩子的 effect 之前,React 使用 typeof 检查实例是否实现了相应的钩子。例如:React 使用下面的代码来检查实例是否有 componentDidUpdate 钩子:

if (typeof instance.componentDidUpdate === 'function') {
    workInProgress.effectTag |= Update;
}

现在我们大概已经知道了 ClickCounter 的 fiber 节点在 render 阶段要执行的操作,现在让我们看看这些操作是如何改变 fiber 上的值的。调用 setState 之后,当 React 开始工作的时候,ClickCounter 组件的 fiber 节点像下面这样:

{
    effectTag: 0,
    elementType: class ClickCounter,
    firstEffect: null,
    memoizedState: {count: 0},
    type: class ClickCounter,
    stateNode: {
        state: {count: 0}
    },
    updateQueue: {
        baseState: {count: 0},
        firstUpdate: {
            next: {
                payload: (state, props) => {…}
            }
        },
        ...
    }
}

当工作完成之后,我们最终得到的 fiber 节点像这样:

{
    effectTag: 4,
    elementType: class ClickCounter,
    firstEffect: null,
    memoizedState: {count: 1},
    type: class ClickCounter,
    stateNode: {
        state: {count: 1}
    },
    updateQueue: {
        baseState: {count: 1},
        firstUpdate: null,
        ...
    }
}

对比 ClickCounter fiber 的前后差异我们可以发现当 update 被应用之后 memoizedState 和 updateQueue.baseState 中的 count 的值为 1。组件实例中的 state 也会被更新。在这个时候,在队列中已经没有 updates 了,所以 firstUpdate 为 null。effectTag 的值不再是 0,它变成了 4,在二进制中,这是 100,这代表了 side-effect 的类型是 Update。

export const Update = 0b00000000100;

总结一下,在处理 ClickCounter fiber 节点时,React 会调用 pre-mutation 生命周期方法、更新 state 以及定义相关的 side-effects。

Reconciling children for the ClickCounter Fiber

updateClassInstance 运行结束之后,React 会调用finishClassComponent函数,在这个函数中会调用组件的 render 方法,并且在 render 方法返回的 react elements 上运行 diff 算法。diff 算法大概的规则是:

当比较两个相同类型的React DOM element 时,React 会检查这两个元素的属性,保持相同的底层 DOM 节点,只更新已更改的属性。

Child reconciliation 的过程非常复杂,如果有可能我会单独写一篇文章介绍这个过程。在我们的例子中 ClickCounter 的 render 方法返回的是数组,所以在 Child reconciliation 时会调用reconcileChildrenArray

在这里我们有两点需要着重理解一下

  • 在进行 child reconciliation 时 ,它会为 render 方法返回的 React elements 创建或更新 fiber 节点。finishClassComponent 返回当前 fiber 的第一个 child,这个返回值会被赋给 nextUnitOfWork 变量并且在之后的 work loop 中处理。
  • React 会更新 children 的 props,这是 parent 工作的一部分。

例如,在 React reconciles ClickCounter fiber 的 children 之前,span 元素的 fiber 节点看上去是这样的

{
    stateNode: new HTMLSpanElement,
    type: "span",
    key: "2",
    memoizedProps: {children: 0},
    pendingProps: {children: 0},
    ...
}

memoizedProps.children 和 pendingProps.children 的值都是 0。从 render 方法中返回的 span 元素的结构如下:

{
    $$typeof: Symbol(react.element)
    key: "2"
    props: {children: 1}
    ref: null
    type: "span"
}

对比 span fiber 节点和 span 元素上的属性,你会发现有些属性值是不同的。createWorkInProgress函数用于创建 fiber 节点的 alternate,它使用 react element 上最新的 props 和已经存在的 fiber 创建出 alternate。当 ClickCounter 组件完成 children reconciliation 过程之后,span 的 fiber 节点的 pendingProps 属性会被更新

{
    stateNode: new HTMLSpanElement,
    type: "span",
    key: "2",
    memoizedProps: {children: 0},
    pendingProps: {children: 1},
    ...
}

稍后,当 react 为 span fiber 执行工作时,react 会将 pendingProps 复制到 memoizedProps 上并添加更新 DOM 的 effects。

我们已经介绍了 React 在 render 阶段为 ClickCounter fiber 节点执行的所有工作。由于按钮是 ClickCounter 组件的第一个 child,所以它将被分配给 nextUnitOfWork 变量,但是按钮上没有需要执行工作,所以 React 会快速的移动到按钮的兄弟节点上,也就是 span fiber 节点,这个过程发生在 completeUnitOfWork 函数中。

处理 Span Fiber 的 update

现在 nextUnitOfWork 中保存的是 span fiber 的 alternate 并且 React 会在它上面开始工作。React 从 beginWork 函数开始,这与处理 ClickCounter 的步骤类似。

因为 span fiber 的类型是 HostComponent,所以在 beginWork 函数中会进入 HostComponent 对应的 switch 分支

function beginWork(current$$1, workInProgress, ...) {
    ...
    switch (workInProgress.tag) {
        case FunctionalComponent: {...}
        case ClassComponent: {...}
        case HostComponent:
          return updateHostComponent(current, workInProgress, ...);
        case ...
}

Reconciling children for the span fiber

在我们的例子中,调用 updateHostComponent 函数时,span fiber 没有发生任何重要的改变。

只要 beginWork 函数执行完,React 就会开始执行 completeWork 函数,但是在执行 completeWork 之前 React 会更新 span fiber 上的 memoizedProps,在前面的章节,我提到过在 reconciles children for ClickCounter 时,React 更新了 span fiber 上的 pendingProps,只要 span fiber 在 beginWork 中执行完成,React 会将 pendingProps 更新到 memoizedProps 上

function performUnitOfWork(workInProgress) {
    ...
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
    ...
}

在此之后会调用 completeWork,completeWork 函数中是一个大的 switch 语句,由于 span fiber 是 HostComponent,所以会进入 updateHostComponent 函数:

function completeWork(current, workInProgress, ...) {
    ...
    switch (workInProgress.tag) {
        case FunctionComponent: {...}
        case ClassComponent: {...}
        case HostComponent: {
            ...
            updateHostComponent(current, workInProgress, ...);
        }
        case ...
    }
}

在 updateHostComponent 函数中,React 基本上执行了如下的操作:

  • 准备 DOM 更新
  • 将 DOM 更新添加到 span fiber 的 updateQueue 中
  • 添加更新 DOM 的 effect

在执行这些操作之前,span fiber 看上去是这样的:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    effectTag: 0
    updateQueue: null
    ...
}

执行这些操作之后,span fiber 是这样的:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    effectTag: 4,
    updateQueue: ["children", "1"],
    ...
}

注意 effectTag 和 updateQueue 的值发生了变化。effectTag 的值从 0 变成了 4,在二进制中,这是 100,这代表了 side-effect 的类型是 Update。updateQueue 字段保存用于 update 的参数。

只要 React 处理完 ClickCounter 和它的 children,render 阶段就结束了。

Effects list

在我们的例子中,span fiber 和 ClickCounter fiber 有 side effects,React 会将 HostFiber 的 firstEffect 属性指向 span fiber。React 在 compliteUnitOfWork函数中创建 effects list,下面是一个带着 effect 的 fiber tree:

带有 effect 的线性表是:

commit 阶段

commit 阶段从 completeRoot函数开始,在开始工作之前先将 FiberRoot.finishedWork 设置为 null

function completeRoot(
  root: FiberRoot,
  finishedWork: Fiber,
  expirationTime: ExpirationTime,
): void {
    ...
    // Commit the root.
      root.finishedWork = null;
    ...
}

与 render 阶段不同的是,commit 阶段的操作是同步的。在我们的例子中,在 commit 阶段会更新 DOM 和调用 componentDidUpdate 生命周期函数,在 render 阶段为 span 和 ClickCounter 节点定义了以下 effect:

{ type: ClickCounter, effectTag: 5 }
{ type: 'span', effectTag: 4 }

ClickCounter 的 effectTag 为 5,在二进制中为 101,它表示调用组件的 componentDidUpdate 生命周期。span 的 effectTag 为 4,在二进制中为 100,它表示 DOM 更新

应用 effects

让我们看一下 React 是怎么应用(apply)这些 update 的,应用 effects 是从调用commitRoot函数开始的,这个函数主要调用了如下的三个函数:

function commitRoot(root, finishedWork) {
    commitBeforeMutationLifecycles()
    commitAllHostEffects();
    root.current = finishedWork;
    commitAllLifeCycles();
}

在 commitRoot 中调用的这三个函数都实现了一个大的循环,循环遍历 effects list 并检查 effect 的类型。当发现与函数的用途有关的 effect 时,函数就会应用(apply)它。

由于 commitBeforeMutationLifecycles 的目的是检查 Snapshot effect 并且调用 getSnapshotBeforeUpdate 方法,但是我们没有在 ClickCounter 组件上实现这个方法,那么在 render 阶段就不会添加 Snapshot effect,所以在我们的例子中 commitBeforeMutationLifecycles 什么都不会做。

effect 类型有:

export const NoEffect = /*              */ 0b00000000000;
export const PerformedWork = /*         */ 0b00000000001;
// You can change the rest (and add more).
export const Placement = /*             */ 0b00000000010;
export const Update = /*                */ 0b00000000100;
export const PlacementAndUpdate = /*    */ 0b00000000110;
export const Deletion = /*              */ 0b00000001000;
export const ContentReset = /*          */ 0b00000010000;
export const Callback = /*              */ 0b00000100000;
export const DidCapture = /*            */ 0b00001000000;
export const Ref = /*                   */ 0b00010000000;
export const Snapshot = /*              */ 0b00100000000;
// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /*   */ 0b00110100100;
// Union of all host effects
export const HostEffectMask = /*        */ 0b00111111111;
export const Incomplete = /*            */ 0b01000000000;
export const ShouldCapture = /*         */ 0b10000000000;

DOM updates

执行完 commitBeforeMutationLifecycles 之后,React 会调用 commitAllHostEffects函数 ,在 commitAllHostEffects 中 React 会将 span 的文本从 0 变成 1,但是对于 ClickCounter fiber,commitAllHostEffects 什么都不会做,因为类组件的节点没有任何 DOM 更新。commitAllHostEffects 函数如下:

function commitAllHostEffects() {
    while (nextEffect !== null) {
        ...
        switch (primaryEffectTag) {
          case Placement: {...}
          case PlacementAndUpdate: {...}
          case Update:
            {
              var current = nextEffect.alternate;
              commitWork(current, nextEffect);
              break;
            }
          case Deletion: {...}
        }
        nextEffect = nextEffect.nextEffect;
    }
}

commitAllHostEffects 主要是通过 switch 语句选择正确的 effect 类型并执行相应的操作。在本例中,我们需要更新 span 元素上的文本,因此我们在这里进入 switch 的 Update 分支。在 Update 分支中调用 commitWork 函数,但是实际上最终调用的是 updateDOMProperties

function updateDOMProperties(domElement, updatePayload, ...) {
  for (let i = 0; i < updatePayload.length; i += 2) {
    const propKey = updatePayload[i];
    const propValue = updatePayload[i + 1];
    if (propKey === STYLE) { ...}
    else if (propKey === DANGEROUSLY_SET_INNER_HTML) {...}
    else if (propKey === CHILDREN) {
      setTextContent(domElement, propValue);
    } else {...}
  }
}

updateDOMProperties 接受在 render 阶段添加的 updateQueue 作为参数,并且更新 span 元素的 textContent 属性。

在 commitRoot 函数中,当 DOM 更新之后,在 render 阶段生成的workInProgress被设置为current

root.current = finishedWork;

调用 Post-mutation 生命周期

在 commitRoot 函数中调用的最后一个函数是 commitAllLifeCycles,React 会在这个函数中调用 Post-mutation 生命周期函数。在我们的例子中,在 Render 阶段,React 会将 Update effect 添加到 ClickCounter fiber 上,在 commitAllLifeCycles 函数中会检查 Update effect:

function commitAllLifeCycles(finishedRoot, ...) {
    while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
        if (effectTag & (Update | Callback)) {
            const current = nextEffect.alternate;
            commitLifeCycles(finishedRoot, current, nextEffect, ...);
        }
        if (effectTag & Ref) {
            commitAttachRef(nextEffect);
        }
        nextEffect = nextEffect.nextEffect;
    }
}

如果有 ref,在 commitAllLifeCycles 中也会更新 ref,但是在我们的例子中没有 ref,所以 commitAttachRef 不会被调用。

由于我们 ClickCounter fiber 有 Update effect,所以 commitLifeCycles 会被调用,我们定义在组件实例上的 componentDidUpdate 最终是在 commitLifeCycles 中被调用的

function commitLifeCycles(finishedRoot, current, ...) {
  ...
  switch (finishedWork.tag) {
    case FunctionComponent: {...}
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.effectTag & Update) {
        if (current === null) {
          instance.componentDidMount();
        } else {
          ...
          instance.componentDidUpdate(prevProps, prevState, ...);
        }
      }
    }
    case HostComponent: {...}
    case ...
}

以上就是React 中state与props更新深入解析的详细内容,更多关于React中state props更新的资料请关注我们其它相关文章!

(0)

相关推荐

  • ReactQuery系列React Query 实践示例详解

    目录 引言 客户端状态 vs 服务端状态 React Query 关于默认行为的解释 使用React Query DevTools 把query key理解成一个依赖列表 一个新的缓存入口 把服务端状态和客户端状态分开 enabled属性是很强大的 创建自定义hook 引言 当2018年GraphQL特别是Apolllo Client开始流行之后,很多人开始认为它将替代Redux,关于Redux是否已经落伍的问题经常被问到. 我很清晰地记得我当时对这些观点的不理解.为什么一些数据请求的库会替代全

  • ReactQuery系列之数据转换示例详解

    目录 引言 数据转换 后端 查询函数中 render函数中 使用select配置 引言 欢迎来到“关于react-query我不得不说的一些事情”的第二章节.随着我越来越深入这个库以及他的社区,我发现一些人们经常会问到的问题.最开始,我计划在一篇超长的文章里面把这些都讲清楚,最终我还是决定将他们拆分成一些有意义的主题.今天第一个主题是一个很普遍但是很重要的事情:数据转换. 数据转换 我们不得不面对这个问题-大部分的人并没有使用GraphQL.如果你使用了,那么恭喜你,因为你可以请求到你期望的数据

  • React setState是异步还是同步原理解析

    目录 setState异步更新 那么为什么setState设计为异步呢? 如何获取异步的结果 setState一定是异步的吗? setState异步更新 开发中当组件中的状态发生了变化,页面并不会重新渲染.我们必须要通过setState来告知React数据已经发生了变化,重新渲染页面. 先来看下面的例子: constructor() { super(); this.state = { message: "Hello World", }; } changeText() { this.se

  • React 中state与props更新深入解析

    目录 正文 组件的 updater 处理 ClickCounter Fiber 的 update beginWork Reconciling children for the ClickCounter Fiber 处理 Span Fiber 的 update Reconciling children for the span fiber Effects list commit 阶段 应用 effects DOM updates 调用 Post-mutation 生命周期 正文 在这篇文章中,我使

  • 解决react中useState状态异步更新的问题

    目录 疑惑 状态异步更新带来的问题 问题示例 问题解决 类组件的解决方案 函数组件的解决方案 其他解决方案 结尾 疑惑 相信刚开始使用react函数组件的小伙伴也遇到过一个坑,就是 useState 更新状态是异步更新的,但是react 并没有提供关于这个问题的解决方案.那我们能否使用自己的方法来解决这个问题呢?答案肯定是可以的. 状态异步更新带来的问题 就拿一个比较常见的场景来说.在react项目中,我们想在关闭对话框后再去处理其他业务.但是 useState 的状态是异步更新的.我们通过se

  • 谈谈React中的Render Props模式

    概述 Render Props模式是一种非常灵活复用性非常高的模式,它可以把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件拥有这样的能力,接下来我们一步一步来看React组件中如何实现这样的功能. 简要介绍:分离UI与业务的方法一直在演进,从早期的mixins,到HOC,再到Render Prop,本文主要对比HOC,谈谈Render Props 1 . 早期的mixins 早期复用业务通过mixins来实现,比如组件A和组件B中,有一些公用函数,通过mixins剥离这些公用部分,并

  • React在组件中如何监听redux中state状态的改变

    目录 在组件中监听redux中state状态的改变 解决方式 React和redux的状态处理 在组件中监听redux中state状态的改变 解决方式 1.在组件中引入store 2.在constructor构造器方法中,重写store.subscribe方法(该方法即是监听state状态改变的放过) 组件完整代码如下: import React, { Component } from 'react' import CSSModules from 'react-css-modules'  imp

  • React 中 setState 的异步操作案例详解

    目录 前言 React 中的 setState 为什么需要异步操作? 什么时候setState会进行同步操作? 前言 在使用state的时候, 如果我们企图直接修改state中的某一个值之后直接打印(使用)他,就会发现,他其实并没有改变. 就像下面的例子,企图通过点击事件之后就使用修改之后的state的值,但是会发state中的并没有被立即修改,还是原先的值,我们都知道那是因为 setState就相当于是一个异步操作,不能立即被修改. import React, { Component } fr

  • 详解React中传入组件的props改变时更新组件的几种实现方法

    我们使用react的时候常常需要在一个组件传入的props更新时重新渲染该组件,常用的方法是在componentWillReceiveProps中将新的props更新到组件的state中(这种state被成为派生状态(Derived State)),从而实现重新渲染.React 16.3中还引入了一个新的钩子函数getDerivedStateFromProps来专门实现这一需求.但无论是用componentWillReceiveProps还是getDerivedStateFromProps都不是

  • vue和react中props变化后如何修改state

    目录 vue和react中props变化后修改state 改进 react改变state必须知道的知识点 1.不能直接修改state 2.state的更新是异步的 3.state的更新是一个合并的过程 state与不可变对象 vue和react中props变化后修改state 如果只想在 state 更改时重新计算某些数据,比如搜索框案例. vue <template> <div> <input type="text" v-model="filt

  • 解析React中useMemo与useCallback的区别

    useMemo 把“创建”函数和依赖项数组作为参数传⼊入useMemo,它仅会在某个依赖项改变时才重新计算memoized 值.这种优化有助于避免在每次渲染时都进⾏行行⾼高开销的计算. importReact, { useState, useMemo } from"react"; export default functionUseMemoPage(props) { const [count, setCount] =useState(0); constexpensive=useMemo

  • es6在react中的应用代码解析

    不论是React还是React-native,facebook官方都推荐使用ES6的语法,没在项目中使用过的话,突然转换过来会遇到一些问题,如果还没有时间系统的学习下ES6那么注意一些常见的写法暂时也就够用的,这会给我们的开发带来很大的便捷,你会体验到ES6语法的无比简洁.下面给大家介绍es6在react中的应用,具体内容如下所示: import React,{Component} from 'react'; class RepeatArrayextends Component{ constru

  • 代码解析React中setState同步和异步问题

    React起源于Facebook的内部项目.React的出现是革命性的创新,React的是一个颠覆式的前端框架.在React官方这样介绍的它:一个声明式.高效.灵活的.创建用户界面的JavaScript库,即使React的主要作用是构建UI,但是项目的逐渐成长已经使得react成为前后端通吃的WebApp解决方案. angular中用的是watcher对象,vue是观察者模式,react就是state了,他们各有各的特点,没有好坏之分,只有需求不同而选择不同. React的官方网址:https:

随机推荐