【转】【Html】Vuejs2.0学习之二(Render函数,createElement,vm.$slots,函数化组件,模板编译,JSX)

一、Render函数 
因此直接来到Render,原本也想跳过,发现后面的路由貌似跟它还有点关联。先来看看Render 
1.1 官网一开始就看的挺懵的,不知道讲的是啥,动手试了一下,一开头讲的是Render的用法,官网的栗子永远都是一个特色,tm的不贴完整,我这里是个相对完整版的:(为了看的清楚点,替换了下名字)javascript

<div id="div1">
<child :level="2">Hello world!</child>
</div>

<script type="text/x-template" id="template">
  <div>
    <h1 v-if="level === 1">
      <slot></slot>
    </h1>
    <h2 v-if="level === 2">
      <slot></slot>
    </h2>
    <h3 v-if="level === 3">
      <slot></slot>
    </h3>
    <h4 v-if="level === 4">
      <slot></slot>
    </h4>
    <h5 v-if="level === 5">
      <slot></slot>
    </h5>
    <h6 v-if="level === 6">
      <slot></slot>
    </h6>
  </div>
</script>

<script type="text/javascript">
    Vue.component('child', {
  template: '#template',
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

    new Vue({
    el:"#div1"
})
</script>

回顾一下前面所学,这里注册了一个名叫child的全局组件,其模板是id=template的模板,往上一看发现,这个写法跟之前不同啊,之前用的是<template>标签,小伙伴们还有印象不?为此查了下api,也就是说这是新版写法。模板里有作了判断,根据level的值来选择head的尺寸h1-h6,同时使用slot分发内容(不记得的童鞋能够看看我前面的文章)。level在哪里?回头看组件里的props,这东东还有印象不,父组件传递参数给子组件能够用它,同时还作了props验证,level必须是Number类型,这个前面咱们也聊过的。html

最后实例化Vue,在id=div1的块中使用Vue,这样div1就可使用child模板:前端

<div id="div1">
<child :level="2">Hello world!</child>
</div>

此时,父组件div1可使用子模板child,同时父模板可使用level属性,采用bind的方式能够传递数值2,不用:去bind的后果就是传递字符串”2”,这个也聊过了。hello world做为slot分发的内容。因此最后整个内容会显而易见的被渲染为:。。。不写了,本身研究。vue

忽然发现咱们的案例愈来愈复杂了,还好前面有作准备。可是这一切跟Render好像没有半毛钱关系啊,确实没有关系- -!官方举了这个栗子就是说明这种写法是繁杂浪费的,浪费的缘由是,虽然最后只剩下h2,可是其余的h1,h3-h5其实都被渲染了,只不过没有显示而已。为了优化,因此才引用了Render。java

1.2 将上面代码改写为Render方式node

//html
<div id="div1">
<child :level="3">Hello world!</child>
</div>

//script
<script type="text/javascript">
    Vue.component('child', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // tag name 标签名称
      this.$slots.default // 子组件中的阵列
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

    new Vue({
    el:"#div1"
})
</script>

没了?是的,没了。不信你试一下,效果是同样的,绑定1的话渲染出h1,绑定2渲染h2。我去,很6啊,模板都不要就搞定了。怎么作到的?看createElement是个啥东东先,因此就开始createElement。因此,你们们发现了没,这官网的逻辑就是非主流啊,无心中被我发现了要理解他的逻辑必须向我这样边试边看才行,哇咔咔。不过咱们顾名思义一下,createElement看名字像动态建立dom节点(节点vue里面也叫VNode)的过程,在看内容,’h’+this.level根据level建立标签h1-h6,因此它只会渲染一个标签,而不是全部都渲染,因此优化了,并且代码也省了很多呢。git

1.3 createElement有点印象,js添加dom节点能够用它,document.createElement(tag)。这里的createElement(tag,{},[])或者createElement(tag,{},String)相似,不过接收的参数不同,后面两个参数都是可选的github

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签,组件设置,或一个函数
  // 必须 Return 上述其中一个
  'div',
  // {Object}
  // 一个对应属性的数据对象
  // 您能够在 template 中使用.可选项.
  {
    // (下一章,将详细说明相关细节)
  },
  // {String | Array}
  // 子节点(VNodes). 可选项.
  [
    createElement('h1', 'hello world'),
    createElement(MyComponent, {
      props: {
        someProp: 'foo'
      }
    }),
    'bar'
  ]
)

其中tag参数相似,第二个参数{}其实就一个数据对象,表明用在该节点的属性,好比常见的class,style,props,on等,完整的数据对象以下:正则表达式

{
  // 和`v-bind:class`同样的 API
  'class': {
    foo: true,
    bar: false
  },
  // 和`v-bind:style`同样的 API
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 正常的 HTML 特性
  attrs: {
    id: 'foo'
  },
  // 组件 props
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器基于 "on"
  // 因此再也不支持如 v-on:keyup.enter 修饰器
  // 须要手动匹配 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅对于组件,用于监听原生事件,而不是组件使用 vm.$emit 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令. 注意事项:不能对绑定的旧值设值
  // Vue 会为您持续追踨
  directives: [
    {
      name: 'my-custom-directive',
      value: '2'
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 若是子组件有定义 slot 的名称
  slot: 'name-of-slot'
  // 其余特殊顶层属性
  key: 'myKey',
  ref: 'myRef'
}

第三个参数[]能够看出来是表示该节点下面还有其余的节点,就放在此处[createElement(tag1),createElement(tag2)]。ok,回头看1.2中改写的render方法,至关于用了createElement(tag,[])的形式,其中tag=’h’+this.level, []= this.$slots.default。第一个参数好理解,第二个参数this.$slots.default是什么鬼,不知道的时候就去查api,slots很显然就是用于分发的那些slot们,找到api中的slot。官方是这么描述的:express

用来访问被 slot 分发的内容。每一个具名 slot 有其相应的属性(例如:slot="foo" 中的内容将会在
vm.$slots.foo 中被找到)。default 属性包括了全部没有被包含在一个具名 slot 中的节点。
在使用 render 函数书写一个组件时,访问 vm.$slots 最有帮助。

因此这货其实表明的是不具名的slot内容,也就是[VNode1,VNode2…]数组,这里的只有一个VNode就是那句被child包裹的Hello world!因此1.2中的render最后渲染的结果其实就是一个<h1>Hello world!</h1>这样的节点。

1.4 原文后面给了个完整例子不描述了,不同的地方在于建立a标签的时候使用了(tag,{},[])结构

createElement('a', {
          attrs: {
            name: headingId,
            href: '#' + headingId
          }
        }, this.$slots.default)
var getChildrenTextContent = function (children) {
  return children.map(function (node) {
    return node.children
      ? getChildrenTextContent(node.children)
      : node.text
  }).join('')
}

var headingId = getChildrenTextContent(this.$slots.default)
      .toLowerCase()
      .replace(/\W+/g, '-')
      .replace(/(^\-|\-$)/g, '')

getChildrenTextContent 这个函数,由于this.$slots.default是个数组[VNode1,VNode2…],因此能够作map处理(印象中是SE6方法),对数组中的每一个元素作统一处理:递归,一层层去查看VNode是否有子节点,有子节点就调用自身,直到无子节点后取出他的文本内容。最后用数组的join方法把每一层的文本用空格符链接 
好比

<div id="div1">
    <child :level="1">
        Hello world!
        <h2>
            woqu
            <h3>what</h3>
        </h2>
    </child>
</div>

this.$slots.default的值是[VNode1,VNode2,VNode3],其中

VNode1 = Hello world!
VNode2 = <h2>woqu</h2>
VNode3 = <h3>what</h3>

VNode1没child,直接返回了Hello world!,VNode2有child是h2,因此递归了一次h2里面没child,返回了woqu,VNode3状况相似,最终返回了what。因此map的结果就是获得了一个数组[‘Hello world!’,’woqu’,’what’],而后调用join方法串起来,获得’Hello world! woqu what’; 
后面再进行.toLowerCase()转小写,变为’hello world! woqu what’;

replace(/\W+/g, '-')进行正则替换,正则对于搞it的来讲应该不陌生,js中的正则格式是这样的,/正则表达式/匹配模式,匹配模式固然是可选的,\W表示非单词字符(0-9,a-z,A-Z,_),+表示一个或多个,/g表示使用全局匹配模式,全局的特色是每次匹配完,下次匹配的下标就是下一位,因此此次替换会把连续的非单词字符替换为-,变为’hello-world-woqu-what’;

再使用replace(/(^\-|\-$)/g, '')作一次正则替换,^\-表示匹配开头的-字符,\-$表示匹配结尾的-字符,|表示或者,这句的意思是若是字符串开头或结尾有-,就把他们替换成”,也就是直接删除,因而这里没有变化’hello-world-woqu-what’。

综上所述,var headingId = ‘hello-world-woqu-what’。

1.5 VNodes 必须惟一。这句话说的不是很清楚,其实就是同一个VNode只能用在一个地方。 
好比

render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div', [
    // Yikes - duplicate VNodes!
    myParagraphVNode, myParagraphVNode
  ])
}

这里的myParagraphVNode,被使用于’div’中的两个VNode,这种用法是不行,要想用只能建立两个相同的VNode对象,而不是这样指向同一个VNode对象。

1.6 Render之函数化组件 
大概是这个意思,看看1.5的render的结构, 
render:function(createElement){} 这个结构能够建立VNode对吧,可是没法访问外部数据,若是但愿建立的VNode须要依赖外部数据怎么办?这就是这一节的内容。

将其改写为如下方式,就能够访问外部数据了:

Vue.component('my-component', {
  functional: true,     //1
  // 为了弥补缺乏的实例
  // 提供第二个参数做为上下文
  render: function (createElement, context) {   //2
    // ...
  },
})

经过1和2两个改写,就能够利用context去访问外部数据了,context至关于一个组件的上下文,能够访问该组件的一些数据: 
props: 提供props 的对象 
children: VNode 子节点的数组 
slots: slots 对象 
data: 传递给组件的 data 对象 
parent: 对父组件的引用

好比:this.$slots.default 更新为 context.children,以后this.level 更新为 context.props.level。 
差很少就是这个意思

1.7 模板编译过程 
这里讲到一些vue模板底层在生命周期的编译阶段Vue.compile的处理方式。 
好比模板:

<div>
  <h1>I'm a template!</h1>
  <p v-if="message">
    {{ message }}
  </p>
  <p v-else>
    No message.
  </p>
</div>   

在编译的时候会相似如下的处理 
这里写图片描述 
能够看出div被建立的时候,相似于createElement,传了VNodes数组给他,_m(0)就是第一个节点VNode<h1>I'm a template!</h1> 后面的参数是个选择运算符a?b:c,若是message为true,则建立一个p节点,若是为false,也建立一个p节点,只不错两个p节点内容不同

另外能够为createElement取别名,通常用h表示

1.7 JSX 
这个东东做为我这样的前端小白,之前是没有听过的。查了一下,JSX语法,像是在Javascript代码里直接写XML的语法,每个XML标签都会被JSX转换工具转换成纯Javascript代码。看下面的例子:

//不使用JSX的状况下可能要这么写
render: function (h) {
  h(
  'div', 
  [
    h('span', 'Hello'),
    ' world!'
  ]
)
}
//使用JSX能够像写xml或html这类标签语言同样直接写,是否是直观不少
render (h) {
    return (
      <div>
        <span>Hello</span> world!
      </div>
    )
  };

Vue中使用JSX须要这个插件 :Babel plugin 。https://github.com/vuejs/babel-plugin-transform-vue-jsx

 

原文地址:https://blog.csdn.net/kkae8643150/article/details/52910389

相关文章
相关标签/搜索