Vue 之 JSX 初识篇

 

介绍一下 JSX

JSX 简介

JSX 是一种 Javascript 的语法扩展,​​JSX​​​ = ​​Javascript​​​ + ​​XML​​​,即在​​Javascript​​​里面写​​XML​​​,因为​​JSX​​​的这个特性,所以他即具备了​​Javascript​​​的灵活性,同时又兼具​​html​​的语义化和直观性。



学习 JSX,先了解一下 createElement

提到​​JSX​​​,不可避免的就要提到​​createElement​​,当你看完本节,你会发现,奇怪的知识又增多了。

无论是​​Vue​​​还是​​React​​​,都存在​​createElement​​​,而且作用基本一致。可能你对​​createElement​​​不是很了解,函数名翻译过来就是增加一个元素,但他的返回值你一定知道。​​createElement​​​函数返回的值称之为虚拟节点,即​​VNode​​​,而由​​VNode​​​扎堆组成的树便是大名鼎鼎,面试必问的​​虚拟DOM​​。

​createElement​​​函数的参数,在这里抄一下​​Vue​​官方文档

// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',

// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},

// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)

 

从上面可以看出​​createElement​​一共有三个参数,三个参数分别是

  • 第一个参数是需要渲染的组件,可以是组件的标签,比如​​div​​​;或者是一个组件对象,也就是你天天写的​​export default {}​​;亦或者可以是一个异步函数。
  • 第二个参数是这个组件的属性,是一个对象,如果组件没有参数,可以传 null(关于组件的属性,下文将依次介绍)
  • 第三个参数是这个组件的子组件,可以是一个字符串(textContent)或者一个由 VNodes 组成的数组

 

 

没有 v-model 怎么办,还有其他指令可以用吗?

当你选择使用​​JSX​​​的时候,你就要做好和指令说拜拜的时候了,在​​JSX​​​中, 你唯一可以使用的指令是​​v-show​​​,除此之外,其他指令都是不可以使用的,有没有感到很慌,这就对了。不过呢,换一个角度思考,指令只是​​Vue​​​在模板代码里面提供的语法糖,现在你已经可以写​​Js​​​了,那些语法糖用​​Js​​都可以代替了。

v-model

​v-model​​​是​​Vue​​​提供的一个语法糖,它本质上是由 ​​value​​​属性(默认) + ​​input​​​事件(默认)组成的, 所以,在​​JSX​​​中,我们便可以回归本质,通过传递​​value​​​属性并监听​​input​​事件来实现数据的双向绑定



export default {
data() {
return {
name: ''
}
},
methods: {
// 监听 onInput 事件进行赋值操作
$_handleInput(e) {
this.name = e.target.value
}
},
render() {
// 传递 value 属性 并监听 onInput事件
return <input value={this.name} onInput={this.$_handleInput}></input>
}
}

 

v-if 与 v-for

在模板代码里面我们通过​​v-for​​​去遍历元素,通过​​v-if​​​去判断是否渲染元素,在​​jsx​​​中,对于​​v-for​​​,你可以使用​​for​​​循环,​​array.map​​​来代替,对于​​v-if​​​,可以使用​​if​​​语句,​​三元表达式​​等来代替

循环遍历列表


const list = ['java', 'c++', 'javascript', 'c#', 'php']
return (
<ul>
{list.map(item => {
return <li>{item}</li>
})}
</ul>
)

 使用条件判断

const isGirl = false
return

 

v-bind

在模板代码中,我们一般通过 ​​v-bind:prop="value"​​​或​​:prop="value"​​​来给组件绑定属性,在​​JSX​​里面写法也类似


render() {
return <input value={this.name}></input>
}

 

v-html 与 v-text

在说​​v-html​​​与​​v-text​​​之前,我们需要先了解一下​​Vue​​​中的属性,​​Vue​​中的属性一共分为三种:

第一种是大家写 bug 时候最常用的​​props​​,即组件自定义的属性;

第二种是​​attrs​​,是指在父作用域里面传入的,但并未在子组件内定义的属性。

