vue2 中使用 render 函數(shù)編寫(xiě)組件的方式
vue2 中如何使用 render 函數(shù)編寫(xiě)組件
vue 提供了聲明式編寫(xiě) UI 的方式,即 vue 提供了對(duì) DOM 進(jìn)行描述的方式。
有兩種描述 DOM 的方式:模板和 render 函數(shù)。模板在編譯階段會(huì)被轉(zhuǎn)成 render 函數(shù),這一過(guò)程叫編譯模板。
模板可讀性好,但是有時(shí)候模板并不靈活,大型的模板可讀性也不好。
render 函數(shù)可讀性不高,但是靈活,使用 render 函數(shù)封裝組件,使用得當(dāng),可提高組件的擴(kuò)展性和易用性。jsx 可解決 render 函數(shù)讀寫(xiě)性不高的問(wèn)題。
在 vue 的項(xiàng)目入口文件中,下面的代碼新建一個(gè) vue 應(yīng)用的根組件,并默認(rèn)命名為 Root
,并將其掛載在 HTML 模板 #app
div 上,它的模板在哪?
new Vue({ render: h => h(App), }).$mount('#app')
這是一個(gè)沒(méi)有模板的組件。
KaTeX parse error: Expected 'EOF', got '#' at position 8: mount('#?app'),選擇器對(duì)應(yīng)的 do…mount(elector)`掛載元素,替換 selector 后,dom 的屬性丟失。 兩者表現(xiàn)不同,有點(diǎn)奇怪,但是不需要太關(guān)注這個(gè)區(qū)別。
今天再來(lái)復(fù)習(xí) render 函數(shù),重點(diǎn)關(guān)注這些容易踩坑的地方:
- 學(xué)習(xí) render 函數(shù)的使用,重點(diǎn):樣式、事件、插槽、指令、props、v-model、函數(shù)組件的處理。
- 學(xué)習(xí)使用 jsx 封裝組件。
- 在 render 函數(shù)中使用表單組件,因?yàn)楸韱谓M件涉及到
v-model
容易踩坑。
為何要了解 render ?
在 render 函數(shù)中,可用 jsx 代替模板語(yǔ)法,可充分發(fā)揮 js 的能力,使得組件擴(kuò)展性更好、封裝更加優(yōu)雅。
官網(wǎng)說(shuō)得比較明白了,但是例子過(guò)于簡(jiǎn)單,只能體現(xiàn)優(yōu)雅,沒(méi)有體現(xiàn)擴(kuò)展性,稍后封裝一個(gè)能體現(xiàn)擴(kuò)展性
的組件。
render 基礎(chǔ)語(yǔ)法
render 函數(shù)簽名:
render(createElement: CreateElement, hack: RenderContext<Props>): VNode;
返回值 – VNode (虛擬節(jié)點(diǎn)) — 一個(gè)用于描述 vue 組件結(jié)構(gòu)的 JS 對(duì)象
說(shuō)明
①. 返回值往往是一個(gè)單節(jié)點(diǎn),劃線的 context 是 vue 全局對(duì)象,有 $children 屬性,是一個(gè) VNode 數(shù)組,元素是一個(gè) VNode,這樣的層層嵌套,正好對(duì)應(yīng)的組件的嵌套。 $el 是組件掛載點(diǎn),_uid 是組件 id。調(diào)試時(shí)
可能會(huì)用到。②. 也可以返回 VNode 數(shù)組。比如返回
this.$scopedSlots.default()
這是一個(gè)作用域插槽,可能是數(shù)組。
參數(shù)
第一個(gè)參數(shù) createElement
,是一個(gè)函數(shù),DOM 中有 createElement 用于創(chuàng)建 DOM 節(jié)點(diǎn)。vue 中的 createElement
是用于創(chuàng)建 VNode 的。
h 是 createElement 的別名,代表
Hyperscript
(生成 HTML 的腳本),Hyperscript itself stands for “script that generates HTML structures”。
寫(xiě)成
h
,更加方便輸入,更加語(yǔ)義化。
第二個(gè)參數(shù) hack
是渲染上下文,組件的props
、listeners
、slots
都在這個(gè)參數(shù)里。
說(shuō)明
第二個(gè)參數(shù)在函數(shù)組件中才有,非函數(shù)組件為 undefined。因?yàn)楹瘮?shù)組件中不存在組件實(shí)例 this,要提供這個(gè)參數(shù)獲取 props 等內(nèi)容。
第二個(gè)參數(shù)通常寫(xiě)成context
,更加語(yǔ)義化。
render 寫(xiě)成這樣:
render(h,context){ return <span>render函數(shù)</span> }
render 使用 es6 聲明方法的方式且返回 jsx ,vue 會(huì)自動(dòng)注入
const h = this.$createElement
,僅限于 render 函數(shù),其他函數(shù)要使用,就要手動(dòng)通過(guò)參數(shù)傳入。
render(){ return <span>render函數(shù)</span> }
這是 es5
的方式:
render:function(){ // 顯示傳遞 h: function(h) 才行 return <span>render函數(shù)</span> }
render
不能返回文本,想要顯示文本內(nèi)容,必須套一個(gè)標(biāo)簽, react 的 render 可以返回文本。
返回 jsx,vue 內(nèi)部會(huì)調(diào)用
createElement
編譯成 VNode。
如何返回純文本?
返回文本的 hack 寫(xiě)法
return this._v('someText')
,不推薦這么寫(xiě),會(huì)導(dǎo)致他人難以理解。
createElement
返回值:VNode
VNode 是一個(gè)描述組件的普通 js 對(duì)象。
參數(shù)
createElement( // html 標(biāo)簽 、自定義標(biāo)簽,比如 el-input // template // NOTE 用于傳遞插槽 // 一個(gè)組件選項(xiàng)對(duì)象 // resolve 了上述任何一種的一個(gè) async 函數(shù) // TODO 如何使用 'div', // NOTE 必需的 // 模板使用到的數(shù)據(jù)對(duì)象 {}, // string 或者 子VNode [] )
第一個(gè)參數(shù)不能省略,所以不能返回純文本。 和 react 的 render 不同。
注意
第一個(gè)參數(shù)可以是 template,往往和第二個(gè)參數(shù)的slot
屬性一起使用,指定插槽名稱(chēng),傳遞插槽時(shí)可以用到。
resolve 的用法我沒(méi)有搜索到例子,歡迎大佬告訴我。
重點(diǎn)關(guān)注第二個(gè)參數(shù)
處理樣式和類(lèi)
{ // :class = "{foo:true,bar:false}" class:{ foo: true, bar: false }, // :style="{color:'red','font-size':'14px'}" style:{ color:'red', fontSize:'14px' } }
組件 props
{ props: { myCustomProp: '組件屬性值' } }
HTML 特性和 DOM 屬性
{ // HTML 特性 // NOTE 在組件內(nèi)部使用 $attrs 獲取 attrs // NOTE 會(huì)和 class 屬性合并嗎? // NOTE 和 class 屬性的優(yōu)先級(jí),誰(shuí)高? // 這里的 class 不會(huì)添加到 標(biāo)簽上 attrs: { id: 'divId', class: 'className' }, // DOM 屬性 domProps:{ textContent: 'div 文本',// 優(yōu)先級(jí)高于 v-text innerHTML: 'BAR' // 優(yōu)先級(jí)高于 v-html } }
注意
①. attrs 特性中的 class 不會(huì)被添加到標(biāo)簽上。
②. 注意區(qū)分 HTML 特性和 DOM 屬性的區(qū)別。
處理事件
{ // v-bind:event on: { customEventName: value => { // 監(jiān)聽(tīng)組件的自定義事件 即 emit 觸發(fā)事件 } }, // 監(jiān)聽(tīng)組件上的原生事件,只能用在組件上 nativeOn: { click: target => { // 監(jiān)聽(tīng)原生事件 即非 emit 觸發(fā)的事件 } } }
注意
nativeOn
只能用于自定義組件。
插槽
{ scopedSlots: { // 默認(rèn)插槽 default: props => h('span',props.text), otherSlot: props => h('div',props.customProp) }, slot: 'slotName'// 一般和第一個(gè)參數(shù) template 一起使用 }
使用模板定義一個(gè)按鈕:
<template> <div> <slot name="left"></slot> <button> <slot v-bind:person="person"> <span>按鈕</span> </slot> </button> <slot name="right" v-bind:age="person.age"></slot> </div> </template> <script> export default { name: 'MyButton', data() { return { person: { name: 'jack', age: 23, }, } }, } </script>
在模板種使用該組件:
<MyButton> <template #right="{age}"> <span>按鈕右邊 {{ age }} 歲</span> </template> <template v-slot="{ person }">這是按鈕,{{ person }}</template> <template #left> <span>按鈕左邊</span> </template> </MyButton>
在 render 中使用該組件
import MyButton from './MyButton.vue' export default { name: 'UseButton', render(h) { //NOTE h 第一個(gè)參數(shù)為 template 第二個(gè)參數(shù)里的 slot 屬性指定插槽名稱(chēng) const slotLeft = h('template', { slot: 'left' }, '按鈕左邊') const slotRight = h('template', { slot: 'right' }, '按鈕右邊') const slotDefault = h('template', { slot: 'default' }, '默認(rèn)插槽') const children = [slotLeft, slotDefault, slotRight] return h(MyButton, {}, children) }, }
在 render 中獲取作用域插槽拋出的數(shù)據(jù)
import MyButton from './MyButton.vue' export default { name: 'UseButton', render(h) { const slotLeft = h('template', { slot: 'left' }, '按鈕左邊') const children = [slotLeft] return h( MyButton, { scopedSlots: { default: props => { console.log(props) const { person } = props const text = `作用域插槽,${JSON.stringify(person)}` // 返回 h 創(chuàng)建的 VNode return h('span', {}, text) }, right: props => { console.log(props) const { age } = props // 返回 jsx return <span>按鈕右邊 {age} 歲</span> }, }, }, children ) }, }
總結(jié)
①. 普通命名插槽,使用h('template',{slot:'slotName'},children)
編寫(xiě),然后放渲染組件的第三個(gè)參數(shù)
里。
②. 作用域插槽在第二個(gè)參數(shù)的 scopedSlots
對(duì)象里,該對(duì)象的每個(gè)屬性名是組件的插槽名
,值是一個(gè)函數(shù),參數(shù)為插槽綁定的數(shù)據(jù)。
使用 render 函數(shù)重寫(xiě)編寫(xiě)
MyButton
export default { name: 'MyButton', data() { return { person: { name: 'jack', age: 23, }, } }, render(h) { // NOTE default 關(guān)鍵字 不重命名 無(wú)法解構(gòu) const { left, right, default: _defaultSlot } = this.$scopedSlots // NOTE 傳遞一個(gè)對(duì)象,在模板中使用解構(gòu)取出屬性 const defaultSlot = _defaultSlot({ person: this.person }) const leftSlot = left() const rightSlot = right(this.person) const button = h('button', {}, [defaultSlot]) return h('div', {}, [leftSlot, button, rightSlot]) }, }
返回 jsx
export default { name: 'MyButton', data() { return { person: { name: 'jack', age: 23, }, } }, render(h) { const { left, right, default: _defaultSlot } = this.$scopedSlots // NOTE 檢查插槽是否存在 const defaultSlot = (_defaultSlot && _defaultSlot({ person: this.person })) || <span>按鈕</span> const leftSlot = (left && left()) || '' const rightSlot = right(this.person) const button = h('button', {}, [defaultSlot]) // 返回 jsx 使得 dom 結(jié)構(gòu)更加清晰 return ( <div> {leftSlot} {defaultSlot} {rightSlot} </div> ) }, }
函數(shù)式組件:
export default { name: 'MyButton', functional: true, props: { person: { type: Object, default: () => ({ name: 'jack', age: 23 }), }, }, // NO DATA in functional component // data() { // return { // person: { // name: 'jack', // age: 23, // }, // } // }, render(h, { props, scopedSlots }) { const { left, right, default: _defaultSlot } = scopedSlots const defaultSlot = (_defaultSlot && _defaultSlot({ person: props.person })) || <span>按鈕</span> const leftSlot = (left && left()) || '' const rightSlot = right(props.person) const button = h('button', {}, [defaultSlot]) return ( <div> {leftSlot} {button} {rightSlot} </div> ) }, }
總結(jié)
①. 普通插槽、命名插槽、作用域插槽都通過(guò)this.$scopedSlots
獲取,它們都是返回 VNode 的函數(shù)。
②. 插槽綁定的數(shù)據(jù)通過(guò)插槽函數(shù)傳遞,基本數(shù)據(jù)使用{}
包裹,方便在模板中解構(gòu)。
③. 返回 jsx 能讓 div 結(jié)構(gòu)更加清晰。
④. 注意檢查是否存在插槽,以啟用后備內(nèi)容。
指令
{ directives: [{ name: 'directive-name', value: '2', expression: '1+1', arg: 'foo', modifiers: { foo: true } }] }
在模板中定義指令
<template> <!-- title 是名字, 指令的 value 由表達(dá)式計(jì)算出來(lái) --> <!-- v-title:argument.modifier1.modifier2="expression" --> <div> 在模板中編寫(xiě)指令 <p v-title>這是簡(jiǎn)單指令</p> <!-- 只能帶一個(gè)參數(shù) --> <p v-title:argu>這是帶參數(shù)的指令</p> <!-- 動(dòng)態(tài)參數(shù) --> <p v-title:[dynamicArgu()]>這是帶動(dòng)態(tài)參數(shù)的指令</p> <p v-title:argu.foo.bar>這是帶參數(shù)和修飾符的指令</p> <p v-title:job.foo="data">這是帶參數(shù)、修飾符和普通表達(dá)式的指令</p> <p v-title:job.foo="expresFun">這是帶參數(shù)、修飾符和函數(shù)表達(dá)式的指令</p> </div> </template> <script> export default { name: 'Title', directives: { title: { inserted(el, bindings, vnode) { const { context: that } = vnode const { value = false } = bindings if (typeof value === 'function') { that.setTile(el, value(that.data)) } else { that.setTile(el, value) } }, componentUpdated(el, bindings, vnode) { const { context: that } = vnode const { value = false } = bindings if (typeof value === 'function') { that.setTile(el, value(that.data)) } else { that.setTile(el, value) } }, }, }, data() { return { data: { age: 23, job: 'web dev' }, } }, methods: { setTile(el, titleValue) { const textContent = el.textContent const title = textContent.trim() || '暫無(wú)數(shù)據(jù)' el.title = typeof titleValue === 'string' ? titleValue : title }, dynamicArgu() { return Math.random() > 0.5 ? 'argu1' : 'argu0' }, expresFun(data) { return data.age + '歲' }, }, } </script>
指令對(duì)象 bindings
總結(jié)
不建議在 render 函數(shù)中編寫(xiě)指令,難以理解,指令需要在模板使用才能發(fā)揮其設(shè)計(jì)的目的。render 中可直接控制 DOM。
v-model 指令
使用 render 定義組件,如何提供 v-model
?
說(shuō)明:
prop:–value +使用 on
監(jiān)聽(tīng)組件的事件,在處理函數(shù)中觸發(fā)input
自定義事件。
在 render 函數(shù)中使用 v-model 指令的處理有三種方案:
① . 在數(shù)據(jù)對(duì)象中使用 model
屬性:
{ model: { value: this.value,// value 是 data 里的屬性 callback: value => { // 可以再賦值之前做其他邏輯 // 驗(yàn)證數(shù)據(jù) // 觸發(fā)事件 this.value = value } } }
②. 傳遞 value + 監(jiān)聽(tīng) input 事件
{ props: { // value 是 data 中的屬性 value: this.value }, on: { input: value => { // 可做其他事情 // 觸發(fā)事件 this.value = value } } }
③. 在 jsx 中使用 vModel
屬性
// input 是 data 中的屬性 <MyInput vModel={this.input} />
三種方案的優(yōu)缺點(diǎn):
model
屬性更加好,當(dāng)表單項(xiàng)還有其他事件時(shí),還可以在 on
中監(jiān)聽(tīng)它們,比如 element 的下拉,有change
、clear
等事件。
props value
+ input
, 很符合 v-model 的語(yǔ)法糖。
jsx+ vModel
屬性,簡(jiǎn)潔,常用。
其他屬性
{ key: 'v for 中的 key', ref:'模板變量', refInFor: true, // 循環(huán)中的 ref 是一個(gè)數(shù)組 }
使用 render 封裝一個(gè)輸入框
MyInput.jsx
import './my-input.css' export default { name: 'MyInput', props: { // 需要實(shí)現(xiàn) v-model 指令 value: { type: [String, Number], default: '', }, }, render(h) { return h('input', { class: { 'my-input': true, }, style: { backgroundColor: '#ccc', }, attrs: { id: 'my-input', class: 'a-my-input', 'data-key': 'key', }, domProps: { value: this.value, }, // 監(jiān)聽(tīng) input 的 input 事件 on: { input: ({ target }) => { this.$emit('input', target.value) }, }, }) }, }
說(shuō)明
還可以使用computed
: domProp 的 value 接收一個(gè)計(jì)算屬性,為該計(jì)算屬性提供 setter 和 getter ,在 input 事件處理函數(shù)中設(shè)置計(jì)算屬性的值,在 setter 中觸發(fā) 自定義的 input 事件。這種方法不如上面的明白,代碼量也多了。
在模板中使用該組件
<MyInput v-model="myInput" />
在 render 函數(shù)中使用
export default { name: 'UseInput', data() { return { input: '', } }, render(h) { return h('div', {}, [ h(MyInput, { model: { value: this.input, callback: value => { // 可在此做其他事件 this.input = value }, }, }), h('h3', {}, this.input), ]) }, }
希望 UseInput
,支持 v-model
,即在二次封裝 MyInput。
方案 1:添加 value props
在 model 中觸發(fā) input
,刪除 data 中的 input。
import MyInput from './my-input.jsx' export default { name: 'UseInput', props: { value: { type: [String, Number], default: '' }, }, render(h) { return h('div', {}, [ h(MyInput, { model: { value: this.value, callback: value => { // 可在此做其他事件 this.$emit('input', value) }, }, }), h('h3', {}, this.value), ]) }, }
方案 2: 添加 value props
,將其通過(guò) props
傳入 UseInput,監(jiān)聽(tīng) UseInput 的input
事件,在此觸發(fā)input
事件。
import MyInput from './my-input.jsx' export default { name: 'UseInput', props: { value: { type: [String, Number], default: '' }, }, render(h) { return h('div', {}, [ h(MyInput, { props: { value: this.value, }, on: { input: value => { this.$emit('input', value) }, }, }), h('h3', {}, this.value), ]) }, }
說(shuō)明: 對(duì)于具有多種事件的表單項(xiàng),比如 element 的下拉框,第一種方案更加好,
on
屬性留位置給從外傳入的處理函數(shù)。
方案 3: jsx + vModel + prop value
import MyInput from './my-input.jsx' export default { name: 'UseInput', props: { value: { type: [String, Number], default: '' }, }, data() { return { input: this.value, } }, render(h) { return ( <div> <MyInput vModel={this.input} /> {/* <h2>{this.input}</h2> */} </div> ) }, }
** 注意**:這種方案不能實(shí)現(xiàn)雙向綁定
其他問(wèn)題
如何限制繼承的屬性,inheritAttrs 設(shè)置為 false,無(wú)法顯示。
在模板定義的組件中,inheritAttrs
屬性設(shè)置為 false, 除style
、class
以為的屬性不會(huì)添加到根組件,實(shí)現(xiàn)手動(dòng)控制。
render 定義的組件中,也是一樣的。
參考
What does the ‘h’ stand for in Vue’s render method?
A Practical Use Case for Vue Render Functions: Building a Design System Typography Grid
How to use v-model ( for custom input component) in render function?
到此這篇關(guān)于vue2 中如何使用 render 函數(shù)編寫(xiě)組件的文章就介紹到這了,更多相關(guān)vue2使用 render 函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vite+vue3項(xiàng)目初始化搭建的實(shí)現(xiàn)步驟
本文主要介紹了vite+vue3項(xiàng)目初始化搭建的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07Vue鼠標(biāo)點(diǎn)擊事件和鍵盤(pán)事件舉例詳解
在Vue框架中我們經(jīng)常需要綁定各種JS事件,如"點(diǎn)擊事件"、"鼠標(biāo)移動(dòng)事件"、"鍵盤(pán)事件"等等,這篇文章主要給大家介紹了關(guān)于Vue鼠標(biāo)點(diǎn)擊事件和鍵盤(pán)事件的相關(guān)資料,需要的朋友可以參考下2024-01-01VUE使用vue?create命令創(chuàng)建vue2.0項(xiàng)目的全過(guò)程
vue-cli是創(chuàng)建Vue項(xiàng)目的一個(gè)腳手架工具,vue-cli提供了vue create等命令,下面這篇文章主要給大家介紹了關(guān)于VUE使用vue?create命令創(chuàng)建vue2.0項(xiàng)目的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07在Vue中使用Avue、配置過(guò)程及實(shí)際應(yīng)用小結(jié)
在項(xiàng)目中遇到通過(guò)點(diǎn)擊加號(hào)實(shí)現(xiàn)輸入框的增加、以及對(duì)該輸入框的輸入內(nèi)容進(jìn)行驗(yàn)證,通過(guò)這些誘導(dǎo)因素創(chuàng)作的這篇文章,本文重點(diǎn)給大家介紹在Vue中使用Avue、配置過(guò)程以及實(shí)際應(yīng)用,需要的朋友可以參考下2022-10-10Vue手寫(xiě)防抖和節(jié)流函數(shù)代碼詳解
在Vue中函數(shù)的防抖和節(jié)流不是什么新鮮話題,這篇文章主要給大家介紹了關(guān)于Vue3中防抖/節(jié)流的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02vue實(shí)現(xiàn)登錄滑動(dòng)拼圖驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)登錄滑動(dòng)拼圖驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03element-ui時(shí)間日期選擇器限制選擇范圍的幾種場(chǎng)景
這篇文章主要給大家介紹了關(guān)于element-ui時(shí)間日期選擇器限制選擇范圍的幾種場(chǎng)景,一般在實(shí)際開(kāi)發(fā)場(chǎng)景中我們需要對(duì)時(shí)間選擇做一些限制,如不能選擇今天之前的時(shí)間、不能選擇今天以后的日期、限制日期不能大于開(kāi)始日期等等,需要的朋友可以參考下2023-08-08