使用jsx的前置条件
如果使用vite创建的项目
- 安装依赖:
npm install @vitejs/plugin-vue-jsx --save-dev
- vite.config.ts配置插件
// vite.config.js
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [
vueJsx({
// options are passed on to @vue/babel-plugin-jsx
}),
],
})
jsx语法
v-for
不能使用v-for指令,在jsx中map来代替
// 组件JsxComp.jsx
import { defineComponent } from "vue"
import styles from './jsxcomp.module.less'
export default defineComponent({
setup(props, { emit, slots }) {
const buttons = [{
name: '收入',
id: 1
}, {
name: '支出',
id: 2
}, {
name: '结余',
id: 3
}]
return () => (
<div style="border: 1px solid #999;">
<h2>按钮区</h2>
{buttons.map((btn, index) => (
<button key={btn.id} class={[styles['default-btn'], btn.id === activeBtn.value && styles['active-btn']]} onClick={() => clickBtn(btn.id)}>{btn.name}</button>
))}
</div>
)
}
})
v-slots
为节省空间,组件JsxComp.jsx中的其余代码不展示,只展示在setup中返回的与标题相关的部分模板
// 子组件JsxComp.jsx
<div>
<h2>插槽区域:</h2>
<div class={styles['default-slot-area']}>
默认插槽区:
<div>{ slots?.default?.() }</div>
</div>
<div style="margin-top: 10px; border-radius: 5px; padding: 5px; border: 1px solid #999;">
具名插槽区main:
<!-- 给slot传参,在模板语法中为作用域插槽 >
<div>{ slots?.main?.({age: 1}) }</div>
</div>
</div>
父组件为App.jsx
// 父组件App.jsx
// 方式一:
const slots = {
main: ({age}) => (
<>
<div style="color: #1E90FF;">节点1,从子组件传过来的值age: {age}</div>
<div style="color: #87CEEB;">节点2</div>
</>
)
}
<JsxComp v-slots={slots}>
<div>
<h1>默认插槽内容</h1>
<div style="color: #fdc;">内容区</div>
</div>
</JsxComp>
// 方式二:
const slots = {
default: () => (
<div>
<h1>默认插槽内容</h1>
<div style="color: #fdc;">内容区</div>
</div>
),
main: () => (
<>
<div style="color: #1E90FF;">节点1</div>
<div style="color: #87CEEB;">节点2</div>
</>
)
}
<JsxComp>
{ slots }
</JsxComp>
// 方式三:
const slots = {
default: () => (
<div>
<h1>默认插槽内容</h1>
<div style="color: #fdc;">内容区</div>
</div>
),
main: () => (
<>
<div style="color: #1E90FF;">节点1</div>
<div style="color: #87CEEB;">节点2</div>
</>
)
}
<JsxComp v-slots={slots}>
</JsxComp>
父组件为App.vue
<JsxComp>
<template #default>
<div>默认slot</div>
</template>
<template #main="{ age }">
<div>主要内容1,从子组件传过来的数据:{{ age }}</div>
<div>主要内容2</div>
</template>
</JsxComp>
v-if
const isVisible = ref(false)
const changeVisible = () => {
isVisible.value = !isVisible.value
}
// setup返回的渲染模板
<div>
<h2>v-if演示</h2>
<button onClick={changeVisible}>changeVisible</button>
<div style="width: 100%; height: 200px; background: #D4F2E7;line-height: 200px;text-align: center;">
{
isVisible.value && <span>能显示隐藏的区域1</span>
}
{
isVisible.value ? <span>能显示隐藏的区域2</span> : ''
}
</div>
</div>
v-model
实现父子组件的双向绑定
// 子组件JsxComp.jsx
import { defineComponent} from "vue"
export default defineComponent({
props: ['modelValue', 'title'],
emits: ['update:modelValue', 'update:title'],
setup(props, { emit, slots }) {
return () => (
<>
<h1>这是jsx组件</h1>
<div>
<h2>测试v-model</h2>
<div>
<span>不添加参数的model -- props.modelValue: { props.modelValue } </span>
<input value={props.modelValue} onInput={e => emit('update:modelValue', e.target.value)}/>
</div>
<div>
<span>添加title参数的model -- props.title: { props.title }</span>
<input value={props.title} onInput={e => emit('update:title', e.target.value)}/>
</div>
</div>
</>
)
}
})
// 父组件App.jsx
import { defineComponent, ref } from "vue";
import JsxComp from '../src/components/JsxComp'
export default defineComponent({
setup() {
const count = ref(0)
const title = ref('placehold title')
return () => (
<>
<div>双向绑定的count:{ count.value }</div>
<div>双向绑定的title:{ title.value }</div>
<JsxComp
v-model={count.value}
v-model:title={title.value}
>
</JsxComp>
</>
)
}
})
<!-- 父组件App.vue >
<div>
<span>双向绑定count的值:{{ count }}</span>
<br/>
<span>双向绑定title的值:{{ title }}</span>
</div>
<JsxComp
v-model="count"
v-model:title="title"
>
</JsxComp>
// 父组件App.vue
<script setup lang="ts">
import { ref } from 'vue'
import JsxComp from './components/JsxComp.jsx'
const count = ref(0)
const title = ref('title')
组件内部的双向绑定
let count = ref(0)
const addNum = () => {
count.value++
}
<input type="number" v-model={count.value} />
<span>{count.value}</span>
<button onClick={addNum}>+</button>
绑定样式
// 直接定义style
<div style="border: 1px solid #999;"></div>
<div style={{color: 'red'}}>红颜色的字</div>
// 绑定定义的变量的样式
const innerStyle = {
buttonStyle: {
padding: '5px',
border: '1px solid #999',
'border-radius': '5px',
'background-color': '#0000ff3d'
}
}
<button style={innerStyle.buttonStyle}>blueColor</button>
// 带有变量的样式
const color = ref('#ADD8E6')
const colors = ['#ADD8E6', '#F0FFFF', '#2F4F4F', '#7FFFAA']
let preIndex = 0
const changeColor = () => {
const randomIndex = Math.floor(Math.random() * colors.length)
if (preIndex === randomIndex) {
changeColor()
return;
}
color.value = colors[randomIndex]
preIndex = randomIndex
}
<button onClick={changeColor}>修改以下背景色</button>
<div style={{'background': color.value, transition: 'all 1s ease'}}></div>
绑定class
如果绑定的class只在本组件中使用,需要在文件名后添加.module,例如,有个文件名为test.css,如果只希望定义的class在组件内部使用,则文件名需为:
test.module.css。编译后,模块内的class会在原有class基础上添加一些特殊字符,例如,在文件中定义了一个class名为blue_bg,经过编译后编译成了_blue-bg_10cyo_6
// jsxcomp.module.less
.default-slot-area {
padding: 5px;
border: 1px solid #999;
border-radius: 5px;
}
.blue-bg {
background-color: rgb(111, 111, 236);
}
.border {
border: 1px solid #d30677;
}
@activeColor: #d30677;
.default-btn {
margin-right: 5px;
cursor: pointer;
border: 1px solid #999;
background-color: #4169E1;
&:hover {
background-color: @activeColor;
}
}
.active-btn {
background-color: @activeColor;
}
import styles from './jsxcomp.module.less'
// 绑定单个class
<div class={styles['default-slot-area']}></div>
// 绑定多个class
<div class={[styles['blue-bg'], styles.border]}>有多个class的div</div>
// class切换
<button class={[styles['default-btn'], btn.id === activeBtn.value ? styles['active-btn']: '']} }>btn</button>
<button class={[styles['default-btn'], btn.id === activeBtn.value && styles['active-btn']]} >name</button>
绑定事件
jsx组件内部绑定事件
// 不带参数
<button onClick={changeVisible}>changeVisible</button>
// 只带参数
<button onClick={() => clickBtn(btn.id)}>{btn.name}</button>
// 同时带事件e和参数
<button onClick={e => emitEvent(e, 'msg from child')}>向父组件抛事件</button>
子组件抛事件,父组件接收事件
// JsxComp.jsx
export default defineComponent({
emits: ['custom-event'],
setup(props, { emit, slots }) {
// 使用defineEmits会报运行时警告,提示不能使用
// const emits = defineEmits(['emit-event'])
const emitEvent = (e, params) => {
emit('custom-event', {
e,
params
})
}
return () => <button onClick={e => emitEvent(e, 'msg from child')}>向父组件抛事件</button>
}
})
// 父组件App.jsx
<JsxComp
onCustomEvent={(params) => console.log(params)}
>
</JsxComp>
<!-- 父组件App.vue >
<JsxComp @custom-event="getMsg"/>
完整代码
// JsxComp.jsx
import { defineComponent, ref } from "vue"
import styles from './jsxcomp.module.less'
export default defineComponent({
props: ['msg', 'modelValue', 'title', 'name', 'age'],
emits: ['custom-event', 'update:modelValue', 'update:title'],
setup(props, { emit, slots }) {
const innerStyle = {
buttonStyle: {
padding: '5px',
border: '1px solid #999',
'border-radius': '5px',
'background-color': '#0000ff3d'
}
}
const buttons = [{
name: '收入',
id: 1
}, {
name: '支出',
id: 2
}, {
name: '结余',
id: 3
}]
const activeBtn = ref(buttons[0].id)
const clickBtn = id => {
activeBtn.value = id;
}
let count = ref(0)
const addNum = () => {
count.value++
}
// 使用defineEmits会报运行时警告,提示不能使用
// const emits = defineEmits(['emit-event'])
const emitEvent = (e, params) => {
emit('custom-event', {
e,
params
})
}
const color = ref('#ADD8E6')
const colors = ['#ADD8E6', '#F0FFFF', '#2F4F4F', '#7FFFAA']
let preIndex = 0
const changeColor = () => {
const randomIndex = Math.floor(Math.random() * colors.length)
if (preIndex === randomIndex) {
changeColor()
return;
}
color.value = colors[randomIndex]
preIndex = randomIndex
}
const isVisible = ref(false)
const changeVisible = () => {
isVisible.value = !isVisible.value
}
return () => (
<>
<h1>这是jsx组件</h1>
<div>
<span>通过obj传入的name: { props.name }</span>
<br/>
<span>通过obj传入的age: { props.age }</span>
</div>
<hr style="border-color: red;"/>
<div>输入值props.msg: { props.msg } </div>
<input type="number" v-model={count.value} />
<span>{count.value}</span>
<button onClick={addNum}>+</button>
<button onClick={e => emitEvent(e, 'msg from child')}>向父组件抛事件</button>
<div>
<h2>插槽区域:</h2>
<div class={styles['default-slot-area']}>
默认插槽区:
<div>{ slots?.default?.() }</div>
</div>
<div style="margin-top: 10px; border-radius: 5px; padding: 5px; border: 1px solid #999;">
具名插槽区main:
<div>{ slots?.main?.({age: 1}) }</div>
</div>
</div>
<div class={[styles['blue-bg'], styles.border]}>有多个class的div</div>
<button style={innerStyle.buttonStyle}>blueColor</button>
<div style={{color: 'red'}}>红颜色的字</div>
<div style="border: 1px solid #999;">
<h2>按钮区</h2>
{buttons.map((btn, index) => (
// <button key={btn.id} class={[styles['default-btn'], btn.id === activeBtn.value ? styles['active-btn']: '']} onClick={() => clickBtn(btn.id)}>{btn.name}</button>
<button key={btn.id} class={[styles['default-btn'], btn.id === activeBtn.value && styles['active-btn']]} onClick={() => clickBtn(btn.id)}>{btn.name}</button>
))}
</div>
<button onClick={changeColor}>修改以下背景色</button>
<div style={{'background': color.value, transition: 'all 1s ease'}}>
<h2>测试v-model</h2>
<div>
<span>不添加参数的model -- props.modelValue: { props.modelValue } </span>
<input value={props.modelValue} onInput={e => emit('update:modelValue', e.target.value)}/>
</div>
<div>
<span>添加title参数的model -- props.title: { props.title }</span>
<input value={props.title} onInput={e => emit('update:title', e.target.value)}/>
</div>
</div>
<div>
<h2>v-if演示</h2>
<button onClick={changeVisible}>changeVisible</button>
<div style="width: 100%; height: 200px; background: #D4F2E7;line-height: 200px;text-align: center;">
{
isVisible.value && <span>能显示隐藏的区域1</span>
}
{
isVisible.value ? <span>能显示隐藏的区域2</span> : ''
}
</div>
</div>
</>
)
}
})
// App.vue
<template>
<div class="container">
<div>从子组件获取到的msg: {{ msgFromChild }}</div>
<div>
<span>双向绑定count的值:{{ count }}</span>
<br/>
<span>双向绑定title的值:{{ title }}</span>
</div>
<JsxComp
v-model="count"
v-model:title="title"
msg="first one"
@custom-event="getMsg"
>
<template #default>
<div>默认slot</div>
</template>
<template #main="{ age }">
<div>主要内容1,从子组件传过来的数据:{{ age }}</div>
<div>主要内容2</div>
</template>
</JsxComp>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import JsxComp from './components/JsxComp.jsx'
import CompC from './components/CompC.vue';
const count = ref(0)
const title = ref('title')
const changeCount = () => {
count.value--
}
const msgFromChild = ref('')
const getMsg = obj => {
console.log('obj:', obj)
msgFromChild.value = obj.params
}
</script>
<style scoped lang='less'>
.container {
width: 100%;
height: 200px;
background-color: v-bind(color);
}
</style>
// App.jsx
import { defineComponent, ref } from "vue";
import JsxComp from '../src/components/JsxComp'
export default defineComponent({
setup() {
const slots = {
default: () => (
<div>
<h1>默认插槽内容</h1>
<div style="color: #fdc;">内容区</div>
</div>
),
main: ({age}) => (
<>
<div style="color: #1E90FF;">节点1,从子组件传过来的值age: {age}</div>
<div style="color: #87CEEB;">节点2</div>
</>
)
}
const count = ref(0)
const title = ref('placehold title')
const person = {
name: 'jsx',
age: 24
}
// {...person}类似于模板语法的v-bind="person"
return () => (
<>
<div>双向绑定的count:{ count.value }</div>
<div>双向绑定的title:{ title.value }</div>
<JsxComp
v-slots={slots}
v-model={count.value}
v-model:title={title.value}
msg="msg from parent"
onCustomEvent={(params) => console.log(params)}
{...person}
>
</JsxComp>
</>
)
}
})