第三种比较特殊,是​​domProps​​​,经小编不完全测试,在​​Vue​​​中,​​domProps​​​主要包含三个,分别是​​innerHTML​​​,​​textContent/innerText​​​和​​value​​。

  • ​v-html​​​: 在模板代码中,我们用​​v-html​​​指令来更新元素的​​innerHTML​​​内容,而在​​JSX​​​里面,如果要操纵组件的​​innerHTML​​​,就需要用到​​domProps​



export default {
data() {
return {
content: '<div>这是子君写的一篇新的文章</div>'
}
},
render() {
// v-html 指令在JSX的写法是 domPropsInnerHTML
return <div domPropsInnerHTML={this.content}></div>
}
}

 

  • ​v-text​​​: 看了上面的​​v-html​​​,你是不是立即就想到了​​v-text​​​在​​JSX​​​的写法​​domPropsInnerText​​,是的,你没有想错
export default {
data() {
return {
content: '这是子君写的一篇新的文章的内容'
}
},
render() {
return <div domPropsInnerText={this.content}></div>
}
}

 

但实际上我们不需要使用​​domPropsInnerText​​,而是将文本作为元素的子节点去使用即可


<div>{this.content}</div>

 

实际上,对于​​domProps​​​,只有​​innerHTML​​​才需要使用​​domPropsInnerHTML​​的写法,其他使用正常写法即可

 

 

我还要监听事件呢

监听事件与原生事件

 

当我们开发一个组件之后,一般会通过​​this.$emit('change')​​​的方式对外暴露事件,然后通过​​v-on:change​​​的方式去监听事件,很遗憾,在​​JSX​​​中你无法使用​​v-on​​指令,但你将解锁一个新的姿势



render() {
return <CustomSelect onChange={this.$_handleChange}></CustomSelect>
}

 

​JSX​​​中,通过​​on​​​ + 事件名称的大驼峰写法来监听,比如事件​​icon-click​​​,在​​JSX​​​中写为​​onIconClick​

有时候我们希望可以监听一个组件根元素上面的原生事件,这时候会用到​​.native​​修饰符,有点绝望啊,修饰符也是不能用了,但好在也有替代方案,如下代码

render() {
// 监听下拉框根元素的click事件
return <CustomSelect nativeOnClick={this.$_handleClick}></CustomSelect>
}

 

监听原生事件的规则与普通事件是一样的,只需要将前面的​​on​​​替换为​​nativeOn​

除了上面的监听事件的方式之外,我们还可以使用对象的方式去监听事件


render() {
return (
<ElInput
value={this.content}
on={{
focus: this.$_handleFocus,
input: this.$_handleInput
}}
nativeOn={{
click: this.$_handleClick
}}
></ElInput>
)
}

 

事件修饰符

和指令一样,除了个别的之外,大部分的事件修饰符都无法在​​JSX​​中使用,这时候你肯定已经习惯了,肯定有替代方案的。

  • ​.stop​​​ : 阻止事件冒泡,在​​JSX​​​中使用​​event.stopPropagation()​​来代替
  • ​.prevent​​​:阻止默认行为,在​​JSX​​​中使用​​event.preventDefault()​​ 来代替
  • ​.self​​:只当事件是从侦听器绑定的元素本身触发时才触发回调,使用下面的条件判断进行代替


if (event.target !== event.currentTarget){
return
}

 

  • ​.enter​​​与​​keyCode​​: 在特定键触发时才触发回调
