Vue 子组件渲染次数与父组件 props 和子组件数据变化的关系?

一般来讲vue父组件的数据发生变化会触发父组件渲染,如果父组件传入子组件的props数据发生变化会触发子组件渲染,如果 vue 中 父组件的props和子组件的数据同时发生变化,子组件会渲染2次吗?

如题

阅读 1.3k
avatarAI BotBETA

在 Vue 中,当父组件的 props 数据发生变化时,子组件会被重新渲染。然而,子组件的数据变化并不会影响父组件的渲染次数。这是因为 Vue 的响应式原理是单向的:父组件通过 props 将数据传递给子组件,子组件内部的数据变化不会反馈给父组件。

如果父组件的 props 数据和子组件的数据同时发生变化,子组件的渲染次数取决于哪个变化先发生。如果父组件的 props 数据先发生变化,子组件会被重新渲染。如果子组件的数据先发生变化,由于 Vue 的懒加载机制,父组件可能不会感知到这个变化,因此不会触发重新渲染。

需要注意的是,如果子组件内部使用了 key 属性,并且 key 属性的值发生了变化,那么 Vue 会认为这是一个全新的子组件,会销毁旧的子组件并创建新的子组件,这样就会触发两次渲染。所以,如果需要让 Vue 感知子组件的数据变化并重新渲染,一种常用的方法是改变子组件的 key 属性值。

1 个回答

在Vue的源码中,当响应式数据发现改变的时候并不会立刻更新页面,而是执行执行queueWatcher函数,而这个函数内部维护着一个队列,而需要执行更新的组件会被推入到这个队列中,如果你在一个事件循环多次向这个队列中push同一个组件,它只会push一次,因为它每次push的时候都会拿你正在push的组件的id去跟队列中的组件的id比较,如果存在就不再push。Vue会把队列通过nextTick来执行,而nextTick也不是立刻执行的,它内部是以Promise,mutationObserver,setImmediate,setTimeout其中一个来执行,优先级就是上面的排列顺序,所以它就变成了一个异步任务。因此子组件只会更新一次。

Vue视图更新源码分析如下:

/**
*将观察程序推入观察程序队列。
*ID重复的作业将被跳过,除非
*在刷新队列时推送。
*/
export function queueWatcher(watcher: Watcher) {
    // 首先获取当前 watcher 的 id
  const id = watcher.id
  // 判断这个 watcher 是否已经存在,因为 Vue 并不是在数据发生变化之后立刻更新视图
  // 而是收集起来的,因此在上一次没有更新视图之前,即使数据发生改变也无需再添加,因为
  // watcher 更新视图的时候使用的数据依然是最新的数据
  if (has[id] != null) {
   // 已经存在就不再收集
    return
  }

  if (watcher === Dep.target && watcher.noRecurse) {
    return
  }

  has[id] = true
  if (!flushing) {
  // 否则把 watcher 放到一个队列中,在下一次事件循环的时候再一次性更新视图;
    queue.push(watcher)
  } else {
    // if already flushing, splice the watcher based on its id
    // if already past its id, it will be run next immediately.
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) {
      i--
    }
    queue.splice(i + 1, 0, watcher)
  }
  // queue the flush
  // 接着就开始执行渲染
  if (!waiting) {
    waiting = true

    if (__DEV__ && !config.async) {
      flushSchedulerQueue()
      return
    }
    // 执行渲染,可以看到它调用的就是 nextTick 方法;
    // flushSchedulerQueue 就是刷新页面的
    nextTick(flushSchedulerQueue)
  }
}


function flushSchedulerQueue() {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

//刷新前对队列进行排序。
//这样可以确保:
//1。组件从父级更新到子级。(因为父母总是
//在子项之前创建)
//2。组件的用户观察程序在其渲染观察程序之前运行(因为
//用户观察程序是在渲染观察程序之前创建的)
//3。如果组件在父组件的观察程序运行期间被破坏,
//它的观察者可以被跳过。
  queue.sort(sortCompareFn)

//不要缓存长度,因为可能会推送更多的观察者
//当我们运行现有的观察程序时
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    //在dev-build中,检查并停止循环更新。
    if (__DEV__ && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' +
            (watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`),
          watcher.vm
        )
        break
      }
    }
  }
推荐问题