vueソース解析-propメカニズム


コンポーネント化の開発では、子親コンポーネントの通信は直感的であればあるほど簡単であるに違いない.vueは優れたmvvmフレームワークの中の子親通信として簡単に明らかにしなければならない.vue 1に比べて.vue 2はdispatch,emitなどの子親通信方式を削除し,vueの性能を大幅に向上させた.本当に複雑な論理はvuexに任せます.今回はよく知られていないpropを見てみましょう.vueで.サブコンポーネントで使用するために、親コンポーネントからサブコンポーネントにデータを渡すことがよくあります.まず、次の最も簡単な例を見てみましょう.


    


{{a}}
Vue.component('my-component', { name: 'my-component', props: ['test'], template: '<div>A custom component!{{test}}</div>', created(){ console.log(this); }, mounted(){ console.log(this); } }) new Vue({ el: '#app', data: function () { return { heihei:3333 } }, created(){ }, methods: { } })

上は最も簡単なprop伝値の問題で、親コンポーネントは自分のheiheiを伝入します.私たちはやはりvueの内部で何が起こったのか見てみましょう.その前に.まず、vueの応答式原理とvueがどのように巧みに再帰的にコンポーネントを構築しているかを見てみることをお勧めします.次に、vueライフサイクルのpropに関する部分だけに注目します.まずvueインスタンスの作成を開始します.compileがASTを生成するときは自然とattrの属性として扱われる.仮想domを作成するとき.コンポーネントが仮想domを作成するための関数を見てみましょう
function createComponent (
  Ctor,
  data,
  context,
  children,            // render           。                    。          ,           
  tag
) {
  if (isUndef(Ctor)) {
    return
  }

  var baseCtor = context.$options._base;//   vue   

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);//     ,            
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    {
      warn(("Invalid Component definition: " + (String(Ctor))), context);
    }
    return
  }

  // async component
  if (isUndef(Ctor.cid)) {
    Ctor = resolveAsyncComponent(Ctor, baseCtor, context);//            
    if (Ctor === undefined) {
      // return nothing if this is indeed an async component
      // wait for the callback to trigger parent update.
      return
    }
  }

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor);//      vue          ,  vue.mixin       options

  data = data || {};

  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data);
  }

  // extract props
  var propsData = extractPropsFromVNodeData(data, Ctor, tag);//           prop。    ({test:333})

  // functional component
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  var listeners = data.on;//               listeners 。          。  $on  
  // replace with listeners with .native modifier
  data.on = data.nativeOn;//     native           data.on。     html    。  el.addeventlisten  api

  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners
    data = {};
  }

  // merge component management hooks onto the placeholder node
  mergeHooks(data);//     dom   ,                    data 

  // return a placeholder vnode
  var name = Ctor.options.name || tag;
  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 }//       dom
  );
  return vnode
}

仮想domを作成するとき.親コンポーネントのpropは、vnodeのcomponentOptionsのpropsdataオプションに配置されます.実際のdomを作成するとき.このコンポーネントは自然に再インスタンス化されます.インスタンス化は次の関数を呼び出します.ここがコアです.
function createComponentInstanceForVnode (
  vnode, // we know it's MountedComponentVNode but flow doesn't
  parent, // activeInstance in lifecycle state
  parentElm,
  refElm
) {
  var vnodeComponentOptions = vnode.componentOptions;
  var options = {
    _isComponent: true,
    parent: parent,
    propsData: vnodeComponentOptions.propsData,//     props             options 。          
    _componentTag: vnodeComponentOptions.tag,
    _parentVnode: vnode,
    _parentListeners: vnodeComponentOptions.listeners,//     .native     
    _renderChildren: vnodeComponentOptions.children,//        。          $slots
    _parentElm: parentElm || null,
    _refElm: refElm || null
  };
  // check inline-template render functions
  var inlineTemplate = vnode.data.inlineTemplate;
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render;
    options.staticRenderFns = inlineTemplate.staticRenderFns;
  }
  return new vnodeComponentOptions.Ctor(options)//     
}


//                ,                     。  。   。       initState,        if (opts.props) { initProps(vm, opts.props); }。    initprops     ,      

function initProps (vm, propsOptions) {
  var propsData = vm.$options.propsData || {};//  props    
  var props = vm._props = {};
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  var keys = vm.$options._propKeys = [];
  var isRoot = !vm.$parent;
  // root instance props should be converted
  observerState.shouldConvert = isRoot;
  var loop = function ( key ) {//
    keys.push(key);
    var value = validateProp(key, propsOptions, propsData, vm);//       。    ,       
    /* istanbul ignore else */
    {
      if (isReservedProp[key] || config.isReservedAttr(key)) {
        warn(
          ("\"" + key + "\" is a reserved attribute and cannot be used as component prop."),
          vm
        );
      }
      defineReactive$$1(props, key, value, function () {
        if (vm.$parent && !observerState.isSettingProps) {
          warn(
            "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
          );
        }
      });
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      proxy(vm, "_props", key);
    }
  };

  for (var key in propsOptions) loop( key );//  props       。       
  observerState.shouldConvert = true;
}


function validateProp (
  key,
  propOptions,
  propsData,
  vm
) {
  var prop = propOptions[key];
  var absent = !hasOwn(propsData, key);
  var value = propsData[key];
  // handle boolean props
  if (isType(Boolean, prop.type)) {
    if (absent && !hasOwn(prop, 'default')) {
      value = false;
    } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) {
      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 prevShouldConvert = observerState.shouldConvert;
    observerState.shouldConvert = true;
    observe(value);//        
    observerState.shouldConvert = prevShouldConvert;
  }
  {
    assertProp(prop, key, value, vm, absent);
  }
  return value
}

インスタンス化されたときだからです.サブコンポーネントが受け取ったpropsにも応答式があります.サブアセンブリをレンダリングするとき.このプロパティのdep(メッセージステータ)は、サブコンポーネントのwatcher pushを入れます.サブコンポーネントが現在のプロパティを自分で変更すると.サブコンポーネントはre-renderを再表示します.親コンポーネントの値は変更されませんが、サブコンポーネントがオブジェクトである場合は受け入れられます.結果は違います.ここではobserve(value);//独自のレスポンスを作成します.簡単に言えば.propsが親コンポーネントのオブジェクトに渡された場合.では、このオブジェクトのプロパティのgetter、setterは親コンポーネントに作成されています.observe(value)では、作成した応答式のオブジェクトに対して応答式を繰り返し作成しません.したがって、このオブジェクトには親コンポーネントのre-render関数も保持されます.サブコンポーネントが自分でこの値を変更すると.はっきり言った.オブジェクトはすべてメモリに含まれています.サブコンポーネントがこのオブジェクトを変更しました.では、このオブジェクトを引用したものはすべて変わります.出発する前に親コンポーネントtouchフェーズで押し込まれた親コンポーネントのre-renderのリスニングが行われます.いったん値が変わったら.親コンポーネントはrerenderを再表示します.皆さん、よく見てください.
ここまで.オブジェクトでない場合.単純な割り当てです親コンポーネントがこのtestの値を指定した場合.親コンポーネントはre-renderに入ります.このときpatchコーナーが行われます.新旧vnodeを比較します.差分更新コンポーネントにぶつかると.次の関数が表示されます.
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    if (oldVnode === vnode) {
      return
    }
    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    if (isTrue(vnode.isStatic) &&
        isTrue(oldVnode.isStatic) &&
        vnode.key === oldVnode.key &&
        (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {
      vnode.elm = oldVnode.elm;//  。       elm      vnode
      vnode.componentInstance = oldVnode.componentInstance;//  ,                vnode
      return
    }
    var i;
    var data = vnode.data;
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {//     。       prepacth    prepacth  
      i(oldVnode, vnode);
    }
    var elm = vnode.elm = oldVnode.elm;
    var oldCh = oldVnode.children;
    var ch = vnode.children;
    if (isDef(data) && isPatchable(vnode)) {
      for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }
      if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }
    }
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
        if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
      } else if (isDef(ch)) {
        if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
      } else if (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      } else if (isDef(oldVnode.text)) {
        nodeOps.setTextContent(elm, '');
      }
    } else if (oldVnode.text !== vnode.text) {
      nodeOps.setTextContent(elm, vnode.text);
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); }
    }
  }




prepatch: function prepatch (oldVnode, vnode) {// re-render   。patch      。               props  
    var options = vnode.componentOptions;
    var child = vnode.componentInstance = oldVnode.componentInstance;
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    );
  },



function updateChildComponent (
  vm,
  propsData,
  listeners,
  parentVnode,
  renderChildren
) {
  // determine whether component has slot children
  // we need to do this before overwriting $options._renderChildren
  var hasChildren = !!(
    renderChildren ||               // has new static slots
    vm.$options._renderChildren ||  // has old static slots
    parentVnode.data.scopedSlots || // has new scoped slots
    vm.$scopedSlots !== emptyObject // has old scoped slots
  );

  vm.$options._parentVnode = parentVnode;
  vm.$vnode = parentVnode; // update vm's placeholder node without re-render
  if (vm._vnode) { // update child tree's parent
    vm._vnode.parent = parentVnode;
  }
  vm.$options._renderChildren = renderChildren;

  // update props
  if (propsData && vm.$options.props) {
    observerState.shouldConvert = false;
    {
      observerState.isSettingProps = true;
    }
    var props = vm._props;
    var propKeys = vm.$options._propKeys || [];
    for (var i = 0; i < propKeys.length; i++) {
      var key = propKeys[i];
      props[key] = validateProp(key, vm.$options.props, propsData, vm);
    }
    observerState.shouldConvert = true;
    {
      observerState.isSettingProps = false;
    }
    // keep a copy of raw propsData
    vm.$options.propsData = propsData;
  }
  // update listeners
  if (listeners) {
    var oldListeners = vm.$options._parentListeners;
    vm.$options._parentListeners = listeners;
    updateComponentListeners(vm, listeners, oldListeners);
  }
  // resolve slots + force update if has children
  if (hasChildren) {
    vm.$slots = resolveSlots(renderChildren, parentVnode.context);
    vm.$forceUpdate();
  }
}

ここでpatchでは新旧の2つの編成を比較する場合.コンポーネントが親コンポーネントからpropsを渡す場合があるためです.propsには必ず応答式があります.コールバックはサブコンポーネントのrender関数です.では、ここでの割り当ては必ずサブコンポーネントを再レンダリングします.サブアセンブリ自体のpatchサイクルに入ります.このようなコンポーネントは、自分で非同期で更新できます.親コンポーネントはまずコンポーネントを管理せず、自分でご飯を食べて帰って次のノードを更新します.
まとめ:総じて言えば.最も核心的な部分は、親コンポーネントからサブコンポーネントに渡されるpropオプションです.サブコンポーネントがインスタンス化されると、独自の応答式が作成されます.これは最も核心的なものです.皆さんはよく体得することができます.あら.急に忙しい.詳しくは言えません.すみません.後日補充します