vue中props实现的原理

45 阅读2分钟

Vue的props实现原理

这是一道常见的面试题——vue中的props的实现原理 我们都知道,在使用组件的时候可以传入一些属性:

<MyComponent a=1></MyComponent>
 =>ast语法树 => vnode

简而言之我们写的这代码片段会通过ast语法树 解析成JS语法 最后会把这个语法生成所谓的虚拟节点vnode

那么虚拟节点产生的时候会把这个传入的属性会放在哪?

以下是源码片段源码文件

  // 源码文件:create-component.js
  // return a placeholder vnode
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, //组件的名字
    data, undefined, undefined, undefined, context,    //组件的数据
    { Ctor, propsData, listeners, tag, children },  // propsData
    asyncFactory
  )

源码中定义了vnode,分别有组件的名字、数据之类的。 后面会把当初传入的所有的属性放到propsData中,这里面就放的所有属性的定义,所以会将属性放到虚拟节点上,等会组件初始化的时候会进行一系列的操作

    //源码文件:init.js
  export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData  // 这里!!!
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

组件初始化的时候opts.propsData = vnodeComponentOptions.propsData 意为会拿到propsData 而且把这个propsData定义到了我们当前的VM的$optionsopts.propsData 把我们默认虚拟节点的属性对象附到了我们当前示例上,接下来

//源码:state.js
//propsOptions 用户定义的属性
function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const 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
  // root instance props should be converted
  if (!isRoot) { //如果是根元素 属性需要定义成响应式的
    toggleObserving(false)
  }//否则属性是从父组件传递过来的,我们不需要将属性定义响应式
  for (const key in propsOptions) {
    keys.push(key)//校验用户定义的属性和传入属性
    const value = validateProp(key, propsOptions, propsData, vm)
  ...
  } else {
      defineReactive(props, key, value)
    }
  ...

当我们初始化时可以通过vm.$options.propsData 拿到用户传入的数据(比如开篇的 a=1) ,那么接收的属性都在propsOptions里, 进一步声明vm._props = {}声明一个空对象,之后会判断是否为根元素如果是根元素 属性需要定义成响应式的,否则属性是从父组件传递过来的,我们不需要将属性定义响应式。 再之后会去循环用户定义的propos选项(for (const key in propsOptions))相当于

propos:{a:{type:String}}

根据用户定义的个对象,和我们用户传入的这样一个值进行一个校验,并且拿到最终的结果。如果是根元素靠defineReactive(props, key, value)把当前的Key和Value定义到我们的_props实现响应式 (那不是根元素的话就不会将它定义成响应式的,因为它本身这个属性就是从父组件传递给子组件的,所以它一定也是响应式的了)

最后的最后把属性都定义到了vm._props中,最后将vm._props定义到示例中,这样就可以拿到传入的属性