【轉】【Html】Vuejs2.0學習之二(Render函數,createElement,vm.$slots,函數化組件,模板編譯,JSX)


1、Render函數 
所以直接來到Render,本來也想跳過,發現后面的路由貌似跟它還有點關聯。先來看看Render 
1.1 官網一開始就看的挺懵的,不知道講的是啥,動手試了一下,一開頭講的是Render的用法,官網的栗子永遠都是一個特點,tm的不貼完整,我這里是個相對完整版的:(為了看的清楚點,替換了下名字)

<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類型,這個前面我們也聊過的。

最后實例化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分發的內容。所以最后整個內容會顯而易見的被渲染為:。。。不寫了,自己研究。

突然發現我們的案例越來越復雜了,還好前面有做准備。但是這一切跟Render好像沒有半毛錢關系啊,確實沒有關系- -!官方舉了這個栗子就是說明這種寫法是繁雜浪費的,浪費的原因是,雖然最后只剩下h2,但是其他的h1,h3-h5其實都被渲染了,只不過沒有顯示而已。為了優化,所以才引用了Render。

1.2 將上面代碼改寫為Render方式

//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,所以它只會渲染一個標簽,而不是所有都渲染,所以優化了,而且代碼也省了不少呢。

1.3 createElement有點印象,js添加dom節點可以用它,document.createElement(tag)。這里的createElement(tag,{},[])或者createElement(tag,{},String)類似,不過接收的參數不一樣,后面兩個參數都是可選的

// @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。官方是這么描述的:

用來訪問被 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM