jsx是JavaScript的一个类似于XML的拓展,有了它,我们可以用以下的方式来书写代码:
const box = <div>Hello</div>
在Jsx表达式中,使用大括号来嵌入动态值
const box = <div msg={data}>Hello,{userName}</div>
create-vue
和VueCli都有预置的JSX语法支持
虽然最早是由 React 引入,但实际上 JSX 语法并没有定义运行时语义,并且能被编译成成各种不同的输出形式。如果你之前使用过 JSX 语法,那么请注意 Vue 的 JSX 编译方式与 React 中 JSX 的编译方式不同,因此你不能在 Vue 应用中使用 React 的 JSX 编译。与 React JSX 语法的一些明显区别包括:
- 可以使用HTML attributes,比如
class
和for
作为props
,而不需要使用className
或htmlFor
- 传递子元素给组件的方式不同
Vue的类型定义也提供了TSX语法的类型推断支持。当时用TSX语法的时候,确保在tsconfig.json
中配置了"jsx": "preserve"
,这样的TypeScript就能保证VueJSX语法编译过程中的完整性
v-bind
绑定标签的属性值
主要用来绑定标签固有的属性 在React中需要使用className或htmlFor 但在Vue中已经进行了相关的处理,可以直接绑定标签的属性
<div class={className.value}>我是一行文字</div>
const placeholderText = "email";
const App = () => <input type="email" placeholder={placeholderText} />;
v-show
用来控制元素是否显现
<div v-show={isInputShow.value}>
<el-input v-model={textName.value} placeholder="请输入..." />
</div>
v-model
为DOM元素绑定数据
- 可以只用一个参数
- 如果需要使用两个参数,那么第二个参数就要是字符串!
<div v-show={isInputShow.value}>
<el-input v-model={textName.value} placeholder="请输入..." />
</div>
v-for
重复一定次数渲染
有时候我们需要重复将一个DOM渲染一定的次数,比如5次,我们在template
中会这么写
<template>
<div v-for="item in 5">
{{item + 1}}、Hello,World
</div>
</template>
那么如何在JSX中实现这样的效果呢
如下所示:
<div>
{Array.from(new Array(5).keys()).map((item) => {
return <div>{item + 1}</div>;
})}
</div>
个人感觉这个其实是比在
template
中实现起来复杂的
渲染一个数组
很多时候我们需要将从后端获取到的一个数组类型的数据 渲染到我们的前端页面上 就需要使用到我们的循环渲染
比如下面这一组数据
const userList = ref<UserInfo[]>([
{
id: 1,
name: "yxr",
},
{
id: 2,
name: "zyl",
},
{
id: 3,
name: "yyl",
},
]);
我们就可以使用JS数组中的map方法
<div class="list">
{userList.value.map((item: UserInfo) => {
return (
<div class="list-item">
<div>id: {item.id}</div>
<div>name: {item.name}</div>
</div>
);
})}
</div>
v-on
以on
开头,并跟着大写字母的props
会被当作事件监听器。比如,onClick
与模板中的@click
等价
<el-button onClick={handleAddData}>添加</el-button>
props
可以理解为某个组件的属性,父组件在使用某个子组件的时候,可以根据需要设置子组件的这些属性,从而显示不同的内容
Props:主要用于父组件向子组件传递数据
// 使用 <script setup>
defineProps({
title: String,
likes: Number
})
// 非 <script setup>
export default {
props: {
title: String,
likes: Number
}
}
而在tsx中,我们需要按照如下方式定义props
export default defineComponent({
name: "MyList",
props: {
listData: {
type: Array as PropType<Log[]>,
required: true,
},
},
});
PropType<>主要用来结合TypeScript,可以指定我们自定义的更详细的数据类型
prop校验
如果需要对prop进行相关的校验,可以向defineProps()
宏提供一个带有Prop
校验的对象,而不是一个字符串数组,例如:
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
当不满足校验规则的时候,Vue会在控制台发出一个警告
自定义指令
自定义指令和事件往往是一起使用的,可以用于子组件向父组件传递消息等等操作
在子组件中通过
setup()
函数中的context.emit
来触发某个自定义事件
const handleCreate = () => {
ctx.emit('create', JSON.stringify(props.formData));
};
其中,第一个参数是事件的名称,第二个参数是传递给父组件的参数
在父组件中,我们使用onCreate
来定义我们处理子组件create
事件的方法
<script lang="tsx">
import { defineComponent, ref } from "vue";
import MyForm from "@/components/MyForm.vue";
import { FormDataTest } from "@/types";
export default defineComponent({
name: "Register",
setup() {
const data = ref<FormDataTest>({
age: 18,
name: 'zyl',
phone: '123',
});
const handleListenCreate = (data: string) => {
console.log('子组件点击了提交,数据为');
console.log(JSON.parse(data));
};
return () => (
<div>
<h1>用户注册</h1>
<div>
<MyForm
formData={data.value}
infoText="注册"
onCreate={handleListenCreate}
/>
</div>
</div>
);
},
});
</script>
子组件传递过来的数据会自动填充到函数的参数中,便于从父组件中获取数据
const handleListenCreate = (data: string) => {
console.log('子组件点击了提交,数据为');
console.log(JSON.parse(data));
};
此外,事件
其实是一种特殊的Prop,因此我们最好在子组件上定义好这种Prop规则,上述例子对应的Prop如下:
props: {
formData: {
type: Object as PropType<FormDataTest>,
},
infoText: {
type: String,
required: true,
},
onCreate: {
type: Function as PropType<(msg: string) => void>,
},
},
这样,我们就可以在子组件中进行某些操作,然后将这些操作得到的数据作为参数传递给父组件中对应处理的函数,并由父组件再进行相关必要的操作,也就是实现了子组件主动和父组件之间的通信
插槽
有时候我们会遇到这样的写法:
<MyForm>
<MyInput />
</MyForm>
那么,MyInput
组件实际会被渲染到哪里呢?Vue其实是不知道的,因此需要我们去指定
比如上述MyInput
组件,就会渲染到MyForm
组件默认插槽所在的位置上
如果有多个插槽,那么就需要使用到具名插槽,给插槽指定一个名称,从而渲染到对应名称的插槽中
此处不再赘述,只是讲解在jsx/tsx中,我们如何使用插槽
setup(props: any, ctx: SetupContext<EmitsOptions>) {
const handleCreate = () => {
ctx.emit('create', JSON.stringify(props.formData));
};
watch(props.formData, (newVal, oldVal) => {
ElMessage.success({
message: '来自父组件的数据被修改了',
});
});
return () => (
<div>
<div>
<div>{ctx.slots.default ? ctx.slots.default() : null}</div>
<div>{ctx.slots.title ? ctx.slots.title() : null}</div>
</div>
<el-form model={props.formData} label-width='120px'>
<el-form-item label='姓名'>
<el-input modelValue={props.formData?.name} />
</el-form-item>
<el-form-item label='年龄'>
<el-input modelValue={props.formData?.age} type='number' />
</el-form-item>
<el-form-item label='电话'>
<el-input modelValue={props.formData?.phone} type='number' />
</el-form-item>
<el-form-item>
<el-button type='primary' onClick={handleCreate}>
{props.infoText}
</el-button>
<el-button>清空</el-button>
</el-form-item>
</el-form>
</div>
);
},
由于使用了TypeScript,因此需要使用三目运算符来检测一下父组件是否有传递插槽
父组件如下:
<MyForm
formData={data.value}
infoText='注册'
onCreate={handleListenCreate}>
{{
default: () => <div>default</div>,
title: () => <div>title</div>,
}}
</MyForm>