main.js(入口文件):

import Vue from "vue";import App from "./app.vue";new Vue({  el: "#app",  data() {    return {      person: {        sex: "male",        name: "Jack",      },    };  },  template:    "<div><button @click='test'>测试</button><App :person='person'/></div>",  components: { App },  methods: {    test() {      this.person = {        name: "Mike",      };    },  },});

app.vue:

<template>  <div>{{person.name}}<button @click="test">测试</button></div></template><script>  // app组件选项  export default {    name: "app",    props: ["person"],    methods: {      test() {        this.person.name = "Rose";      },    },  };</script>

1.组件选项中的 props 存在多种格局,最终都会调用normalizeProps格式化 props

// 格式化propsfunction normalizeProps(options, vm) {  var props = options.props;  if (!props) {    return;  }  var res = {};  var i, val, name;  if (Array.isArray(props)) {    i = props.length;    while (i--) {      val = props[i];      if (typeof val === "string") {        name = camelize(val);        res[name] = { type: null };      } else if (process.env.NODE_ENV !== "production") {        warn("props must be strings when using array syntax.");      }    }  } else if (isPlainObject(props)) {    for (var key in props) {      val = props[key];      name = camelize(key);      res[name] = isPlainObject(val) ? val : { type: val };    }  } else if (process.env.NODE_ENV !== "production") {    warn(      'Invalid value for option "props": expected an Array or an Object, ' +        "but got " +        toRawType(props) +        ".",      vm    );  }  options.props = res;}

2.main.js 中的根组件渲染页面时,先调用 render 函数生成组件内标签节点(VNode 实例)。生成 app 组件标签节点时,读取根组件的数据person赋值给 props 属性personprops 数据保留在组件标签节点上。

