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>
)
}