Vue渲染函數

前面的話

  Vue 推薦在絕大多數情況下使用 template 來創建HTML。然而在一些場景中,真的需要 JavaScript 的完全編程的能力,這就是 render 函數,它比 template 更接近編譯器。本文將詳細介紹Vue渲染函數

?

引入

  下面是一個例子,如果要實現類似下面的效果。其中,H標簽可替換

<h1><a name="hello-world" href="#hello-world">Hello world!</a>
</h1>

  在 HTML 層,像下面這樣定義來組件接口:

<anchored-heading :level="1">Hello world!</anchored-heading>

  當開始寫一個通過 level prop 動態生成 heading 標簽的組件,可能很快想到這樣實現:

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

?  JS代碼如下

Vue.component('anchored-heading', {template: '#anchored-heading-template',props: {level: {type: Number,required: true}}
})

  在這種場景中使用 template 并不是最好的選擇:首先代碼冗長,為了在不同級別的標題中插入錨點元素,需要重復地使用 <slot></slot>

  雖然模板在大多數組件中都非常好用,但是在這里它就不是很簡潔的了。那么,來嘗試使用 render 函數重寫上面的例子:

<div id="example"><anchored-heading :level="2"><a name="hello-world" href="#hello-world">Hello world!</a></anchored-heading>
</div>
<script src="vue.js"></script>
<script>
Vue.component('anchored-heading', {render: function (createElement) {return createElement('h' + this.level,   // tag name 標簽名稱this.$slots.default // 子組件中的陣列
    )},props: {level: {type: Number,required: true}}
})  
new Vue({el: '#example'
})
</script>

  這樣的代碼精簡很多,但是需要非常熟悉 Vue 的實例屬性。在這個例子中,需要知道當不使用 slot 屬性向組件中傳遞內容時,比如 anchored-heading 中的 Hello world!,這些子元素被存儲在組件實例中的 $slots.default

?

虛擬DOM

  在深入渲染函數之前,了解一些瀏覽器的工作原理是很重要的。以下面這段 HTML 為例:

<div><h1>My title</h1>Some text content<!-- TODO: Add tagline -->
</div>

  當瀏覽器讀到這些代碼時,它會建立一個“DOM 節點”樹來保持追蹤,如同會畫一張家譜樹來追蹤家庭成員的發展一樣。HTML 的 DOM 節點樹如下圖所示:

dom

  每個元素都是一個節點。每段文字也是一個節點。甚至注釋也都是節點。一個節點就是頁面的一個部分。就像家譜樹一樣,每個節點都可以有子節點 (也就是說每個部分可以包含其它的一些部分)

  高效的更新所有這些節點會是比較困難的,不過所幸不必再手動完成這個工作了。只需要告訴 Vue 希望頁面上的 HTML 是什么,這可以是在一個模板里:

<h1>{{ blogTitle }}</h1>

  或者一個渲染函數里:

render: function (createElement) {return createElement('h1', this.blogTitle)
}

  在這兩種情況下,Vue 都會自動保持頁面的更新,即便 blogTitle 發生了改變。

【虛擬DOM】

  Vue 通過建立一個虛擬 DOM 對真實 DOM 發生的變化保持追蹤

return createElement('h1', this.blogTitle)

  createElement 到底會返回什么呢?其實不是一個實際的 DOM 元素。它更準確的名字可能是 createNodeDescription,因為它所包含的信息會告訴 Vue 頁面上需要渲染什么樣的節點,及其子節點。我們把這樣的節點描述為“虛擬節點 (Virtual DOM)”,也常簡寫它為“VNode”。“虛擬 DOM”是我們對由 Vue 組件樹建立起來的整個 VNode 樹的稱呼

?

createElement

  接下來需要熟悉的是如何在 createElement 函數中生成模板。這里是 createElement 接受的參數:

// @returns {VNode}
createElement(// {String | Object | Function}// 一個 HTML 標簽字符串,組件選項對象,或者一個返回值類型為 String/Object 的函數,必要參數'div',// {Object}// 一個包含模板相關屬性的數據對象// 這樣,可以在 template 中使用這些屬性。可選參數。
  {
  },// {String | Array}// 子節點 (VNodes),由 `createElement()` 構建而成,// 或簡單的使用字符串來生成“文本節點”。可選參數。
  ['先寫一些文字',createElement('h1', '一則頭條'),createElement(MyComponent, {props: {someProp: 'foobar'}})]
)

【深入data對象】

  正如在模板語法中,v-bind:classv-bind:style ,會被特別對待一樣,在 VNode 數據對象中,下列屬性名是級別最高的字段。該對象也允許綁定普通的 HTML 特性,就像 DOM 屬性一樣,比如 innerHTML (這會取代 v-html 指令)

{// 和`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}}],// Scoped slots in the form of// { name: props => VNode | Array<VNode> }
  scopedSlots: {default: props => createElement('span', props.text)},// 如果組件是其他組件的子組件,需為插槽指定名稱slot: 'name-of-slot',// 其他特殊頂層屬性key: 'myKey',ref: 'myRef'
}

【完整示例】

  有了這些知識,現在可以完成最開始想實現的組件:

var getChildrenTextContent = function (children) {return children.map(function (node) {return node.children? getChildrenTextContent(node.children): node.text}).join('')
}
Vue.component('anchored-heading', {render: function (createElement) {// create kebabCase idvar headingId = getChildrenTextContent(this.$slots.default).toLowerCase().replace(/\W+/g, '-').replace(/(^\-|\-$)/g, '')return createElement('h' + this.level,[createElement('a', {attrs: {name: headingId,href: '#' + headingId}}, this.$slots.default)])},props: {level: {type: Number,required: true}}
})

【約束】

  組件樹中的所有 VNodes 必須是唯一的。這意味著,下面的 render function 是無效的:

render: function (createElement) {var myParagraphVNode = createElement('p', 'hi')return createElement('div', [// 錯誤-重復的 VNodes
    myParagraphVNode, myParagraphVNode])
}

  如果真的需要重復很多次的元素/組件,可以使用工廠函數來實現。例如,下面這個例子 render 函數完美有效地渲染了 20 個重復的段落:

render: function (createElement) {return createElement('div',Array.apply(null, { length: 20 }).map(function () {return createElement('p', 'hi')}))
}

?

JS代替模板

【v-if和v-for】

  由于使用原生的 JavaScript 來實現某些東西很簡單,Vue 的 render 函數沒有提供專用的 API。比如,template 中的 v-ifv-for

<ul v-if="items.length"><li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>

  這些都會在 render 函數中被 JavaScript 的 if/elsemap 重寫:

render: function (createElement) {if (this.items.length) {return createElement('ul', this.items.map(function (item) {return createElement('li', item.name)}))} else {return createElement('p', 'No items found.')}
}

【v-model】

  render 函數中沒有與 v-model 相應的 api,必須自己來實現相應的邏輯:

render: function (createElement) {var self = thisreturn createElement('input', {domProps: {value: self.value},on: {input: function (event) {self.value = event.target.valueself.$emit('input', event.target.value)}}})
}

  這就是深入底層要付出的,盡管麻煩了一些,但相對于 v-model 來說,可以更靈活地控制

【事件&按鍵修飾符】

  對于 .passive.capture.once事件修飾符,Vue 提供了相應的前綴可以用于 on

Modifier(s)     Prefix
.passive       &
.capture       !
.once         ~
.capture.once   or
.once.capture    ~!

  下面是一個例子

on: {'!click': this.doThisInCapturingMode,'~keyup': this.doThisOnce,`~!mouseover`: this.doThisOnceInCapturingMode
}

  對于其他的修飾符,前綴不是很重要,因為可以直接在事件處理函數中使用事件方法:

Modifier(s)     Equivalent in Handler
.stop          event.stopPropagation()
.prevent        event.preventDefault()
.self          if (event.target !== event.currentTarget) return
Keys:
.enter, .13     if (event.keyCode !== 13) return (...)
Modifiers Keys:
.ctrl, .alt, .shift, .meta     if (!event.ctrlKey) return (...)

  下面是一個使用所有修飾符的例子:

on: {keyup: function (event) {// 如果觸發事件的元素不是事件綁定的元素// 則返回if (event.target !== event.currentTarget) return// 如果按下去的不是 enter 鍵或者// 沒有同時按下 shift 鍵// 則返回if (!event.shiftKey || event.keyCode !== 13) return// 阻止 事件冒泡
    event.stopPropagation()// 阻止該元素默認的 keyup 事件
    event.preventDefault()// ...
  }
}

【插槽】

  可以從 this.$slots 獲取 VNodes 列表中的靜態內容:

render: function (createElement) {// `<div><slot></slot></div>`return createElement('div', this.$slots.default)
}

  還可以從 this.$scopedSlots 中獲得能用作函數的作用域插槽,這個函數返回 VNodes:

render: function (createElement) {// `<div><slot :text="msg"></slot></div>`return createElement('div', [this.$scopedSlots.default({text: this.msg})])
}

  如果要用渲染函數向子組件中傳遞作用域插槽,可以利用 VNode 數據中的 scopedSlots 域:

render (createElement) {return createElement('div', [createElement('child', {// pass `scopedSlots` in the data object// in the form of { name: props => VNode | Array<VNode> }
      scopedSlots: {default: function (props) {return createElement('span', props.text)}}})])
}

?

JSX

  如果寫了很多 render 函數,可能會覺得痛苦

createElement('anchored-heading', {props: {level: 1}}, [createElement('span', 'Hello'),' world!']
)

  特別是模板如此簡單的情況下:

<anchored-heading :level="1"><span>Hello</span> world!
</anchored-heading>

  這就是為什么會有一個 Babel 插件,用于在 Vue 中使用 JSX 語法的原因,它可以讓我們回到更接近于模板的語法上

import AnchoredHeading from './AnchoredHeading.vue'
new Vue({el: '#demo',render (h) {return (<AnchoredHeading level={1}><span>Hello</span> world!</AnchoredHeading>
    )}
})

  [注意]將 h 作為 createElement 的別名是 Vue 生態系統中的一個通用慣例,實際上也是 JSX 所要求的,如果在作用域中 h 失去作用,在應用中會觸發報錯

?

函數式組件

  之前創建的錨點標題組件是比較簡單,沒有管理或者監聽任何傳遞給它的狀態,也沒有生命周期方法。它只是一個接收參數的函數。在這個例子中,我們標記組件為 functional,這意味它是無狀態 (沒有 data),無實例 (沒有 this 上下文)

  一個 函數式組件 就像這樣:

Vue.component('my-component', {functional: true,// 為了彌補缺少的實例// 提供第二個參數作為上下文render: function (createElement, context) {// ...
  },// Props 可選
  props: {// ...
  }
})

  [注意]在 2.3.0 之前的版本中,如果一個函數式組件想要接受 props,則 props 選項是必須的。在 2.3.0 或以上的版本中,你可以省略 props 選項,所有組件上的屬性都會被自動解析為 props

  組件需要的一切都是通過上下文傳遞,包括:

props:提供 props 的對象
children: VNode 子節點的數組
slots: slots 對象
data:傳遞給組件的 data 對象
parent:對父組件的引用
listeners: (2.3.0+) 一個包含了組件上所注冊的 v-on 偵聽器的對象。這只是一個指向 data.on 的別名。
injections: (2.3.0+) 如果使用了 inject 選項,則該對象包含了應當被注入的屬性。

  在添加 functional: true 之后,錨點標題組件的 render 函數之間簡單更新增加 context 參數,this.$slots.default 更新為 context.children,之后this.level 更新為 context.props.level

  因為函數式組件只是一個函數,所以渲染開銷也低很多。然而,對持久化實例的缺乏也意味著函數式組件不會出現在 Vue devtools 的組件樹里。

  在作為包裝組件時它們也同樣非常有用,比如,當需要做這些時:

  1、程序化地在多個組件中選擇一個

  2、在將 children, props, data 傳遞給子組件之前操作它們

  下面是一個依賴傳入 props 的值的 smart-list 組件例子,它能代表更多具體的組件:

var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }
Vue.component('smart-list', {functional: true,render: function (createElement, context) {function appropriateListComponent () {var items = context.props.itemsif (items.length === 0)           return EmptyListif (typeof items[0] === 'object') return TableListif (context.props.isOrdered)      return OrderedListreturn UnorderedList}return createElement(appropriateListComponent(),context.data,context.children)},props: {items: {type: Array,required: true},isOrdered: Boolean}
})

【slots()和children對比】

  為什么同時需要 slots()childrenslots().default 不是和 children 類似的嗎?在一些場景中,是這樣,但是如果是函數式組件和下面這樣的 children 呢?

<my-functional-component><p slot="foo">first</p><p>second</p>
</my-functional-component>

  對于這個組件,children 會給兩個段落標簽,而 slots().default 只會傳遞第二個匿名段落標簽,slots().foo 會傳遞第一個具名段落標簽。同時擁有 childrenslots() ,因此可以選擇讓組件通過 slot() 系統分發或者簡單的通過 children 接收,讓其他組件去處理

