# vue3项目中jsx的应用
# 在vue3中使用jsx的两种方式
# 1. 基于单文件SFC
<template>
<Rander/>
</template>
<script setup lang="tsx">
import {ref, computed} from 'vue'
const count = ref(0)
const Rander = () => {
return (
<div>
<p>{count.value}</p>
<button onClick={() => count.value++}>点我+1</button>
</div>
)
}
</script>
# 2. 基于JSX文件
- 新建jsx文件
- 借助
defineComponent来定义组件
import {defineComponent, ref} from 'vue'
//方法一 组合式API写法 (推荐)
export default defineComponent({
setup() {
const count = ref(0)
//jsx UI结构
return () => (
<div>
<div>{count.value}</div>
<button
onClick={() => {
count.value++
}}>
点我+1
</button>
</div>
)
}
})
//方法二
import {defineComponent, ref} from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
return {
count
}
},
//jsx UI结构
render() {
return (
<div>
<div>{this.count}</div>
<button
onClick={() => {
this.count++
}}>
点我+1
</button>
</div>
)
}
})
注意:
vue内置的defineComponent函数作用为:在定义 Vue 组件时提供类型推导的辅助函数。
该方法还有一种备用签名 旨在与组合式 API 和 渲染函数或 JSX 一起使用。
# 插件安装
vue3中的JSX是使用插件@vitejs/plugin-vue-jsx 来支持JSX写法的。
pnpm i @vitejs/plugin-vue-jsx -D
# 插件配置
安装完之后在vite.config.ts进行插件使用,代码如下:
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [
vue(),
vueJsx()]
})
jsconfig.json配置
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"include": [
"src/**/*.tsx"
]
}
# defineComponent 和 setup
SFC方式结构固定:template、script、style
<template>
<div>hello word</div>
</template>
<script lang="ts" setup></script>
<style scoped></style>
TSX方式就完全是一个ts文件的写法,没有模板template和样式style
import {defineComponent} from 'vue'
export default defineComponent({
setup() {
return () => <div>hello word</div>
}
})
# setup中函数的返回值有多种方式,可以直接返回html,这个适合结构简单的页面,如果返回比较多,可以使用如下方式:
import {defineComponent} from 'vue'
export default defineComponent({
setup() {
return () => (
<div>
<div>child1</div>
<div>child2</div>
<div>child3</div>
</div>
)
}
})
# 如果是多节点,可以使用空符号包裹
return () => (
<>
<div>child1</div>
<div>child2</div>
<div>child3</div>
</>
)
# 在以上的方式中我们把除了布局以外的逻辑都写在//Todo部分,但是有时候我们需要做一些按条件渲染的逻辑,那么也可以在return里加处理逻辑,例如:
import {defineComponent} from 'vue'
export default defineComponent({
setup() {
return () => {
if (someThing) {
return (
<div>
<div>child1</div>
<div>child2</div>
<div>child3</div>
</div>
)
} else return (
<div>noThing</div>
)
}
}
})
注意
这种方式类似v-if,但是和v-if还是有点区别,v-if可以作用在更小的范围,而tsx这种方式只适合整个组件的条件渲染,
//sfc
<template>
<div v-if=“soneThing”>A</div>
<div v-else>B</div>
</template>
return () => (
<div>
{
soneThing ? <div>A</div> : <div>B</div>
}
</div>
)
# v-bind
绑定变量,也就是简写的:冒号,修改方式就是将冒号去掉,把双引号改为大括号
//SFC
<template>
<div :class="myClass" :style="myStyle" :type="myType"></div>
</template>
//TSX
return () => (
<div class={myClass} style={myStyle} type={myType}></div>
)
# v-for
采用map循环的方式,返回一个数组
//SFC
<template>
<div v-for="(item ,index) in list" :key="index">{{item}}</div>
</template>
//TSX
return () => (
<>
{
list.map((item, index) => <div>{item}</div>)
}
</>
)
# 自定义指令
自定义指令和普通指令v-model一样
//SFC
<template>
<div v-someThing="someThing">自定义指令</div>
</template>
//TSX
return () => (
<div v-someThing={someThing}></div>
)
# 插槽
插槽有两种实现方式,一种是用v-slots绑定对象,一种是直接在元素中使用对象。
//SFC 子级
<template>
<div>
<slot>默认插槽</slot>
<slot name="header">具名插槽</slot>
<slot name="main" :item="item">作用域插槽</slot>
</div>
</template>
//SFC 父级
<template>
<child>
<div>默认插槽</div>
<template #hearder>
<div>具名插槽</div>
</template>
<template #main="{item}">
<div>{{item}}作用域插槽</div>
</template>
</child>
</template>
//TSX 子级
import {defineComponent} from 'vue'
export default defineComponent({
setup(props, {slots}) {
return () => (
<div>
<div>默认插槽:{slots.default && slots.default()}</div>
<div>具名插槽:{slots.hearder && slots.hearder()}</div>
<div>作用域插槽:{slots.main && slots.main({name: '作用域插槽传值'})}</div>
</div>
)
}
})
// TSX 父级
//第一种方式
return () => (
<child v-slots={{
default: () => <div>默认插槽</div>,
header: () => <div>具名插槽</div>,
main: (props: Record<'name', string>) => <div>{props.name}作用域插槽</div>
}}>
</child>
)
# props
父组件向子组件传值
//SFC
<script lang="ts" setup>
import {defineProps} from "vue"
const props = defineProps<{ name: string, age: number }>({
name: '',
age: 18
})
</script>
//TSX
import {defineComponent} from 'vue'
export default defineComponent({
props: {
name: {
type: String,
default: ''
},
age: {
type: Number,
default: 18
},
},
setup(props) {
return () => (
<div>{props.name}</div>
)
}
})
注意
需要注意的是,prop传递过来的值如果没有默认值,需要判断是否为空,可以使用计算属性或者条件渲染处理。
# emit
子组件向父组件传值
<script lang="ts" setup>
import {defineEmits} from 'vue'
const emits = defineEmits<{ (value: nameChange, name: String): void }>()
emits('nameChange', '张三')
</script>
//TSX
import {defineComponent} from 'vue'
export default defineComponent({
emits: ['changeName'],
setup(props, {emit}) {
emit('changeValue', '张三')
return () => (
<div>{props.name}</div>
)
}
})
# 事件监听
事件监听就是v-on或者@,在TSX中事件以on开头,即使我们的自定义事件没有on,也要在监听的时候加上
一般都采用的是小驼峰的方式
//SFC
<template>
<div @click="handleClick">无参数</div>
<div @click="(event)=>handleClick1(event)">有参数</div>
<div @click="handleClick2('张三')">自定义参数</div>
</template>
//TSX
return () => (
<>
<div onClick={handleClick}>无参数</div>
<div OnClick="(event)=>handleClick1(event)">有参数</div>
<div OnClick="handleClick2('张三')">自定义参数</div>
</>
)
//函数定义相同
const handleClick = () => {
console.log('点击')
}
const handleClick1 = (event) => {
console.log('有参数', event)
}
const handleClick2 = (name: string) => {
console.log('自定义参数', name)
}
注意
自定义事件只需要在事件名前面加上on即可,参数传递与上面一致
在TSX中处理事件不能使用事件修饰符,因此需要在事件函数中自行处理,例如冒泡、阻止默认行为等。
# attrs 透传
所有没有在props和emit中声明的属性都会被attrs保存
//SFC 父级
<template>
<child name="张三" :age="18"/>
</template>
//SFC 子级
<template>
<div v-bind="$attrs">透传</div>
</template>
//TSX 子级 父级同上
import {defineComponent} from 'vue'
export default defineComponent({
setup(props, {attrs}) {
return () => (
<div {...attrs}>{props.name}</div>
)
}
})
# 组件引用
通过ref获取组件dom信息
//SFC
<template>
<child ref="dom"/>
</template>
<script lang="ts" setup>
import {ref, onMounted} from 'vue'
const dom = ref(null)
onMounted(() => {
console.log('组件实例', dom.value)
})
</script>
const dom = ref(null)
return () => (
<child ref={dom}/>
)
# 对外暴露属性和方法
在父组件中直接调用子组件的属性和方法
//SFC
defineExpose<{ handleClick: () => void }>({handleClick})
setup(props, {expose})
{
expose({handleClick})
}
# tsx中的css问题
在vue文件中可以在<style/>标签中写入scoped防止造成样式污染
在tsx中我们通常都是使用css文件外部引入的方式定制组件样式 而tsx中并没有<style/>标签让我们写入scoped
在vite项目中vue为我们提供了css module的方式引入css,引入后会生成一个唯一的名称
vite.config.ts配置
css: {
modules:{
localsConvention:'camelCase'
}
}
这里规定css类名的命名规则为小驼峰,即child-item类在js中会变成childItem变量。但是要实现css
module的功能,对css文件命名由要求,必须在后缀名前面是module,例如xxx.module.css、xxx.module.less、xxx.module.scss。
示例如下:
//child.module.css
.child-item {
color: red;
}
在tsx文件中引用
import('./child.module.css')
return () => <div class={childItem}></div>