if(event.keyCode === 13) {
// 执行逻辑

 

 

除了上面这些修饰符之外,尤大大为了照顾我们这群 CV 仔,还是做了一点优化的,对于​​.once​​​,​​.capture​​​,​​.passive​​​,​​.capture.once​​,尤大大提供了前缀语法帮助我们简化代码


render() {
return (
<div
on={{
// 相当于 :click.capture
'!click': this.$_handleClick,
// 相当于 :input.once
'~input': this.$_handleInput,
// 相当于 :mousedown.passive
'&mousedown': this.$_handleMouseDown,
// 相当于 :mouseup.capture.once
'~!mouseup': this.$_handleMouseUp
}}
></div>
)
}

 

 

插槽

插槽就是子组件中提供给父组件使用的一个占位符,插槽分为默认插槽,具名插槽和作用域插槽,下面小编依次为你带来每种在​​JSX​​中的用法与如何去定义插槽。

默认插槽

  • 使用默认插槽

使用​​element-ui​​​的​​Dialog​​​时,弹框内容就使用了默认插槽,在​​JSX​​中使用默认插槽的用法与普通插槽的用法基本是一致的,如下代码所示:

render() {
return (
<ElDialog title="弹框标题" visible={this.visible}>
{/*这里就是默认插槽*/}
<div>这里是弹框内容</div>
</ElDialog>
)
}

 

  • 自定义默认插槽

在​​Vue​​​的实例​​this​​​上面有一个属性​​$slots​​​,这个上面就挂载了一个这个组件内部的所有插槽,使用​​this.$slots.default​​就可以将默认插槽加入到组件内部


export default {
props: {
visible: {
type: Boolean,
default: false
}
},
render() {
return (
<div class="custom-dialog" vShow={this.visible}>
{/*通过this.$slots.default定义默认插槽/}
{this.$slots.default}
</div>
)
}
}

 

具名插槽

  • 使用具名插槽

有时候我们一个组件需要多个插槽,这时候就需要为每一个插槽起一个名字,比如​​element-ui​​​的弹框可以定义底部按钮区的内容,就是用了名字为​​footer​​的插槽

render() {
return (
<ElDialog title="弹框标题" visible={this.visible}>
<div>这里是弹框内容</div>
{/** 具名插槽 */}
<template slot="footer">
<ElButton>确定</ElButton>
<ElButton>取消</ElButton>
</template>
</ElDialog>
)
}

 

 

  • 自定义具名插槽

在上节自定义默认插槽时提到了​​$slots​​​,对于默认插槽使用​​this.$slots.default​​​,而对于具名插槽,可以使用​​this.$slots.footer​​进行自定义

 

render() {
return (
<div class="custom-dialog" vShow={this.visible}>
{this.$slots.default}
{/**自定义具名插槽*/}
<div class="custom-dialog__foolter">{this.$slots.footer}</div>
</div>
)
}

 

 

 

作用域插槽

  • 使用作用域插槽
    有时让插槽内容能够访问子组件中才有的数据是很有用的,这时候就需要用到作用域插槽,在​​JSX​​中,因为没有​​v-slot​​指令,所以作用域插槽的使用方式就与模板代码里面的方式有所不同了。比如在​​element-ui​​中,我们使用​​el-table​​的时候可以自定义表格单元格的内容,这时候就需要用到作用域插槽



data() {
return {
data: [
{
name: '子君'
}
]
}
},
render() {
return (
{/*scopedSlots即作用域插槽,default为默认插槽,如果是具名插槽,将default该为对应插槽名称即可/}
<ElTable data={this.data}>
<ElTableColumn
label="姓名"
scopedSlots={{
default: ({ row }) => {
return <div style="color:red;">{row.name}</div>
}
}}
></ElTableColumn>
</ElTable>
)
}

 

  • 自定义作用域插槽

使用作用域插槽不同,定义作用域插槽也与模板代码里面有所不同。加入我们自定义了一个列表项组件,用户希望可以自定义列表项标题,这时候就需要将列表的数据通过作用域插槽传出来。

render() {
const { data } = this
// 获取标题作用域插槽
const titleSlot = this.$scopedSlots.title
return (
<div class="item">
{/* 如果有标题插槽,则使用标题插槽,否则使用默认标题 /}
{titleSlot ? titleSlot(data) : <span>{data.title}</span>}
</div>
)
}

 

只能在 render 函数里面使用 JSX 吗

 当然不是,你可以定义​​method​​​,然后在​​method​​​里面返回​​JSX​​​,然后在​​render​​​函数里面调用这个方法,不仅如此,​​JSX​​还可以直接赋值给变量,比如下面这段代码

 

 

methods: {
$_renderFooter() {
return (
<div>
<ElButton>确定</ElButton>
<ElButton>取消</ElButton>
</div>
)
}
},
render() {
const buttons = this.$_renderFooter()
return (
<ElDialog visible={this.visible}>
<div>这里是一大坨内容</div>
<template slot="footer">{buttons}</template>
</ElDialog>
)
}