?

模板編譯

  Vue 的模板實際是編譯成了 render 函數。這是一個實現細節,通常不需要關心。下面是一個使用 Vue.compile 來實時編譯模板字符串的簡單 demo:

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

  render:

function anonymous(
) {with(this){return _c('div',[_m(0),(message)?_c('p',[_v(_s(message))]):_c('p',[_v("No message.")])])}
}

  staticRenderFns:

_m(0): function anonymous(
) {with(this){return _c('header',[_c('h1',[_v("I'm a template!")])])}
}

?

轉載于:https://www.cnblogs.com/xiaohuochai/p/7521542.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/455047.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/455047.shtml
英文地址,請注明出處:http://en.pswp.cn/news/455047.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據綁定原理

一、數據單向綁定原理指先把模板寫好&#xff0c;然后把模板和數據(數據可能來自后臺)整合到一起形成HTML代碼&#xff0c;最后把這段HTML代碼插入到文檔流里。缺點&#xff1a;一旦HTML代碼生成就沒有辦法改變&#xff0c;如果有新數據重新傳入&#xff0c;就必須重新把模板和…

視頻解碼優化

以下通過剖析一些經驗來了解視頻解碼優化 1. 在嵌入式系統中實現MPEG4的視頻解碼 有兩種方法可行 (1)采用ffmpeg(mplayer 的核心就是采用ffmpeg)&#xff0c;然后對ffmpeg mp4解碼優化 1).對IDCT匯編化,并優化VLD的實現 ->inline&匯編化 2).根據ARM9 cache&cache…

Logstash入門簡介

Logstash入門簡介 介紹 Logstash是一個開源的服務器端數據處理管道&#xff0c;能夠同時從多個來源采集數據&#xff0c;轉換數據&#xff0c;然后將數據發送到最喜歡的存儲庫中&#xff08;我們的存儲庫當然是ElasticSearch&#xff09; 我們回到我們ElasticStack的架構圖&a…

Django templates 和 urls 拆分

如果在Django項目 下面新建了blog和polls兩個APP應用&#xff0c;在每個APP下面都各自新建自己的url和templates&#xff0c;那么我們需要如何進行項目配置呢&#xff1f; INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, dja…

springboot怎么殺進程_線上服務平均響應時間太長,怎么排查?

線上服務平均響應時間太長&#xff0c;怎么排查&#xff1f;https://xie.infoq.cn/article/914b5c56000a3880016abd8d6前言&#xff1a;最近線上環境某個接口服務響應時間偏長&#xff0c;導致用戶體驗超差&#xff0c;那平時該怎么快速的排查這類問題呢&#xff1f;①、為代碼…

Redis學習第五課:Redis Set類型及操作

Set是集合&#xff0c;它是string類型的無序集合。set是通過hash table實現的&#xff0c;添加、刪除和查找的復雜度都是O(1)。 對集合我們可以取并集、交集、差集。通過這些操作我們可以實現SNS中的好友推薦和blog的tag功能。 Set集合操作&#xff1a; sadd:向名稱為Key的set中…

MPEG音視頻編解碼之MP3編解碼概述

2 MP3編解碼原理 2.1 MP3音頻壓縮標準概述 MP3全稱是動態影像專家壓縮標準音頻層面3&#xff08;Moving Picture Experts Group Audio Layer III&#xff09;。是當今較流行的一種數字音頻編碼和有損壓縮格式&#xff0c;它設計用來大幅度地降低音頻數據量&#xff0c;而對于…

Python實現GitBook工具

寫在前面 本工具是通過Python腳本實現 GitBook 自動 生成 執行 編譯 發布的功能 你可以在這里下載exe 使用 1. exe下載,并移動位置 將exe文件放在你的gitbook文件夾中,或者放在空文件夾中 2. file.md 創建 名為file.md的文件,在你要寫book的目錄下 注意: 這里file.md文件名…

shell腳本中用到的條件和循環語句

本博文介紹一下shell腳本中常用的條件和循環語句&#xff1a;條件語句&#xff1a;循環語句&#xff1a;示例&#xff1a;if語句&#xff1a;eg1.eg2.2.case語句&#xff1a;簡單的case語句&#xff1a;配合循環的case語句&#xff1a;3.for語句&#xff1a;簡單的for語句&…

BZOJ 2243 染色(樹鏈剖分好題)

2243: [SDOI2011]染色 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 7971 Solved: 2990[Submit][Status][Discuss] Description 給定一棵有n個節點的無根樹和m個操作&#xff0c;操作有2類&#xff1a; 1、將節點a到節點b路徑上所有點都染成顏色c&#xff1b; 2、詢問…

processing動態代碼大全_做一張動態海報需要多少步?

人們習慣性地把程序員跟設計師分成兩種不同性質的人&#xff0c;好像程序員就不會有美感&#xff0c;設計師邏輯思維就一定會很弱&#xff0c;但最近幾年我們發現越來越多的程序員學設計&#xff0c;設計師學編程的跨界故事。新媒體藝術家&#xff0c;邱偉豪也是其中一員&#…

【ffmpeg for wince】音視頻編解碼多平臺移植(for window/wince)

from: http://www.cnblogs.com/windwithlife/archive/2009/05/31/1492728.html 終于完成了了第二個Client side原型&#xff08;for Wince)&#xff0c;其中花掉我最多時間的就是ffmpeg的對WINCE的移植。其中有大半時間是由于網上的一些不完整及不正確信息所誤導&#xff0c;…

python實現猴子爬山算法

猴子爬山一只頑猴在一座有N級臺階的小山上爬山跳躍。上山時需從山腳至山頂往上跳N級臺階&#xff0c;一步可跳1級&#xff0c;或跳3級&#xff0c;求上山有多少種不同的跳法&#xff1f; &#xff08;N<50&#xff09; 問題分析: 每一次都可以選擇1,2,3有3種跳法 方法1 直…

指針版 單鏈表復習

#include <bits/stdc.h> #define P pair<int,int> using namespace std;typedef long long LL;typedef struct LNode{int data;struct LNode *nxt; }LNode,*LinkList;bool Linklist_init(LinkList &root){root new LNode; ///分配頭結點&#xff0c;指針域為空…

手寫springboot_Spring Boot 入門教程 | 圖文講解

目錄一、Spring Boot 是什么二、為什么要使用 Spring Boot三、快速入門3.1 創建 Spring Boot 項目3.2 項目結構3.3 引入 Web 依賴3.4 編寫第一個接口3.5 啟動程序&#xff0c;驗證效果四、總結五、GitHub 示例代碼一、Spring Boot 是什么以下截圖自 Spring Boot 官方文檔&#…

lunix 安裝python3

Linux下默認系統自帶python2.6的版本&#xff0c;這個版本被系統很多程序所依賴&#xff0c;所以不建議刪除&#xff0c;如果使用最新的Python3那么我們知道編譯安裝源碼包和系統默認包之間是沒有任何影響的&#xff0c;所以可以安裝python3和python2共存 首先去python官網下載…

手機音視頻應用開發(專注于Symbian、iPhone、Android等跨平臺音視頻應用開發方案)

一款好的手機應用&#xff0c; 能讓用戶在第一分鐘就愛上他&#xff0c; 一款爛的手機應用&#xff0c; 能讓用戶在第一分鐘就要卸載它。 好的應用必須的穩定、快速。市場日益激勵&#xff0c;一個項目的周期是一個漫長的過程&#xff0c;投入的時間、精力、費用。一筆龐大的預…

Colemak布局的實現 Window+Linux+Android

Colemak布局的實現 WindowLinuxAndroid title: ‘Colemak布局的實現’ subtitle: ‘一個極客的鍵盤布局’ tags: entertainment solution 前言 大部分同學使用的鍵盤布局都是QWERTY布局 而科學研究表明,可能這個設計不是最高效率的布局,甚至的有意為了降低打字的效率而研究的…

機器學習之樸素貝葉斯法

轉載請注明出處&#xff1a;http://www.cnblogs.com/Peyton-Li/ 樸素貝葉斯法是機器學習模型中一個比較簡單的模型&#xff0c;實現簡單&#xff0c;比較常用。 是定義在輸入空間上的隨機向量&#xff0c;是定義在輸出空間上的隨機變量。是和的聯合概率分布。訓練數據集由獨立同…

如何讓梯形變成平行四邊形_開放的課堂 創新的天地——平行四邊形的面積教學片段與反思...

一、 課題的確定學生在三年級學過長方形、正方形的面積計算&#xff0c;經歷過從數方格的辦法得出面積計算公式的過程。因此&#xff0c;學生對于面積計算公式的推導有一定的經驗和知識基礎。基于上述考慮&#xff0c;我想完全放手讓學生去研究如何計算平行四邊形的面積。這對學…