首页 > 作文

Vue.js 源码分析(十二) 基础篇 组件详解

更新时间:2023-04-06 07:51:44 阅读: 评论:0

组件是可复用的vue实例,一个组件本质上是一个拥有预定义选项的一个vue实例,组件和组件之间通过一些属性进行联系。

组件有两种注册方式,分别是全局注册和局部注册,前者通过vue.component()注册,后者是在创建vue实例的时候在components属性里指定,例如:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="vue.js"></script></head><body>    <div id="app">        <child title="hello wrold"></child>        <hello></hello>        <button @click="test">测试</button>    </div>    <script>        vue.component('child',{                     //全局注册            props:['title'],            template:"<p>{{title}}</p>"        })        var app = new vue({            el:'#app',            components:{                hello:{template:'<p>hello vue</p>'} //局部组件            },            methods:{                test:function(){                    console.log(this.$children)                                               console.log(this.$children[1].$parent ===this)                        }            }        })    </script></body></html>

渲染dom为:

其中hello world是全局注册的组件渲染出来的,而hello vue是局部组件渲染出来的。

我们在测试按钮上绑定了一个事件,点击按钮后输出如下:

可以看到vue实例的$children属性是个数组,对应的是当前实例引用的所有组件的实例,其中$children[0]是全局组件child的实例,而children[1]是局部组件hello的实例。

而this.$children[1].节水征文$parent ===this输出为true则表审计总结示对于组件实例来说,它的$parent指向的父组件实例,也就是例子里的根组件实例。

vue内部也是通过$children和$parent属性实现了组件和组件之间的关联的。

组件是可以无限复用的,比如:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="vue.js"></script></head><body>    <div id="app">        <child title="hello wrold"></child>        <child title="hello vue"></child>        <child title="hello ro"></child>    </div>    <script>        vue.component('child',{                               props:['title'],            template:"<p>{{title}}</p>"        })        var app = new vue({el:'#app'})    </script></body></html>

渲染为:

注:对于组件来说,需要把data属性设为一个函数,内部返回一个数据对象,因为如果只返回一个对象,当组件复用时,不同的组件引用的data为同一个对象,这点和根vue实例不同的,可以看官网的例子:点我点我

例1:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="vue.js"></script></head><body>    <div id="app">        <child ></child>    </div>    <script>        vue.component('child',{                data:{title:"hello vue"},            template:"<p>{{title}}</p>"        })        var app = new vue({el:'#app'})    </script></body></html>

运行时浏览器报错了,如下:

报错的内部实现:vue注册组件时会先执行vue.extend(),然后执行mergeoptions合并一些属性,执行到data属性的合并策略时会做判断,如下:

strats.data = function (              //data的合并策略          第1196行  parentval,  childval,  vm) {  if (!vm) {                            //如果vm不存在,对于组件来说是不存在的    if (childval && typeof childval !== 'function') {     //如果值不是一个函数,则报错      "development" !== 'production' && warn(        'the "data" option should be a function ' +        'that returns a per-instance value in component ' +        'definitions.',        vm      );      return parentval    }    return mergedataorfn(parentval, childval)  }  return mergedataorfn(parentval, childval, vm)};

源码分析

以这个例子为例:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="vue.js"></script></head><body>    <div id="app">        <child title="hello wrold"></child>     </div>    <script>        vue.component('child',{            props:['title'],            template:"<p>{{title}}</p>"        })        var app = new vue({el:'#app',})    </script></body></html>

vue内部会执行initglobalapi()函数给大vue增加一些静态方法,其中会执行一个initastregisters函数,该函数会给vue的原型增加一个vue.component、vue.directive和vue.filter函数函数,分别用于注册组件、指令和过滤器,如下

function initastregisters (vue) {       //初始化component、directive和filter函数 第4850行  /**   * create ast registration methods.   */  ast_types.foreach(function (type) {     //遍历//ast_types数组 ast_types是一个数组,定义在339行,等于:['component','directive','filter']    vue[type] = function (      id,      definition    ) {      if (!definition) {        return this.options[type + 's'][id]      } el {        /* istanbul ignore if */        if ("development" !== 'production' && type === 'component') {          validatecomponentname(id);        }        if (type === 'component' && isplainobject(definition)) {      //如果是个组件          definition.name = definition.name || id;          definition = this.options._ba.extend(definition);           //则执行vue.extend()函数     ;this.options._ba等于大vue,定义在5050行        }        if (type === 'directive' && typeof definition === 'function') {          definition = { bind: definition, update: definition };        }        this.options[type + 's'][id] = definition;           //将definition保存到this.options[type + 's']里,例如组件保存到this.options['component']里面        return definition      }    };  });}

vue.extend()将使用基础vue构造器,创建一个“子类”。参数是一个包含组件选项的对象,也就是注册组件时传入的对象,如下:

  vue.extend = function (extendoptions) {       //初始化vue.extend函数  第4770行    extendoptions = extendoptions || {};    var super = this;    var superid = super.cid;    var cachedctors = extendoptions._ctor || (extendoptions._ctor = {});    if (cachedctors[superid]) {      return cachedctors[superid]    }    var name = extendoptions.name || super.options.name;    if ("development" !== 'production' && name) {      validatecomponentname(name);    }    var sub = function vuecomponent (options) {             //定义组件的构造函数,函数最后会返回该函数      this._init(options);    };    /*中间进行一些数据的合并*/    // cache constructor    cachedctors[superid] = sub;    return sub  };}

以例子为例,当加载完后,我们在控制台输入console.log(vue.options[“components”]),输出如下:

可以看到child组件的构造函数被保存到vue.options[“components”][“child“]里面了。其他三个keepalive、transition和transitiongroup是vue的内部组件

当vue加载时会执行模板生成的render函数,例子里的render函数等于:

执行_c(‘child’,{attrs:{“title”:”hello wrold”}})函数时会执行vm.$createelement()函数,也就是vue内部的createelement函数,如下

function createelement (      //创建vnode 第4335行  context,  tag,  data,  children,  normalizationtype,  alwaysnormalize) {  if (array.isarray(data) || isprimitive(data)) {     //如果data是个数组或者是基本类型    normalizationtype = children;    children = data;                                      //修正data为children    data = undefined;                                     //修正data为undefined  }  if (istrue(alwaysnormalize)) {    normalizationtype = always_normalize;  }  return _createelement(context, tag, data, children, normalizationtype)    //再调用_createelement}function _createelement (     //创建vnode  context,                       //context:vue对象  tag,                           //tag:标签名或组件名  data,  children,  normalizationtype) {  /*略*/  if (typeof tag === 'string') {      //如果tag是个字符串    var ctor;    ns = (context.$vnode && context.$vnode.ns) || config.gettagnamespace(tag);    if (config.isrervedtag(tag)) {                                                //如果tag是平台内置的标签      // platform built-in elements      vnode = new vnode(                                                                //调用new vnode()去实例化一个vnode        config.parplatformtagname(tag), data, children,           undefined, undefined, context      );    } el if (isdef(ctor = resolveast(context.$options, 'components', tag))) {   //如果该节点名对应一个组件,挂载组件时,如果某个节点是个组件,则会执行到这里      // component        vnode = createcomponent(ctor, data, context, children, tag);                    //创建组件vnode    } el {      // unknown or unlisted namespaced elements      // check at runtime becau it may get assigned a namespace when its      // parent normalizes children      vnode = new vnode(        tag, data, children,        undefined, undefined, context      );    }  } el {    // direct component options / constructor    vnode = createcomponent(tag, data, context, children);  }  if (array.isarray(vnode)) {    return vnode  } el if (isdef(vnode)) {    if (isdef(ns)) { applyns(vnode, ns); }    if (isdef(data)) { registerdeepbindings(data); }    retu小议慎独rn vnode                                                                    //最后返回vnode  } el {    return createemptyvnode()  }}
resolveast()用于获取资源,也就是获取组件的构造函数(在上面vue.extend里面定义的构造函数),定义如下:
function resolveast (       //获取资源 第1498行  options,  type,  id,  warnmissing) {  /* istanbul ignore if */  if (typeof id !== 'string') {    return  }  var asts = options[type];  // check local registration variations first  if (hasown(asts, id)) { return asts[id] }                        //先从当前实例上找id  var camelizedid = camelize(id);  if (hasown(asts, camelizedid)) { return asts[camelizedid] }     //将id转化为驼峰式后再找  var pascalcaid = capitalize(camelizedid);  if (hasown(asts, pascalcaid)) { return asts[pascalcaid] }   //如果还没找到则尝试将首字母大写查找  // fallback to prototype chain  var res = asts[id] || asts[camelizedid] || asts[pascalcaid];  //最后通过原型来查找  if ("development" !== 'production' && warnmissing && !res) {    warn(      'failed to resolve ' + type.slice(0, -1) + ': ' + id,      options    );  }  return res}

例子里执行到这里时就可以获取到在vue.extend()里定义的sub函数了,如下:

我们点击这个函数时会跳转到sub函数,如下:

回到_createelement函数,获取到组件的构造函数后就会执行createcomponent()创建组件的vnode,这一步对于组件来说很重要,它会对组件的data、options、props、自定义事件、钩子函数、原生事件、异步组件分别做一步处理,对于组件的实例化来说,最重要的是安装钩子吧,如下:

function createcomponent (      //创建组件vnode 第4182行 ctor:组件的构造函数  data:数组 context:vue实例  child:组件的子节点  ctor,  data,  context,  children,  tag) {  /*略*/  // install component management hooks onto the placeholder node  installcomponenthooks(data);                //安装一些组件的管理钩子  /*略*/    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 },    asyncfactory  );                                          //创建组件vnode  return vnode                                //最后返回vnode}

installcomponenthooks()会给组件安装一些管理钩子,如下:

function installcomponenthooks (datacos平方) {         //安装组件的钩子 第4307行  var hooks = data.hook || (data.hook = {});        //尝试获取组件的data.hook属性,如果没有则初始化为空对象  for (var i = 0; i < hookstomerge.length; i++) {   //遍历hookstomerge里的钩子,保存到hooks对应的key里面    var key = hookstomerge[i];    hooks[key] = componentvnodehooks[key];  }}

componentvnodehooks保存了组件的钩子,总共有四个:init、prepatch、inrt和destroy,对应组件的四个不同的时期,以例子为例执行完后data.hook等于如下:

最太阳系和银河系后将虚拟vnode渲染为真实dom节点的时候会执行n createelm()函数,该函数会优先执行createcomponent()函数去创建组件,如下:

  function createcomponent (vnode, inrtedvnodequeue, parentelm, refelm) {     //创建组件节点 第5590行   ;注:这是patch()函数内的createcomponent()函数,而不是全局的createcomponent()函数    var i = vnode.data;                                                               //获取vnode的data属性    if (isdef(i)) {                                                                   //如果存在data属性(组件vnode肯定存在这个属性,普通vnode有可能存在)      var isreactivated = isdef(vnode.componentinstance) && i.keepalive;              //这是keepalive逻辑,可以先忽略      if (isdef(i = i.hook) && isdef(i = i.init)) {                                   //如果data里定义了hook方法,且存在init方法        i(vnode, fal /* hydrating */, parentelm, refelm);      }      // after calling the init hook, if the vnode is a child component      // it should've created a child instance and mounted it. the child      // component also has t the placeholder vnode's elm.      // in that ca we can just return the element and be done.      if (isdef(vnode.componentinstance)) {        initcomponent(vnode, inrtedvnodequeue);        if (istrue(isreactivated)) {          reactivatecomponent(vnode, inrtedvnodequeue, parentelm, refelm);        }        return true      }    }  }

createcomponent会去执行组件的init()钩子函数:

  init: function init (         //组件的安装 第4110行    vnode,                        //vnode:组件的占位符vnode    hydrating,                    //parentelm:真实的父节点引用    parentelm,                    //refelm:参考节点    refelm  ) {    if (                                                        //这是keepalive逻辑      vnode.componentinstance &&      !vnode.componentinstance._isdestroyed &&      vnode.data.keepalive    ) {      // kept-alive components, treat as a patch      var mountednode = vnode; // work around flow      componentvnodehooks.prepatch(mountednode, mountednode);    } el {      var child = vnode.componentinstance = createcomponentinstanceforvnode(      //调用该方法返回子组件的vue实例,并保存到vnode.componentinstance属性上        vnode,        activeinstance,        parentelm,        refelm      );      child.$mount(hydrating ? vnode.elm : undefined, hydrating);    }  },

createcomponentinstanceforvnode会创建组件的实例,如下:

function createcomponentinstanceforvnode (      //第4285行 创建组件实例 vnode:占位符vnode parent父vue实例 parentelm:真实的dom节点  refelm:参考节点  vnode, // we know it's mountedcomponentvnode but flow doesn't  parent, // activeinstance in lifecycle state  parentelm,  refelm) {  var options = {    _iscomponent: true,    parent: parent,    _parentvnode: vnode,    _parentelm: parentelm || null,    _refelm: refelm || null  };  // check inline-template render functions  var inlinetemplate = vnode.data.inlinetemplate;               //尝试获取inlinetemplate属性,定义组件时如果指定了inline-template特性,则组件内的子节点都是该组件的模板  if (isdef(inlinetemplate)) {                                  //如果inlinetemplate存在,我们这里是不存在的    options.render = inlinetemplate.render;     options.staticrenderfns = inlinetemplate.staticrenderfns;  }  return new vnode.componentoptions.ctor(options)               //调用组件的构造函数(vue.extend()里面定义的)返回子组件的实例,也就是vue.extend()里定义的sub函数}

最后vue.extend()里的sub函数会执行_init方法对vue做初始化,初始化的过程中会定义组件实例的$parent和父组件的$children属性,从而实现父组件和子组件的互连,组件的大致流程就是这样子

本文发布于:2023-04-06 07:51:42,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/ab55a5864c4d91e836b9b6aa42cb35fe.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:Vue.js 源码分析(十二) 基础篇 组件详解.doc

本文 PDF 下载地址:Vue.js 源码分析(十二) 基础篇 组件详解.pdf

上一篇:3.从实例开始
下一篇:返回列表
标签:组件   函数   实例   属性
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图