首页 > 作文

Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解

更新时间:2023-04-03 03:18:15 阅读: 评论:0

普通的插槽里面的数据是在父组件里定义的,而作用域插槽里的数据是在子组件定义的。

有时候作用域插槽很有用,比如使用element-ui表格自定义模板时就用到了作用域插槽,element-ui定义了每个单元格数据的显示格式,我们可以通过作用域插槽自定义数据的显示格式,对于二次开发来说具有很强的扩展性。

作用域插槽使用<template>来定义模板,可以带两个参数,分别是:

slot-scope ;模板里的变量,旧版使用scope属性

slot ;该作用域插槽的name,指定多个作用域插槽时用到,默认为default,即默认插槽

例如:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script></head><body>    <div id="app">          <child>            <template slot="header" slot-scope="props">                 <!--定义了名为header的作用域插槽的模板-->              <h1>{{props.info.name}}-{{props.info.age}}</h1>            </template>            <template slot-scope="show">                                <!--定义了默认作用域插槽的模板-->              <p>{{show.today}}</p>       公元前是什么意思     </template>          </child>      </div>      <script>        vue.config.productiontip=fal;        vue.config.devtools=fal;        vue.component('child',{          template:`<div class="container">                        <header><slot name="header" :info="info"></slot江西会考></header>     //header插槽                        <main><slot today="礼拜一">默认内容</slot></main>               //默认插槽                    </div>`,              data(){                return { info:{name:'ge',age:25} }              }        })        debugger        new vue({          el: '#app',          data:{            title:'我是标题',            msg:'我是内容'          }        })      </script></body></html>

我们在子组件定义了两个插槽,如下:

header插槽内通过v-bind绑定了一个名为info的特性,值为一个对象,包含一个name和age属性

另一个是普通插槽,传递了一个today特性,值为礼拜一

父组件引用子组件时定义了模板,渲染后结果如下:

对应的html代码如下:

其实vue内部把父组件template下的子节点编译成了一个函数,在子组件实例化时调用的,所以作用域才是子组件的作用域

源码分析

父组件解析模板将模板转换成ast对象时会执行processslot()函数,如下:

function processslot (el) {       //第9767行   解析slot插槽  if (el.tag === 'slot') {          //如果是slot    /*普通插槽的逻辑*/  } el {    var slotscope;    if (el.tag === 'template') {                //如果标签名为template(作用域插槽的逻辑)      slotscope = getandremoveattr(el, 'scope');          //尝试获取scope      /* istanbul ignore if */        if ("development" !== 'production' && slotscope) {  //在开发环境下报一些信息,因为scope属性已淘汰,新版本开始用slot-scope属性了        warn$2(          "the \"scope\" attribute for scoped slots have been deprecated and " +          "replaced by \"slot-scope\" since 2.5. the new \"slot-scop唯美图e\" attribute " +          "can also be ud on plain elements in addition to <template> to " +          "denote scoped slots.",          true        );      }      el.slotscope = slotscope || getandremoveattr(el, 'slot-scope'); //获取slot-scope特性,值保存到ast对象的slotscope属性里    } el if ((slotscope = getandremoveattr(el, 'slot-scope'))) {      /*其它分支*/    }    var slottarget = getbindingattr(el, 'slot');          //尝试获取slot特性    if (slottarget) {                                     //如果获取到了      el.slottarget = slottarget === '""' ? '"default"' : slottarget;   //则保存到el.slottarget里面      // prerve slot as an attribute for native shadow dom compat      // only for non-scoped slots.      if (el.tag !== 'template' && !el.slotscope) {        addattr(el, 'slot', slottarget);      }    }  }}

执行到这里,对于<template slot=”header” slot-scope=”props”> 节点来说,添加了一个slotscope和slottarget属性,如下:

对于<template slot-scope=”show”>节点来说,由于没有定义slot属性,它的ast对象如下:

作用域插槽和普通节点最大的不同点是它不会将当前结点挂在ast对象树上,而是挂在了父节点的scopedslots属性上。

在解析完节点属性后会执行start()函数内的末尾会判断如果发现ast对象.slotscope存在,则会在currentparent对象(也就是父ast对象)的scopedslots上新增一个el.slottarget属性,值为当前template对应的ast对象。

if (currentparent && !element.forbidden) {    //第9223行  解析模板时的逻辑 如果当前对象不是根对象, 且不是style和text/javascript类型script标签  if (element.elif || element.el) {           //如果有elif或el指令存在(设置了v-el或v-elif指令)    processifconditions(element, currentparent);  } el if (element.slotscope) { // scoped slot  //如果存在slotscope属性,即是作用域插槽    currentparent.plain = fal;    var name = element.slottarget || '"default"';(currentparent.scopedslots || (currentparent.scop9月25日是什么星座edslots = {}))[name] = element;   //给父元素增加一个scopedslots属性,值为数组,每个键名为对应的目标名称,值为对应的作用域插槽ast对象  } el {     currentparent.children.push(element);    element.parent = currentparent;  }}

这样父节点就存在一个slottarget属性了,值为对应的作用域插槽ast对象,例子里执行到这一步对应slottarget如下:

default和header分别对应父组件里的两个template节点

父组件执行generate的时候,如果ast对象的scopedslots属性存在,则执行genscopedslots()函数拼凑data:

  if (el.scopedslots) {     //如果el.scopedslots存在,即子节点存在作用域插槽      data += (genscopedslots(el.scopedslots, state)) + ",";    //调用genscopedslots()函数,并拼接到data里面  } 

genscopedslots函数会返回scopedslots:_u([])函数字符串,_u就是全局的resolvescopedslots函数,genscopedslots如下:

function genscopedslots (     //第10390行   slots,  state) {  return ("scopedslots:_u([" + (object.keys(slots).map(function (key) {     //拼凑一个_u字符串      return genscopedslot(k全反射的条件ey, slots[key], state)                            //遍历slots,执行genscopedslot,将返回值保存为一个数组,作为_u的参数    }).join(',')) + "])")}

genscopedslot会拼凑每个slots,如下:

function genscopedslot ( //第10399行   key,  el,  state) {  if (el.for && !el.forprocesd) {    return genforscopedslot(key, el, state)  }  var fn = "function(" + (string(el.slotscope)) + "){" +      //拼凑一个函数,el.slotscope就是模板里设置的slot-scope属性    "return " + (el.tag === 'template'      ? el.if        ? ((el.if) + "?" + (genchildren(el, state) || 'undefined') + ":undefined")        : genchildren(el, state) || 'undefined'      : genelement(el, state)) + "}";  return ("{key:" + key + ",fn:" + fn + "}")}

解析后生成的render函数如下:

with(this){return _c('div',{attrs:{"id":"app"}},[_c('child',{scopedslots:_u([{key:"header",fn:function(props){return [_c('h1',[_v(_s(props.info.name)+"-"+_s(props.info.age))])]}},{key:"default",fn:function(show){return [_c('p',[_v(_s(show.today))])]}}])})],1)}

这样看着不清楚,我们整理一下,如下:

with(this) {    return _c(        'div',         {attrs: {"id": "app"}},        [_c('child', {            scopedslots: _u([              {key: "header",fn: function(props) {return [_c('h1', [_v(_s(props.info.name) + "-" + _s(props.info.age))])]}},              {key: "default",fn: function(show) {return [_c('p', [_v(_s(show.today))])]}}            ])          }        )],         1)}

可以看到_u的参数是一个对象,键名为插槽名,值是一个函数,最后子组件会执行这个函数的,创建子组件的实例时,会将scopedslots属性保存到data.scopedslots上

对于子组件的编译过程和普通插槽没有什么区别,唯一不同的是会有attr属性,例子里的组件编译后生成的render函数如下:

with(this){return _c('div',{staticclass:"container"},[_c('header',[_t("header",null,{info:info})],2),_v(" "),_c('main',[_t("default",[_v("默认内容")],{today:"礼拜一"})],2)])}

这样看着也不清楚,我们整理一下,如下:

with(this) {    return _c('div', {staticclass: "container"},        [          _c('header', [_t("header", null, {info: info})], 2),           _v(" "),           _c('main', [_t("default", [_v("默认内容")], {today: "礼拜一"})], 2)        ]      )}

可以看到最后和普通插槽一样也是执行_t函数的,不过在_t函数内会优先从scopedslots中获取模板,如下:

function renderslot (       //渲染插槽  name,  fallback,  props,  bindobject) {  var scopedslotfn = this.$scopedslots[name];           //尝试从 this.$scopedslots中获取名为name的函数,也就是我们在上面父组件渲染生成的render函数里的作用域插槽相关函数  var nodes;  if (scopedslotfn) { // scoped slot                    //如果scopedslotfn存在    props = props || {};    if (bindobject) {      if ("development" !== 'production' && !isobject(bindobject)) {        warn(          'slot v-bind without argument expects an object',          this        );      }      props = extend(extend({}, bindobject), props);    }    nodes = scopedslotfn(props) || fallback;          //最后执行scopedslotfn这个函数,参数为props,也就是特性数组  } el {    /*普通插槽的分支*/  }  var target = props && props.slot;  if (target) {    return this.$createelement('template', { slot: target }, nodes)  } el {    return nodes  }}

最后将nodes返回,也就是在父节点的template内定义的子节点返回,作为最后渲染的节点集合。

本文发布于:2023-04-03 03:18:12,感谢您对本站的认可!

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

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

本文word下载地址:Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解.doc

本文 PDF 下载地址:Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解.pdf

标签:插槽   函数   属性   作用
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图