[toc]
通過本文, 你可以學(xué)到一些
vue中jsx的語法。
vue更加推薦使用模板開發(fā)組件,但是在一些基礎(chǔ)組件開發(fā)中,為了獲取js的完全編程能力,不可避免需要使用一些jsx語法,而官方vue jsx對vue特有的語法糖這方面的文檔特別少, 本文記錄一些本人在開發(fā)過程中使用jsx的經(jīng)驗(yàn)和思考。
整體上來看,由于vue的createElement跟react的createElement有區(qū)別,導(dǎo)致jsx的寫法也有一些區(qū)別.但整體上還是符合react的jsx,react jsx進(jìn)階的語法。
如果需要了解vue jsx特殊寫法的原理,可以閱讀babel-plugin-transform-vue-jsx , 本文不做探討。
data寫法
jsx本質(zhì)上是createElement的語法糖,最終會被編譯器轉(zhuǎn)為createElement函數(shù).當(dāng)在jsx的標(biāo)簽中使用{ ...obj }時, obj將會編譯為createElement的第二個參數(shù).
vue的createElement跟react的createElement函數(shù)第二個參數(shù)意義是不一樣的.在vue中,第二個參數(shù)是 data對象, 而react第二個參數(shù)是props。所以本人將這種方式稱為data寫法。
如在vue中需要設(shè)置動態(tài)屬性時:
const props={
name: 'joyer',
},
<my-button {...{
props:props,
}}></my-button>
當(dāng)不知道模板中某個vue語法怎么用jsx實(shí)現(xiàn)時,可以先轉(zhuǎn)換為createElement的data對象,然后使用{...data}寫在jsx標(biāo)簽上(本文重點(diǎn)).
如官方推薦原生dom屬性的jsx寫法:
<button domPropsType="submit"><button>
采用data寫法為:
<button { ...{
domProps: {
type: 'submit',
},
}}><button>
該方式不夠優(yōu)雅,如果官方有更加優(yōu)雅的語法糖,推薦使用官方推薦。如果某個語法,官方?jīng)]有案例,該方式就可以作為最終選擇。 并且通過這種方式,createElement中所有的特性都可以用于jsx的開發(fā)了。
下文中所有的寫法, 都可以使用data寫法。
v-model的寫法
官網(wǎng)中v-model寫法不可行。
模板中寫法為:
<el-input v-model.trim="inputValue"/>
jsx寫法需要為:
<el-input vModel_trim={inputValue}/>
// 或者使用
<el-input
value={this.inputValue}
on-input={val => this.inputValue = val.trim()}/>
v-for
模板中的寫法:
<el-tag
v-for="(item, index) in content"
:key="index"
type="success"
>
{{item.name}}
</el-tag>
jsx的寫法:
{
this.content.map((item, index) = >{
return (<el-tag
key={ index }
type="success">{ item.name }</el-tag>);
})
}
事件 & 按鍵修飾符
官方的寫法
<input vOn:click_stop_prevent="newTodoText" />
一些編輯器會提示語法錯誤(因?yàn)?code>react的jsx不允許這樣寫),推薦使用下面的寫法
<input
nativeOn-keyup={arg => arg.keyCode === 13 && this.fetch()}
on-click={event => {event.stopPropagation();event.preventDefault();this.click()} }/>
修飾符需要自己在代碼中實(shí)現(xiàn)?;蛘呖梢允褂眯揎椃唽?,對照官網(wǎng)的語法, jsx寫法為:
<input {...{
on: {
'!click': () => this.doThisInCapturingMode,
'~keyup': () => this.doThisOnce,
'~!mouseover': () => this.doThisOnceInCapturingMode
}
}} />
事件處理都采用了箭頭函數(shù), 跟react一樣, 需要處理this綁定問題,可以使用bind綁定,
`on-click={this.click.bind(this, args) }`
不能直接賦值函數(shù)on-click={this.click}。
高階組件中的v-on="$listeners"和v-bind="$attrs"
在高階組件中, 一般都會使用v-on="$listeners"和v-bind="$attrs",但在官方文檔沒有說明jsx如何實(shí)現(xiàn),只有createElement中說明了實(shí)現(xiàn):
return createElement('div', {
props: {
...$attrs,
otherProp: value,
},
on: {
...$listeners,
click() {
},
}
},this.$slots.default])
參照data寫法, jsx實(shí)現(xiàn)為:
const data = {
props: {
...$attrs,
otherProp: value,
},
on: {
...$listeners,
click() {
},
}
}
<button { ...data }><button>
對于$attrs和$listeners可以有更加靈活的用法。如要實(shí)現(xiàn)elemet-ui中一個可以快速布局el-form-item高階組件,將el-form-item跟el-col的結(jié)合:
render(h) {
// 拆分出作用于col和form-item上的屬性
const colAttrs = {};
const itemAtts = {};
this.$attrs.forEach((attrName) => {
// 這里使用了lodash
if (_.startsWith(attrName, `col-`)) {
colAttrs[attrName.replace(`col-`, '')] = this.$attrs[attrName];
return;
}
itemAtts[attrName] = this.$attrs[attrName];
});
return (<el-col {
...{
props: this.colAttrs,
}
}>
<el-form-item {
...{
props: this.itemAtts,
}
}></el-form-item>
</el-col>);
}
該高階組件可以傳遞兩種類型的屬性, 帶col-開頭的屬性,作用于el-col上面, 其他屬性作用于el-form-item組件上。
如果需要標(biāo)簽屬性透傳,將當(dāng)前組件的所有attrs傳遞給內(nèi)部組件(內(nèi)部組件也用v-bind="$attrs"),還需設(shè)置attrs值。
如高階組件my-button:
<el-button v-bind="$attrs" type="primary">
<slot></slot>
</el-button>
高階組件high-button:
render(h) {
return (<my-button { ...{
props: this.attrs,
attrs: this.$attrs,
} }><my-button>);
}
通過設(shè)置attrs將high-button接收到的所有標(biāo)簽設(shè)置傳遞給my-button中。如果不這樣做, 那么my-button中將接收不到任何屬性設(shè)置,因?yàn)橹粋鬟fprops,在high-button組件中對于沒有匹配high-button的props聲明的標(biāo)簽屬性都會被丟棄。
slot寫法
默認(rèn)插槽模板寫法:
<button>
<slot></slot>
</button>
jsx的寫法:
<button>
{this.$scopedSlots.default && this.$scopedSlots.default()}
</button>
具名插槽模板寫法:
<button>
<slot name="before"></slot>
<slot ></slot>
</button>
jsx寫法:
let before = '';
if (this.$scopedSlots.before) {
before = this.$scopedSlots.before(props => props.text);
}
return (<button>
{ before }
{this.$scopedSlots.default && this.$scopedSlots.default()}
</button>)
作用域插槽模板寫法:
<slot :isAdvancedPanelShow="isAdvancedPanelShow"></slot>
jsx寫法:
{this.$scopedSlots.default && this.$scopedSlots.default({
isAdvancedPanelShow: this.isAdvancedPanelShow
})}
所有的插槽(
this.$scopedSlots[${slotName}])在調(diào)用時, 都盡量判斷以下是否為空, 因?yàn)榭赡艽嬖谑褂媒M件時, 沒有傳遞插槽內(nèi)容.
動態(tài)組件名字
還記得官網(wǎng)怎么介紹createElement獲取js的完全編程能力嗎? 舉了一個根據(jù)不同的等級使用不同標(biāo)題的案例:
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 標(biāo)簽名稱
this.$slots.default // 子節(jié)點(diǎn)數(shù)組
)
},
props: {
level: {
type: Number,
required: true
}
}
})
這個案例通過jsx的寫法為:
Vue.component('anchored-heading', {
render: function (h) {
const TagName = 'h' + this.level;
return <TagName>{ this.$slots.default(this.props) }</TagName>
},
props: {
level: {
type: Number,
required: true
}
}
})
要知道,vue的createElement函數(shù)的第一個參數(shù)可以是一個 HTML 標(biāo)簽名、組件選項(xiàng)對象,或者resolve了上述任何一種的一個async函數(shù)。
動態(tài)組件編排
在實(shí)際開發(fā)中, 碰到一種場景, 有組件CompA, CompB, CompC 。如果是a情況,需要在當(dāng)前組件按照CompA, CompB, CompC順序展示, 如果是不是a情況, 則通過CompC, CompB, CompA來展示。jsx寫法為:
render(h) {
// 假設(shè)組件A是需要特殊設(shè)置一些屬性的
const compA = (<CompA name="joyer">hellow word</CompA>)
const content = this.status === 'a' ? [compA, CompB, CompC] : [CompC, CompB, compA];
return (<div>{content}</div>)
}
這里末尾也可以這樣寫:
return (<div>{...content}</div>)
但在有些編輯器會報錯,因?yàn)樵?code>react不允許這樣寫:
Spread children are not supported in React.
在babel官方編輯器中嘗試
可以在babel官方提供的編輯器中jsx嘗試代碼,不過需要記得在左邊最下角添加babel=plugin-transform-vue-jsx插件:
