vue 组件通信 props

22 篇文章 0 订阅
9 篇文章 0 订阅

porps

了解props源码主要是解决两个疑问

  1. 父组件怎么将值传递给子组件的
  2. 父组件的值更新后,子组件如何更新

父组件怎么将值传递给子组件的

答:我们写的节点模板会被解析并生成render函数,函数执行时会从父组件中获取到传参,子组件获取到传参后,会对其外层进行响应式绑定,并代理到vm上

详细的说,可以从两个部分来解释,分别是

父组件怎么传给子组件
子组件怎么处理传过来的参数

  1. 父组件怎么传给子组件
    我们写的节点模板会被解析并生成render函数、然后通过render函数最终生成一个vnode虚拟节点,render函数大概长这个样子
(function anonymous(
) {
with(this){return _c('div',[_c('children-dom',{attrs:{"qwe":value}})])}
})

这段代码看着很头疼,我们只需要简单了解一下,就是通过with方法将{}内的代码的作用域绑定为this(父级),目的是为了取到父级中的传参
然后我的节点是

<div><children-dom :qwe='value'></children-dom></div>

对应我的节点,内部调用了两次_c函数,并传了标签名attrs(标签上的属性,例子中会把qwe传过去)
_c函数就是createElement函数, 这个函数的作用就是生成一个 VNode虚拟节点节点,我们只需要关注在该函数处理组件时,会在attrs中筛选出props的内容并存在VNode虚拟节点中

/**
 * 提取父组件通过props传递的数据
 */
export function extractPropsFromVNodeData (
  data: VNodeData,
  Ctor: Class<Component>,
  tag?: string
): ?Object {
  // 我们这里只提取原始值。
  // 验证和默认值在子级中处理组件本身。
  const propOptions = Ctor.options.props // xxx : {type: string,...}
  if (isUndef(propOptions)) {
    return
  }
  const res = {}
  const { attrs, props } = data // {key: value}
  if (isDef(attrs) || isDef(props)) { // attrs|props有值
    for (const key in propOptions) {
      const altKey = hyphenate(key)
      if (process.env.NODE_ENV !== 'production') {
        const keyInLowerCase = key.toLowerCase()
      }
      checkProp(res, props, key, altKey, true) || checkProp(res, attrs, key, altKey, false) // 监测属性是否合法
    }
  }
  return res
}

执行结束后就生成了VNode

  1. 子组件怎么处理传过来的参数

接上边的流程,开始创建真实节点,通过_rendercreateElm_init等函数(跳过,只关注props相关的),在init中,会调用initInternalComponent创建$options并赋值父级传过来的数据,然后开始初始化属性,走到了处理props的函数initProps,在这里对props属性的外层进行响应式绑定,并将其代理到vm上,这样,我们就可以使用this.xxx获取组件的属性了

详见代码:

// 创建$options并赋值父级传过来的数据
function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  const parentVnode = options._parentVnode
  ...
  opts.propsData = parentVnode.componentOptions.propsData // 组件属性
  ...
}
...
// 初始化props
function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {} // 在vm上创建用于存储props的对象
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // 在此此处判断,如果当前vm不是根vm,就跳过递归绑定响应式(因为非根传过来的props本身就是响应式的数据)
  if (!isRoot) {
    // 把shouldObserve设为false 用于禁用递归绑定响应式
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm) // 校验value
    if (process.env.NODE_ENV !== 'production') { // 非开发环境抛出错误
      // 如果是保留属性抛出错误
      ...
      defineReactive(props, key, value,set时候抛出不允许修改的错误)
    } else {
      defineReactive(props, key, value)
    }
    // 将props代理到vm上,允许我们通过this[key]访问到props里的属性
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  // 把shouldObserve设为true 用于开启递归绑定响应式,对应上边关闭
  toggleObserving(true)
}
initInternalComponent

会判断如果是组件,就把上边说的props中的内容存到vm.$options上,此时你在子组件中this.$options就可以看到了~

initProps