读取 person 属性时触发属性的 get 办法,收集组件更新的观察者。当批改 person 属性时会触发属性的 set 办法,告诉组件更新页面。
// main.js中根组件的template生成的render函数function render() {  return _c(    "div",    [      _c("button", { on: { click: test } }, [_v("测试")]),      _c(        "App",        { attrs: { person: person } } // app组件标签上的props数据      ),    ],    1  );}
var vnode = new VNode(  "vue-component-" + Ctor.cid + (name ? "-" + name : ""),  data,  undefined,  undefined,  undefined,  context,  {    Ctor: Ctor,    propsData: propsData,    listeners: listeners,    tag: tag,    children: children,  }, // propsData保留了props数据  asyncFactory);

3.当渲染 app 组件标签时,会依据组件标签节点创立组件,并初始化组件数据。调用initProps初始化 props,遍历组件选项中的 props 属性。依据 app 组件标签上的 props 数据(propsData),调用validateProp,获取属性对应的值。而后应用 defineProperty 定义 props 对象上的属性,增加getset办法,设置 props 属性响应式,同时监听属性变动,当 props 上的属性批改时输入正告提醒。最初组件代理 props 属性,能够在组件上间接拜访 props 属性。

initProps中定义了 props 对象上的属性,未定义属性值中的对象的属性。批改 props 对象属性的值时(例:this.person = { name: 'Rose' }),有正告提醒,但批改值中的对象属性时(例:this.person.name = 'Peter'),没有正告提醒。然而页面同样会更新,因为属性值来自于父组件中的 data,父组件中的 data 设置了响应式。
// 初始化props,设置props属性值function initProps(vm, propsOptions) {  // propsOptions组件选项上的props  var propsData = vm.$options.propsData || {}; //组件标签上props数据  var props = (vm._props = {});  var keys = (vm.$options._propKeys = []);  var isRoot = !vm.$parent;  if (!isRoot) {    // 不是根组件    toggleObserving(false); // 更改设置响应式标识(设置props对象属性响应式,不设置对象属性值响应式(对象属性值的响应式可能来自于父组件data数据)  }  var loop = function (key) {    keys.push(key);    var value = validateProp(key, propsOptions, propsData, vm); // 依据组件标签上的prop获取对应的值    /* istanbul ignore else */    if (process.env.NODE_ENV !== "production") {      var hyphenatedKey = hyphenate(key);      if (        isReservedAttribute(hyphenatedKey) ||        config.isReservedAttr(hyphenatedKey)      ) {        warn(          '"' +            hyphenatedKey +            '" is a reserved attribute and cannot be used as component prop.',          vm        );      }      defineReactive$$1(props, key, value, function () {        // 定义props上的属性,设置get,set办法。        if (!isRoot && !isUpdatingChildComponent) {          warn(            // 如果更改了props对象属性的值正告            "Avoid mutating a prop directly since the value will be " +              "overwritten whenever the parent component re-renders. " +              "Instead, use a data or computed property based on the prop's " +              'value. Prop being mutated: "' +              key +              '"',            vm          );        }      });    } else {      defineReactive$$1(props, key, value);    }    if (!(key in vm)) {      proxy(vm, "_props", key); // props挂载到组件上    }  };  for (var key in propsOptions) loop(key);  toggleObserving(true);}
/** * 获取props属性值 */function validateProp(  key, // 组件属性字段  propOptions, // 组件属性配置  propsData, // 组件props属性值  vm) {  var prop = propOptions[key];  var absent = !hasOwn(propsData, key);  var value = propsData[key];  // boolean casting  var booleanIndex = getTypeIndex(Boolean, prop.type);  if (booleanIndex > -1) {    if (absent && !hasOwn(prop, "default")) {      value = false; // Boolean类型未赋值且没有默认值,则默认为false    } else if (value === "" || value === hyphenate(key)) {      // 值为空或者和属性名雷同      // only cast empty string / same name to boolean if      // boolean has higher priority  Boolean类型领有较高的优先级      var stringIndex = getTypeIndex(String, prop.type);      if (stringIndex < 0 || booleanIndex < stringIndex) {        // 类型不包含字符串或者Boolean类型靠前(优先)        value = true;      }    }  }  // check default value  if (value === undefined) {    value = getPropDefaultValue(vm, prop, key);    // since the default value is a fresh copy,    // make sure to observe it.    var prevShouldObserve = shouldObserve;    toggleObserving(true);    observe(value); // 对默认值设置响应式    toggleObserving(prevShouldObserve);  }  if (    process.env.NODE_ENV !== "production" &&    // skip validation for weex recycle-list child component props    !false  ) {    assertProp(prop, key, value, vm, absent);  }  return value;}

4.初始化 dataprops 等 app 组件数据后,开始渲染页面。调用 render 时创立标签节点,顺次读取了 props 属性上的 personname 属性。依据创立的标签节点生成 DOM 元素,增加到页面中。

读取 person 属性时,app 组件 props 中的 person 属性会收集 app 组件更新的观察者。读取 name 属性时,根组件 data 中的 name 属性会收集 app 组件更新的观察者。
// app组件template生成的render函数function render() {  var _vm = this;  var _h = _vm.$createElement;  var _c = _vm._self._c || _h;  return _c("div", [    _vm._v(_vm._s(_vm.person.name)), // 顺次读取了props属性上的person和name属性    _c("button", { on: { click: _vm.test } }, [_vm._v("测试")]),  ]);}

5.点击根组件中的按钮触发 test 函数,批改 person 时,将会触发根组件页面更新,更新过程中调用updateChildComponent更新子组件标签上的数据,批改 props 的属性 person,触发 set 办法,告诉 app 组件也更新页面,app 组件将依据批改后的 props 属性值渲染页面。

function updateChildComponent (  vm,  propsData,  listeners,  parentVnode,  renderChildren) {  if (process.env.NODE_ENV !== 'production') {    isUpdatingChildComponent = true;// 标识阐明正在更新组件  }    ...  vm.$attrs = parentVnode.data.attrs || emptyObject;  vm.$listeners = listeners || emptyObject;  // update props  if (propsData && vm.$options.props) {    toggleObserving(false);    var props = vm._props;    var propKeys = vm.$options._propKeys || [];    for (var i = 0; i < propKeys.length; i++) {      var key = propKeys[i];      var propOptions = vm.$options.props; // wtf flow?      props[key] = validateProp(key, propOptions, propsData, vm);// 更新props属性值    }    toggleObserving(true);    // keep a copy of raw propsData    vm.$options.propsData = propsData;  }    ...  if (process.env.NODE_ENV !== 'production') {    isUpdatingChildComponent = false;  }}

6.点击 app 组件中的按钮触发 test 函数,批改 person 上的 name 属性,触发 name 属性上的 set 办法,告诉 app 组件更新页面。

在根组件内批改 personname 属性,和在 app 组件内批改 personname 属性是一样的,都会触发 app 组件页面更新。