Vue props 原理分析

2,750 阅读2分钟

一:用法

​ vue的props属性用于父组件向子组件传递值,是一个高频使用的特性。

​ props的使用需要父子组件的配合,在父组件中使用v-bind绑定要传递的值,在子组件中的props属性中声明接受属性的类型和默认值。使用示例:

// 父组件 App.vue
<hello-world :testData="testData"></hello-world>

export default{
	name:'App',
    data(){
        return{
            testData:{}
        }
    }
}

// 子组件
export default {
	name:'HelloWorld',
    props:{
        testData:{
            type:Object,
            default:()=>{}
        }
    }
}

二、父组件App.vue中的处理

​ 从上面的使用示例可以看出,父组件的template的子组件标签上使用v-bind指令绑定了一个属性。我们知道template标签的内容会经过AST和Codegen,最终转换成render函数。

2.1:AST转换时的处理

​ AST转换时会对标签上的属性进行处理,对于v-bind的属性会在AST的节点上添加attrs数组属性

// vue源码 src\compiler\parser\index.js
function processAttrs (el){
    const list = el.attrsList
  let i, l, name, rawName, value, modifiers, syncGen, isDynamic
  for (i = 0, l = list.length; i < l; i++) {
      ......
      if (bindRE.test(name)) { // v-bind
          // line 842
      	addAttr(el, name, value, list[i], isDynamic)
      }
      ......
  }
}

2.2:Codegen时的处理

​ Codegen对每个AST节点进行转换处理,最终生成render函数;处理节点时,针对attrs属性的处理如下:

// vue源码 src\compiler\codegen\index.js
export function genData (el: ASTElement, state: CodegenState): string {
  let data = '{'
  ......
  // attributes
  if (el.attrs) {
    data += `attrs:${genProps(el.attrs)},`
  }
  ......
  return data
}

2.3:render函数中的形式

​ 我们看一下App.vue最终生成的render函数

// App.vue的render函数
var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c(
    "div",
    { attrs: { id: "app" } },
    [
      _c("img", { attrs: { src: require("./assets/logo.png") } }),
      _vm._v(" "),
      _c("HelloWorld", { attrs: { testData: _vm.testData } })
    ],
    1
  )
}

我们需要关注的是_c("HelloWorld", { attrs: { testData: _vm.testData } })

可以看到,在template中绑定的属性,最终设置成createElement的第二个参数,在创建HelloWorld组件时发挥作用。

三、子组件HelloWorld.vue中的处理

​ 子组件通过initProps方法将props属性绑定在vm._props上,并将其设置为响应式属性,最后通过proxy方法代理到vm上,达到直接通过this访问的目的。

// vue源码 src\core\instance\state.js
// 删除了部分代码
function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  for (const key in propsOptions) {
    const value = validateProp(key, propsOptions, propsData, vm)
    // 设置为响应式属性
    defineReactive(props, key, value)
    if (!(key in vm)) {
      // 代理到vm上,可以直接通过this.访问
      proxy(vm, `_props`, key)
    }
  }
}