前阵子在 Vue3 项目中封装一个基础组件的时候用到了 JSX 语法,使用下来的感受就是 —— 某些场景下,JSX 的灵活性对我们编写代码还是能够带来一定的帮助的。
举两个常见的例子:
假设我们现在有如下数据,需要渲染其中的 name 字段:
const data = [
{
name: 'name1',
children: [{ name: 'name1-1' }]
},
{ name: 'name2' }
]
如果使用普通模板写法,为了递归我们可能不得不编写两个组件:
父组件 parent.vue:
// parent.vue 父组件
<template>
<div>我是父组件</div>
<Children v-for="item in data" :subData="item" :key="item.name"></Children>
</template>
子组件 children.vue 递归调用自身:
// children.vue 子组件
<template>
<span>{{ subData.name }}</span>
// 递归调用
<template v-if="subData.children">
<Children v-for="item in subData.children" :subData="item" :key="item.name"></Children>
</template>
</template>
而使用 JSX 则可以灵活地使用一个文件实现递归的逻辑:
// name.jsx
const renderChildren = (data: any) => {
return data.map((item: any) => {
if (item.children) {
return renderChildren(item.children)
} else {
return <span>{ item.name }</span>
}
})
}
const render = () => (
<>
<div>我是父组件</div>
{
data.map((item: any) => {
return (
<>
<span>{ item.name }</span>
{ item.children && renderChildren(item.children) }
</>
)
})
}
</>
)
这是一个来自 vue 官网中的例子,如果你需要根据传入的 level 动态生成 <h1></h1> 到 <h6></h6> 之间的标签。
你可能会这样写:
<template>
<h1 v-if="level === 1"></h1>
<h2 v-else-if="level === 2"></h2>
<h3 v-else-if="level === 3"></h3>
<h4 v-else-if="level === 4"></h4>
<h5 v-else-if="level === 5"></h5>
<h6 v-else-if="level === 6"></h6>
</template>
但是如果学会了 JSX 的写法,就可以像这样:
const render = () => {
const level = props.level
const Tag = `h${level}`
return (
<Tag></Tag>
)
}
是不是瞬间简洁了许多!省下来的时间又可以用来愉快的摸鱼啦。
那么接下来我们就来一起看看,如何在 Vue 中使用 JSX吧!
如果是使用 vue-cli 搭建的项目,默认就是支持 JSX 语法的,直接使用就可以。
如果不是 vue-cli 搭建的 webpack 项目,需要按照如下步骤开启:
npm install @vue/babel-plugin-jsx -D
在 babel 的配置文件中添加:
{
"plugins": ["@vue/babel-plugin-jsx"]
}
根据版本的不同,babel 配置文件可能是 .babelrc 或者 babel.config.js,注意区分。
使用 vite 的项目,同样需要先安装插件:
npm install @vitejs/plugin-vue-jsx -D
然后在 vite.config.js 文件中添加以下配置:
// vite.config.js
import vueJsx from '@vitejs/plugin-vue-jsx'
export default {
plugins: [
vueJsx({
// options are passed on to @vue/babel-plugin-jsx
}),
],
}
更多的配置请参考 babel-plugin-jsx。
在 JSX 语法中,可以通过一对大括号 {} 来使用 JS 表达式:
const name = 'zhangsan'
// 通过一对大括号 {} 来包裹 JS 表达式内容
const list1 = <div>{name}</div>
// 同样可以通过大括号 {} 来给标签传递动态属性
const id = 1
const list2 = <div id={id}>{name}</div>
或许你还看到过双大括号 {{}} 这种令人迷惑的写法,其实它表示 绑定的是个 JS 对象:
const name = 'zhangsan'
// 双大括号 {{}} 表示的是绑定的是个 JS 对象
// 可以拆分成 {} 和 { width: '100px' } 来理解
const list1 = <div style={{width:'100px'}}>{name}</div>
Vue3 新增了新特性 Fragment,使得我们在模板语法中能够返回多个根节点:
<template>
<div>Fragment</div>
<span>yes</span>
</template>
Vue 的编译器在编译时,会把这种包含多个根节点的模板被表示为一个片段(Fragment)。
但是在 JSX 中,一组元素必须被包裹在一个闭合标签中返回;因此下面这种写法是不允许的:
// 错误写法 ❌
const render = () => (
<div>Fragment</div>
<span>yes</span>
)
正确做法是用一对闭合标签包裹:
// 正确写法 ✅
const render = () => (
<div>
<div>Fragment</div>
<span>yes</span>
</div>
)
那如果我们不想引入额外的标签该怎么办呢?可以用 <></> 来包裹我们想要返回的内容,如下:
const render = () => (
<>
<div>Fragment</div>
<span>yes</span>
</>
)
乍一看,你是不是觉得 <></> 和我们在使用模板写法时的 <template></template> 作用很相似?
但是实际上,JSX 中被 <></> 标签包裹的内容,会被当做 Fragment 来处理;并且针对 Fragment Vue 在编译和渲染时会有特定的优化策略。
而对于 <template></template>,Vue 只会将其作为一个普通的元素渲染;所以要注意别搞混咯。
在 Vue2 的时代,使用 JSX 时传递属性还是比较麻烦的。
因为 Vue2 中将属性又细分成了 组件属性、HTML Attribute 以及 DOM Property 等等,不同的属性写法也大相径庭,如下:
const render = () => {
return (
<div
// 传递一个 HTML Attribute,属性名称是 id 属性值是 'foo'
id="foo"
// 传递 DOM Property 需要使用前缀 `domProps` 来表示,这里表示传递给 innerHTML 这个 DOM 属性的值为 ‘bar’
domPropsInnerHTML="bar"
// 绑定原生事件需要以 `on` 或者 `nativeOn` 为前缀,相当于 @click.native
onClick={this.clickHandler}
nativeOnClick={this.nativeClickHandler}
// 绑定自定义事件需要用 `props` + 事件名 的方式
propsOnCustomEvent={this.customEventHandler}
// class(类名)、style(样式)、key、slot 和 ref 这些特殊属性写法
class={{ foo: true, bar: false }}
style={{ color: 'red', fontSize: '14px' }}
slot="slot"
key="key"
ref="ref"
// 如果是循环生成的 ref(相当于 v-for),那么需要添加 refInFor 这个标识
// 用来告诉 Vue 将 ref 生成一个数组,否则只能获取到最后一个
refInFor>
</div>
)
}
而在 Vue3 的 JSX 中传递各种属性的方式已经简化了许多,下面会拆开细讲,各位小伙伴们请接着往下看~
传递 DOM Property 时去掉了 domProps 前缀,可以直接书写:
const render = () => {
return (
<div innerHTML="bar"></div>
)
}
Vue3 JSX 传递 HTML Attribute 与 Vue2 JSX 相同,直接书写就行:
const render = () => {
return (
<div id="foo" type="email"></div>
)
}
如果需要动态绑定:
const placeholderText = 'email';
const render = () => {
return (
<input
type="email"
placeholder={placeholderText}
/>
)
};
Vue3 JSX 传递类名与样式的方法,与 Vue2 JSX 相同:
const render = () => {
return (
<div
class={{ foo: true, bar: false }}
style={{ color: 'red', fontSize: '14px' }}
>
</div>
)
}
定义好 ref 后用 JS 表达式绑定就行:
const divRef = ref()
const render = () => {
return (
<div ref={divRef}></div>
)
}
在 Vue 模板写法中,绑定事件时我们使用 v-on 或者 @ 符号:
<template>
<div @click="handleClick">事件绑定</div>
</template>
而在 Vue3 的 JSX 中,会把以 on 开头,并紧跟着大写字母的属性当作事件监听器来解析;
上面的模板写法换成 JSX 就是:
const render = (
<div onClick={handleClick}>事件绑定</div>
)
注意:这里一定要以 on 开头,并且紧跟着大写字母。
错误写法 ❌:onclick、click;
正确写法 ✅:onClick、onClickChange、onClick-change (虽然但是,不会真的有人这么写吧?)
如果你不喜欢这种写法,还可以通过打开 babel 的 transformOn 配置,然后通过属性 on 绑定一个对象,一次性传递多个事件:
// babel 配置
{
"plugins": [
[
"@vue/babel-plugin-jsx",
{
"transformOn": true
}
]
]
}
// 通过属性 on 绑定对象 批量传递多个事件
const render = () => (
<div on={{ click: handleClick, input: handleInput }}> 事件绑定 </div>
)
对于 .passive、.capture 和 .once 事件修饰符,可以使用驼峰写法将他们拼接在事件名称后面。
比如 onClick + Once,就代表监听 click 事件触发,且只触发一次:
const render = () => (
<input
onClickCapture={() => {}}
onKeyupOnce={() => {}}
onMouseoverOnceCapture={() => {}}
/>
)
而像其余的 .self、.prevent 等事件和按键修饰符,则需要使用 withModifiers 函数。
withModifiers 函数接收两个参数:
import { withModifiers } from 'vue'
const count = ref(0)
const render = () => (
<input
onClick={
withModifiers(
// 第一个参数是回调函数
() => count.value++,
// 第二个参数是修饰符组成的数组
['self', 'prevent']
)
}
/>
)
上面的写法就相当于我们在 Vue 模板中这样写:
<template>
<input @click.stop.prevent="() => count++" />
</template>
在 JSX 中是没有 v-for 这个自定义指令的,我们需要用 map 方法来替代:
const render = () => (
<ul>
{
items.value.map(({ id, text }) => {
return (
<li key={id}>{text}</li>
)
})
}
</ul>
)
上面的写法,其实就相当于我们在模板中这样写:
<ul>
<li v-for="{ id, text } in items" :key="id">
{{ text }}
</li>
</ul>
同样,在 JSX 中也是没有 v-if 这个指令的~
但是细想一下,其实 v-if 的功能就是做判断嘛,比如我们的模板长这样:
<div>
<div v-if="ok">yes</div>
<span v-else>no</span>
</div>
那么换成用 JSX 就可以使用 三元表达式 或者 && 连接符 来实现这个功能,我们可以这样写:
// 使用三元表达式
const render = () => (
<div>
{ ok.value ? <div>yes</div> : <span>no</span> }
</div>
)
// 使用 && 连接符
const render = () => (
<div>
{ ok.value && <div>yes</div> }
{ !ok.value && <span>no</span> }
</div>
)
可以直接使用v-show 指令,也可以写成 vShow 这种形式:
const show = ref(false)
// v-show
const render = () => (
<div v-show={show}></div>
)
// 或者 vShow
const render = () => (
<div vShow={show}></div>
)
正常情况下与我们在模板中使用 v-model 无异:
const value = ref('')
const render = () => (
<input v-model={value} />
)
const value = ref('')
// 将默认的 arg 从 modelValue 修改为 childrenProp
const render = () => (
<input v-model:childrenProp={value} />
)
但如果你需要在 JSX 中使用 v-model 的内置修饰符,如.lazy、.trim、.number,那么你需要传递一个数组:
const render = () => (
<input v-model={[value, ['trim']]} />
)
如果你想同时修改默认 arg,并且使用修饰符;那么传递的数组的第二个参数需要定义为你设置的 arg,且是个字符串:
const render = () => (
<input v-model={[value, 'childrenProp', ['trim']]} />
)
上面的写法相当于模板中:
<input v-model:childrenProp.trim="value"></input>
JSX 中自定义指令的使用方法和 v-model 十分相似,只需要把 v-model 替换成你对应的自定义指令就可以啦:
const App = {
directives: { custom: customDirective },
setup() {
const value = ref()
return () => <children v-custom:childrenProp={value} />;
},
};
带修饰符的自定义指令,写法如下:
const App = {
directives: { custom: customDirective },
setup() {
const value = ref()
return () => (
<children v-custom={[value, 'childrenProp', ['a', 'b']]} />;
)
},
};
在 Vue3 的 jsx 中,使用插槽同样分为两步走:
首先,在接收插槽的组件中,给插槽留个“座位”;我们可以从 setup 函数的第二个参数解构出 slots,拿到外部传入的所有插槽:
// 自定义组件 customComp.jsx
export default {
name: 'CustomComp',
props: ['message'],
// 外部传入的插槽信息都在 slots 中
setup(props, { slots }) {
return () => (
<>
// 传入的 default 默认插槽会被展示这里,如果没有传入默认插槽,则展示文本 foo
<h1>{slots.default ? slots.default() : 'foo'}</h1>
// 传入的具名插槽 bar 会被展示在这里
<h2>{slots.bar?.()}</h2>
// 作用域插槽 footer,外部可以通过作用域拿到 text 的值
<h2>{slots.footer?.({ text: props.message })}</h2>
</>
)
}
}
在 Vue3 的 jsx 中传入插槽时,需要使用 v-slots 代替 v-slot:
// 定义好我们需要的插槽
const slots = {
// 这部分内容会传到具名插槽 bar 中
bar: () => <span>B</span>,
// 这部分内容会传到作用域插槽 footer 中
footer: ({ text }) => <span>{text}</span>
};
const render = () => (
// 使用 v-slots 将定义好的插槽 slots 传入自定义组件 CustomComp
<CustomComp v-slots={slots}>
// 这部分内容,会传入组件 CustomComp 的默认插槽中
<div>A</div>
</CustomComp>
);
或者还可以直接用一个对象同时定义好默认插槽和具名插槽:
export default {
setup() {
// 将默认插槽和具名插槽用对象的形式定义好:
const slots = {
default: () => <div>A</div>,
bar: () => <span>B</span>,
// 作用域插槽,能够拿到对应的信息 text
footer: ({ text }) => <span>{text}</span>
};
// 直接使用 v-slots 将整个插槽对象传递给自定义组件 CustomComp
return () => <CustomComp v-slots={slots} />
}
}
另外,如果 babel 的配置项 enableObjectSlots 不为 false 时,传入多个插槽还可以写成对象的形式,对象的 key 为插槽名称, value 为一个函数,函数的返回就是插槽的默认占位:
修改 babel 配置:
{
"plugins": [
[
"@vue/babel-plugin-jsx",
{
"enableObjectSlots": true
}
]
]
}
jsx 具体写法:
const render = () => (
<>
<CustomComp>
{{
// 给自定义组件 CustomComp 传入一个默认插槽,内容是 <div>A</div>
default: () => <div>A</div>,
// 给自定义组件 CustomComp 传入一个具名插槽 bar,内容是 <span>B</span>
bar: () => <span>B</span>,
// 给自定义组件 CustomComp 传入一个具名插槽 footer,内容是 <span>B</span>
footer: ({ text }) => <span>{ text }</span>,
}}
</CustomComp>
// 相当于: {{ default: () => 'foo' }},给自定义组件 CustomComp 传入了一个默认插槽
<CustomComp>{() => 'foo'}</CustomComp>
</>
)
以函数的形式传递插槽,子组件就可以懒调用这些插槽了。
在函数式组件中使用 JSX 十分简单,直接返回相关内容就行:
const App = () => <div></div>;
我们知道 Vue3 中的 setup 函数如果 返回的是个函数,那么这个函数的返回值会被作为模板内容渲染,并且会忽略 template 的内容。
因此在普通的单文件组件中,我们可以直接在 setup 函数中返回我们的 JSX 内容:
import { defineComponent } from 'vue';
const App = defineComponent({
setup() {
const count = ref(0);
return () => (
<div>{count.value}</div>
);
},
});
以上就是 Vue3 中使用 JSX 的全部内容啦!如果文章对你有帮助的话,还希望各位小伙伴不要吝啬你的赞~
另外,如果文章有纰漏 or 你有任何疑惑,都欢迎在评论区讨论。
作者:hprep
链接:https://juejin.cn/post/7272308621710213161
文:https://www.cnblogs.com/dadouF4/p/10032888.html
JavaScript:
基本概念:
JavaScript一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于浏览器客户端的脚本语言。
组成部分
ECMAScript,描述了该语言的语法和基本对象。
文档对象模型(DOM),描述处理网页内容的方法和接口。
浏览器对象模型(BOM),描述与浏览器进行交互的方法和接口。
基本特点
JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。
是一种解释性脚本语言(代码不进行预编译)。
主要用来向HTML(标准通用标记语言下的一个应用)页面添加交互行为。
可以直接嵌入HTML页面,但写成单独的js文件有利于结构和行为的分离。
跨平台特性,在绝大多数浏览器的支持下,可以在多种平台下运行(如Windows、Linux、Mac、Android、iOS等)。
Javascript脚本语言同其他语言一样,有它自身的基本数据类型,表达式和算术运算符及程序的基本程序框架。Javascript提供了四种基本的数据类型和两种特殊数据类型用来处理数据和文字。而变量提供存放信息的地方,表达式则可以完成较复杂的信息处理。
日常用途
嵌入动态文本于HTML页面。
对浏览器事件做出响应。
读写HTML元素。
在数据被提交到服务器之前验证数据。
检测访客的浏览器信息。
控制cookies,包括创建和修改等。
基于Node.js技术进行服务器端编程。
TypeScript:
基本概念:
TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。安德斯·海尔斯伯格,C#的首席架构师,已工作于TypeScript的开发。
TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以不加改变的在TypeScript下工作。TypeScript是为大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性。
TypeScript 支持为已存在的 JavaScript 库添加类型信息的头文件,扩展了它对于流行的库如 jQuery,MongoDB,Node.js 和 D3.js 的好处。
特性
类 Classes
接口 Interfaces
模块 Modules
类型注解 Type annotations
编译时类型检查 Compile time type checking
Arrow 函数 (类似 C# 的 Lambda 表达式)
JavaScript 与 TypeScript 的区别
TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。
TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
JSX:
JSX就是Javascript和XML结合的一种格式。React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。
jsx常用语法:
击上方蓝字关注“小郑搞码事”,每天都能学到知识,搞懂一个问题!
从写React项目开始,我们熟悉了一种语法,叫JSX,它很好的将逻辑和模板融合在一起。当然,有一点我们应该很清楚,在React项目开发的时候,我们可以使用JSX来编写代码,也可以使用纯JavaScript来编写代码,这样的话,即使不学JSX也可以正常开发React项目。
然而,facebook官方推荐我们使用JSX来写React代码。为什么呢?下面从四个方面介绍,带你学会使用JSX。
HTML类型的标签第一个字母用小写来表示,React组件标签第一个字母用大写来表示。
比如:
上面代码是一个HTML类型的标签定义,需要知道两点:
第一,当一个标签里面内容为空的时候,可以直接使用自闭和标签;
第二,因为JSX本身是JavaScript语法,所以一些JavaScript中的保留字要用其他的方式书写,就像上面class要写成className。
这里写的是一个React组件,注意名字第一个字母大写。
标签对标签(或组件名),{}对JS代码,无属性默认为true
什么意思了?就是说在JSX语法中,当遇到标签的时候就解释成组件或HTML标签,当遇到{}包裹的时候就当成JavaScript代码来执行。
比如:
上面代码中,{}内会当表达式处理,isLoggIn为true时输出组件<Nav/>
否则输出<LoginForm/>
当省略一个属性的值的时候,如<Nav isDisable/>
。JSX会自动把它的值认为是true(isDisable=true)。
在子组件中写注释的时候要用{}
比如:
用“三点”操作符来简化多个属性的编写。举个例子:
显然,上面这种写法,当出现更多属性的时候,会显得很难看。下面看一个更优雅的写法。
*请认真填写需求信息,我们会在24小时内与您取得联系。