跳过前边的变量创建,该函数主要干了以下几件事

  1. toggleObserving(false)
    首先看toggleObserving(false)这个函数的作用,这个函数会将响应式绑定中shouldObserve设为false,作用是改为false后再调用响应式绑定的时候,不会递归其子集进行响应式绑定
    比如我们从父级传到子集一个对象,对象在父级创建的时候就已经做过响应式绑定了,在传输的过程中,只是原封不动的拿了过来,然后对最外层做了响应式代理(外层做响应式代理目的是用于更新,下边会说道)
    if (!isRoot)的判断是因为如果是根vm的数据可能不是响应式的,要转换成响应式防止修改属性无法更新
  2. 将父组件传过来的值绑定到vm._props中,并对其调用defineReactive进行响应式绑定
  3. 将props代理到vm上,允许我们通过this[key]直接访问到props里的属性
  4. 再次调用toggleObserving(true)shouldObserve还原为true

父组件的值更新后,子组件如何更新

答:父传子的实现思路就是获取到父级传过来的属性并定义在自己的props上边,当父组件传过来的参数更新时,会重新执行render函数生成虚拟dom,然后diff对比更新子组件

代码流程:

在父组件中更新属性,首先我们看diff算法
首先我们要知道diff算法会根据新老两套vnode(虚拟dom)对比,排查节点是否可以复用,在排查到组件时,会找到老组件vnode和对应的新组建vnode,通过sameVnode判断得出该组件可以复用,走patchVnode,进行新老节点更替

function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    ...
    // 在diff算法中
    if () {
        ...
    } else if (sameVnode(oldStartVnode, newStartVnode)) { // 新老节点相同 --- 头头比较:老头<->新头
        // 走patchVnode(递归),进行新老节点更替
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
    }
    ...
}

patchVnode方法中,会调用prepatch方法,并在内部调用了updateChildComponent方法,用于更新vm上的一系列属性(包括props
两个函数都放在下边

// patchVnode 的作用就是把新的 vnode patch 到旧的 vnode 上
function patchVnode (
    oldVnode,
    vnode,
    insertedVnodeQueue,
    ownerArray,
    index,
    removeOnly
) {
    ...
    /**
     * 只有组件才会调用该函数
     * prepatch函数创建位置:create-component.js
     * prepatch函数内部调用了updateChildComponent方法(函数位置在lifecycle.js)
     * 最终更新了组件实例(oldVnode.componentInstance)中的一系列属性,并更新vnode的组件实例
     * (在内部会调用$forceUpdate())
     */
    let i
    const data = vnode.data
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
        i(oldVnode, vnode)
    }
    ...
}

// prepatch 方法就是拿到新的 vnode 的组件配置以及组件实例,去执行 updateChildComponent 方法
// updateChildComponent是在instance/lifecycle.js里定义的
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
}

updateChildComponent函数中会首先拿到子组件的vm._props,注意上边有说过,这个_props已经做过toggleObserving(false)(禁用递归绑定响应式)状态下的响应式绑定了,然后在这里直接对其进行赋值操作,就可以通过响应式方法更新子组件中的数据了!(在这段代码中也有toggleObserving(false),在上边已经说过原因,这里就不再重复了:)

export function updateChildComponent (
    vm: Component,
    propsData: ?Object,
    listeners: ?Object,
    parentVnode: MountedComponentVNode,
    renderChildren: ?Array<VNode>
) {
    ...
    // 更新props
    if (propsData && vm.$options.props) {
        // 把shouldObserve设为false 用于禁用递归绑定响应式
        toggleObserving(false)
        const props = vm._props // 注意这里的_props是已经进行过响应式绑定的(外层与子组件watcher绑定)
        const propKeys = vm.$options._propKeys || []
        for (let i = 0; i < propKeys.length; i++) {
            const key = propKeys[i]
            const propOptions: any = vm.$options.props // wtf flow?
            props[key] = validateProp(key, propOptions, propsData, vm) // 进行类型判断并返回新值
        }
        toggleObserving(true)
        // keep a copy of raw propsData
        vm.$options.propsData = propsData
    }
    ...
}

---------end---------

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值