vue 通用模板
vue 组件库
vue 官方推荐第三方组件库,里面列举了各种组件库;
.vue 文件
<!-- vue 3 组合式 api -->
<template>
<div>
<h1>年龄:{{ age }}</h1>
<button @click="addAge">长大</button>
<button v-on:click="addAge">长大(非简写)</button>
</div>
</template>
<!-- 注意支持 name 属性定义组件名需安装 vite-plugin-vue-setup-extend -->
<script setup lang="ts" name="Test">
import { ref } from "vue";
let age = ref(18);
function addAge() {
age.value++;
}
</script>
<style lang="scss" scoped></style><!-- vue 2 选项式 api -->
<template>
<div>
<h1>年龄:{{ age }}</h1>
<button @click="addAge">长大</button>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
age: 18,
};
},
mounted() {},
methods: {
addAge() {
this.age++;
},
},
};
</script>
<style lang="scss" scoped></style>知识点
v-show 和 v-if 的区别
v-if 每次切换会销毁/重建组件或元素,v-show 每次切换只会修改元素的 display 属性 v-if 会触发组件生命周期钩子函数,v-show 不会触发;因此,频繁切换显示/隐藏的元素使用 v-show;
计算属性
计算属性更多的是用于读取数据,而不是修改数据;
<template>
<div>
<!-- 首字母大写的全名,虽然被多次使用,但只会计算一次 -->
<h1>{{ fullName }}</h1>
</div>
</template>
<script setup lang="ts" name="Test">
import { ref, computed } from "vue";
let firstName = ref("zhang");
let lastName = ref("san");
// 只读
let fullName = computed(() => {
return (
firstName.value.slice(0, 1).toUpperCase() +
firstName.value.slice(1) +
lastName.value
);
});
// 可读写--示例
let fullName = computed({
get() {
return (
firstName.value.slice(0, 1).toUpperCase() +
firstName.value.slice(1) +
lastName.value
);
},
set(val) {
// fullName 被修改时调用;val 为被修改的值;
},
});
</script><template>
<div>
<!-- 首字母大写的全名,虽然被多次使用,但只会计算一次 -->
<h1>{{ fullName }}</h1>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
firstName: "zhang",
lastName: "san",
};
},
computed: {
// 只读
fullName() {
return (
this.firstName.slice(0, 1).toUpperCase() +
this.firstName.slice(1) +
this.lastName
);
},
// 可读写--示例
fullName: {
get() {
return (
this.firstName.slice(0, 1).toUpperCase() +
this.firstName.slice(1) +
this.lastName
);
},
set(val) {
// fullName 被修改时调用;val 为被修改的值;
},
},
},
};
</script>数据监视
每当数据发生变化时,想额外的处理一些逻辑,可使用数据监视功能;
<!--
vue3 只能监视以下 4 种类型的数据:
1. ref 定义的数据
2. reactive 定义的数据
3. 函数返回一个值
4. 一个包含上述内容的数组
-->
<template>
<div>
<h1>年龄:{{ age }}</h1>
<button @click="addAge">长大</button>
</div>
</template>
<!-- 注意支持 name 属性定义组件名需安装 vite-plugin-vue-setup-extend -->
<script setup lang="ts" name="Test">
import { ref, reactive, watch, watchEffect} from "vue";
// 1. 监视 ref 定义的【基本】数据类型
let age = ref(18);
function addAge() {
age.value++;
}
const myStopWatch = watch(age, (newVal, oldVal) => {
console.log("age 变化了!");
if (newVal > 20) {
myStopWatch(); // 停止监听
}
});
// 2. 监视 ref 定义的【对象】数据类型
// 默认监视的对象地址,若想监视对象属性的变化,需要手动开启深度监视
// 注意,watch 被调用后,newVal 和 oldVal 的值是一样的,原因是因为他们引用的同一个对象
let person = ref({ name: "zs", age: 18 });
watch(person, (newVal, oldVal) => {}, { deep: true,immediate: true }); //immediate 初始化完成后立即执行一次
// 3. 监视 reactive 定义的【对象】数据类型,默认开启深度监视
let person1 = reactive({ name: "zs", age: 18, car: { c1: "宝马" }});
watch(person1, (newVal, oldVal) => {
console.log("变化了!");
});
// 4. 监视 reactive 定义的【对象属性】,即只监听对象中某个属性变化;
watch(
() => person1.car,
(newVal, oldVal) => {
console.log("car 变化了!");
},
{ deep: true }
);
// 5. 监视 reactive 定义的【多个对象属性】
let person2 = reactive({ name: "zs", age: 18, car: { c1: "宝马" } });
watch(
[() => person1.name, () => person1.car]
(newVal, oldVal) => {
console.log("car 变化了!");
},
{ deep: true }
);
// 6. 监听任意属性(无需像 watch 那样指定要监听的属性)
let person3 = reactive({ name: "zs", age: 18});
watchEffect(()=>{
if(person3.name=="ls" || person3.age>=20){
console.log("变化了");
}
})
</script><!-- vue 2 -->
<script>
export default {
name: "Test",
data() {
return {
age: 18,
name: "zs",
info: {
phone: 15555555555,
},
};
},
methods: {},
watch: {
// 监听简单数据
age: {
immediate: true, // 立即执行一次
// handler 当 age 发生改变时调用
handler(newVal, oldVal) {
console.log("变化了");
},
},
// 监听对象
info: {
deep: true, // 多层对象需开启深度监视才能生效
handler(newVal, oldVal) {
console.log("变化了");
},
},
// 简写形式(不需要立即执行或深度监听时,适用)
name(newVal, oldVal) {
console.log("变化了");
},
},
};
</script>添加属性
以下示例演示了如何给响应式对象添加属性,以及如何给响应式数组添加元素;
Vue3 使用 Proxy 实现响应式,写法比 vue2 简洁许多。而 vue2 使用 defineProperty 实现响应式,需使用 this.$set() 才能实现响应式。
<template>
<div>
<h2>对象操作</h2>
<p>{{ user }}</p>
<button @click="addProperty">添加年龄</button>
<h2>数组操作</h2>
<p>{{ items }}</p>
<button @click="addItem">添加水果</button>
<button @click="updateItem">修改第一个</button>
</div>
</template>
<script setup>
import { reactive } from "vue";
const user = reactive({ name: "张三" });
const items = reactive(["苹果", "香蕉", "橙子"]);
// 对象操作
const addProperty = () => {
user.age = 25; // 直接添加新属性即可
};
// 数组操作
const addItem = () => {
items.push("梨子"); // 直接使用数组方法
};
const updateItem = () => {
items[0] = "红苹果"; // 直接通过索引修改
};
</script><template>
<div>
<h2>对象操作</h2>
<p>{{ user }}</p>
<button @click="addProperty">添加年龄</button>
<h2>数组操作</h2>
<p>{{ items }}</p>
<button @click="addItem">添加水果</button>
<button @click="updateItem">修改第一个</button>
</div>
</template>
<script>
export default {
data() {
return {
user: { name: "张三" },
items: ["苹果", "香蕉", "橙子"],
};
},
methods: {
// 对象操作
addProperty() {
this.$set(this.user, "age", 25); // 正确添加响应式属性
// this.user.age = 25; // 错误写法,视图不会更新
},
// 数组操作
addItem() {
// push, pop, shift, unshift, splice, sort, reverse 是 vue2 内部数组变异方法,会触发视图更新;
this.items.push("梨子"); // 使用数组变异方法
// 或者使用 Vue.set
// this.$set(this.items, this.items.length, '梨子');
},
updateItem() {
this.$set(this.items, 0, "红苹果"); // 正确修改数组元素
// this.items[0] = '红苹果'; // 错误写法,不会触发视图更新
},
},
};
</script>绑定样式
<template>
<div class="basic" :class="bg" :style="styleObj">测试绑定样式</div>
</template>
<script setup lang="ts" name="Test">
import { ref } from "vue";
let bg = ref("happy");
let styleObj = ref({
fontSize: "16px",
});
</script>
<style lang="scss" scoped>
.basic {
width: 100px;
height: 100px;
}
.happy {
background-color: red;
}
</style><template>
<div class="basic" :class="bg" :style="styleObj">测试绑定样式</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
bg: "happy",
styleObj: {
fontSize: "16px",
},
};
},
};
</script>
<style lang="scss" scoped>
.basic {
width: 100px;
height: 100px;
}
.happy {
background-color: red;
}
</style>组件通信
父传子
props 方式
适用于父组件直接给子组件传递数据(无需获取子组件数据或调用子组件方法)。
vue3<!-- 父组件 --> <template> <Child :info="info" :hobby="hobby" /> </template> <script setup lang="ts" name="Parent"> import Child from "./components/xxx/Child.vue"; import { ref } from "vue"; // 父组件数据 let info = ref({ name: "parent", age: 48 }); let hobby = ref(["fishing", "somking"]); </script> <!-- 子组件 --> <template> <div>父的 info:{{ info }} -- 父:{{ hobby }}</div> </template> <script setup lang="ts" name="Child"> import { defineProps, withDefaults } from "vue"; // defineProps 可以不用引入,这里只做演示 // 简单接收 defineProps(["info", "hobby"]); // 接收,检验 + 非必要 + 默认值 withDefaults(defineProps<{ hobby?: Array }>(["hobby"]), ["fishing"]); </script>vue2<!-- vue 2 --> <!-- 父组件 Parent.vue --> <template> <div> <Child :info="info" :hobby="hobby" /> </div> </template> <script> import Child from "./Child.vue"; export default { name: "Parent", components: { Child }, data() { return { info: { name: "parent", age: 48 }, hobby: ["fishing", "somking"], }; }, }; </script> <!-- 子组件 Child.vue --> <template> <div>父的 info:{{ info }} -- 父的 hobby:{{ hobby }}</div> </template> <script> export default { name: "Child", // 简单写法 props: ["info", "hobby"], // 完整验证写法 /* props: { info: { type: Object, required: true, default: () => ({ name: 'default', age: 0 }) }, hobby: { type: Array, default: () => ['fishing'] } } */ }; </script>$ref 方式
父组件获取/修改子组件数据、调用子组件方法等(比 props 方式权限更高)。若父组件无需调用子组件方法,更推荐使用 props 方式。
vue3<!-- 父组件 --> <template> <div> <Child ref="sendData" /> <button @click="changeData">修改子组件数据</button> </div> </template> <script setup lang="ts" name="Parent"> import Child from "./components/xxx/Child.vue"; let sendData = ref(); // 得到子组件 ref 对象 // 父传子 function changeData() { sendData.value.info_child = { name: "child", age: 18 }; // 修改子组件数据 -- 方式 1 $refs.sendData.value.info_child = { name: "child", age: 18 }; // 修改子组件数据 -- 方式 2 } </script> <!-- 子组件 --> <template> <div>父的 info:{{ info_child }} -- hobby:{{ hobby_child }}</div> </template> <script setup lang="ts" name="Child"> import { ref } from "vue"; // 子组件数据 let info_child = ref({}); let hobby_child = ref(["working"]); // 把数据交给外部使用 defineExpose({ info_child, hobby_child }); </script>vue2<!-- vue 2 --> <!-- 这种方式打破了组件封装性,应优先考虑使用 props;若父组件想直接调用子组件方法,可使用 $refs --> <!-- 父组件 Parent.vue --> <template> <div> <Child ref="childRef" /> <button @click="changeData">修改子组件数据</button> </div> </template> <script> import Child from "./Child.vue"; export default { name: "Parent", components: { Child, }, methods: { changeData() { // 通过 ref 直接修改子组件数据 this.$refs.childRef.info_child = { name: "child", age: 18 }; this.$refs.childRef.hobby_child = ["working", "reading"]; }, }, }; </script> <!-- 子组件 Child.vue --> <template> <div>父的 info:{{ info_child }} -- hobby:{{ hobby_child }}</div> </template> <script> export default { name: "Child", data() { return { info_child: {}, hobby_child: ["working"], }; }, }; </script>
子传父
- emit 自定义事件方式
$emit 适合用于子组件向父组件传递数据,而不是传递方法
父组件给子组件绑定一个事件,并为事件设置回调函数,用于接收参数。
子组件声明一个事件,指定事件名称和传递参数。
提示
凡是接收数据,必定绑定事件;凡是提供数据,必定触发事件;
vue3<!-- vue 3 --> <!-- 父组件 --> <template> <div> <!-- 给子组件绑定事件 --> <Child @send-child-age="getChildAge" /> 子的数据:{{ childAge }} </div> </template> <script setup lang="ts" name="Parent"> import Child from "./components/xxx/Child.vue"; import { ref } from "vue"; // 接收子组件数据 let childAge = ref(0); function getChildAge(val: string) { childAge.value = val; } </script> <!-- 子组件 --> <template> <div> age -- {{ childAge }} <button @click="emit('send-child-age', childAge)">按钮触发事件</button> </div> </template> <script setup lang="ts" name="Child"> import { ref } from "vue"; let childAge = ref(18); // 声明事件 const emit = defineEmits(["send-child-age"]); // 函数触发 -- 除了用按钮触发,也可以用函数的方式触发 onMounted(() => { emit("send-child-age", childAge); }); </script>vue2<!-- vue 2 --> <!-- 父组件 Parent.vue --> <template> <div> <!-- 监听子组件事件 --> <Child @send-child-age="getChildAge" /> 子的数据:{{ childAge }} </div> </template> <script> import Child from "./Child.vue"; export default { components: { Child }, data() { return { childAge: 0 }; }, methods: { getChildAge(val) { this.childAge = val; }, }, }; </script> <!-- 子组件 Child.vue --> <template> <div> age -- {{ childAge }} <button @click="sendAge">按钮触发事件</button> </div> </template> <script> export default { data() { return { childAge: 18 }; }, methods: { sendAge() { // 触发事件并传递数据 this.$emit("send-child-age", this.childAge); }, }, mounted() { // 组件挂载后自动触发 this.$emit("send-child-age", this.childAge); }, }; </script>
$parent 方式
虽然这种方式也能在父组件中获取、修改子组件数据,但更推荐子组件想调用父组件方法的时候使用
vue3<!-- 父组件 --> <template> <div> 子传递的数据 -- {{ money }} <Child /> </div> </template> <script setup lang="ts" name="Parent"> import Child from "./components/xxx/Child.vue"; let money = ref(100); // 父组件数据 // 把数据交给外部使用 defineExpose({ money }); </script> <!-- 子组件 --> <template> <button @click="changeData($parent)">给父组件发送数据</button> </template> <script setup lang="ts" name="Child"> import { ref } from "vue"; // 子传父 function changeData(parent: any) { parent.money -= 1; // 修改父组件数据 } </script>vue2<!-- vue 2 --> <!-- 父组件 Parent.vue --> <template> <div> 子传递的数据 -- {{ money }} <Child /> </div> </template> <script> import Child from "./Child.vue"; export default { components: { Child }, data() { return { money: 100 }; }, }; </script> <!-- 子组件 Child.vue --> <template> <button @click="changeData">给父组件发送数据</button> </template> <script> export default { methods: { changeData() { this.$parent.money -= 1; // 通过 $parent 直接访问父组件 }, }, }; </script>props 方式
此方式适用于父组件需要获取子组件执行异步操作后返回 Promise 结果,使用场景较少,参考即可。
更推荐使用 emit 事件机制来实现子传父,保持明确的数据流向(子组件 emit → 父组件监听)
vue3<!-- vue 3 --> <!-- 父组件 Parent.vue --> <template> <div> <Child :fetch-data="handleFetchData" /> <p>加载状态:{{ loading ? "加载中。.." : "完成" }}</p> <p>获取子的数据:{{ apiData }}</p> </div> </template> <script setup lang="ts"> import { ref } from "vue"; import Child from "./Child.vue"; const loading = ref(false); const apiData = ref(null); // 接收子组件的 Promise 回调 const handleFetchData = async () => { loading.value = true; try { apiData.value = await Child.fetchData(); // 等待子组件返回数据 } finally { loading.value = false; } }; </script> <!-- 子组件 Child.vue --> <template> <button @click="triggerFetch">获取数据</button> </template> <script setup lang="ts"> defineProps<{ fetchData: () => Promise<any>; }>(); // 模拟异步操作 const mockApiCall = () => { return new Promise((resolve) => { setTimeout(() => resolve({ age: 25, name: "John" }), 1000); }); }; const triggerFetch = async () => { const data = await mockApiCall(); return data; // 这里实际通过 props 回调返回 }; </script>vue2<!-- vue 2 --> <!-- 父组件 Parent.vue --> <template> <div> <Child :fetch-data="handleFetchData" /> <p>加载状态:{{ loading ? "加载中。.." : "完成" }}</p> <p>获取子的数据:{{ apiData }}</p> </div> </template> <script> import Child from "./Child.vue"; export default { components: { Child, }, data() { return { loading: false, apiData: null, }; }, methods: { async handleFetchData() { this.loading = true; try { this.apiData = await this.$refs.child.fetchData(); } finally { this.loading = false; } }, }, }; </script> <!-- 子组件 Child.vue --> <template> <button @click="triggerFetch">获取数据</button> </template> <script> export default { props: { fetchData: { type: Function, required: true, }, }, methods: { mockApiCall() { return new Promise((resolve) => { setTimeout(() => resolve({ age: 25, name: "John" }), 1000); }); }, async triggerFetch() { const data = await this.mockApiCall(); return this.fetchData(data); }, }, }; </script>
依赖注入
使用 provide--inject 方式可以完成一个父组件相对于其所有的后代组件间相互通信。参考地址
<!-- 父组件 -->
<template>
<div>
后代组件修改数据 -- {{ money }}
<Child />
</div>
</template>
<script setup lang="ts" name="Parent">
import Child from "./components/xxx/Child.vue";
import { provide } from "vue"; // 提供者,为后代提供数据
let money = ref({ count: 100, name: "RMB" });
let phone = ref("110");
function updateMoney(value: number) {
money.value.count -= value;
}
// 把数据提供给后代使用
provide("money", money);
provide("moneyContext", { phone, updateMoney });
</script>
<!-- 后代组件 -->
<template>
<div>
收到上层组件的数据:{{ money.count }}
<button @click="updateMoney(1)">给上层组件发送数据</button>
</div>
</template>
<script setup lang="ts" name="Child">
import { ref } from "vue";
import { inject } from "vue";
// inject 注入数据。它的第二个参数可以设置默认值,以防止模板语法{{ money.count }}发出警告
const money = inject("money", { count: 0, name: "默认值" });
const { phone, updateMoney } = inject("moneyContext", {
phone: "默认",
updateMoney: (x: number) => {},
});
</script><!-- vue 2 -->任意组件通信
若组件之间不是父子关系,可使用 mitt 实现任意组件间的通信。
安装
$ npm install --save mitt使用示例
还是那句话:凡是接收数据,必定绑定事件;凡是提供数据,必定触发事件;
以下是组件 A 接收组件 B 数据的一个简单示例:
vue3<!-- vue 3 --> <!-- 组件 A --> <template> <div>组件 B 的数据:{{ dataB }}</div> </template> <script setup lang="ts" name="A"> import mitt from "mitt"; import { ref } from "vue"; const emitter = mitt(); // 接收数据 let dataB = ref(0); emitter.on("my-foo", (e:any) => (dataB.value = e)); // 组件卸载时解绑事件(类似于 vue2 中的总线 bus) onUnmounted(() => emitter.off("my-foo")); </script> <!-- 组件 B --> <template> <div> age -- {{ childAge }} <button @click="emitter.emit("my-foo", myData)"> 发送数据--按钮触发 </button> </div> </template> <script setup lang="ts" name="B"> import { ref } from "vue"; import mitt from "mitt"; const emitter = mitt(); const myData = ref({ name: "zs" }); // 发送数据 -- 函数触发 onMounted(() => { emitter.emit("my-foo", myData); }); </script>vue2<!-- vue 2 使用事件总线 --> <!-- app.vue --> <!-- import Vue from 'vue' export const EventBus = new Vue() --> <!-- 组件 A --> <script> import { EventBus } from "./eventBus"; export default { methods: { sendMessage() { EventBus.$emit("message", "Hello from A"); }, }, }; </script> <!-- 组件 B --> <script> import { EventBus } from "./eventBus"; export default { created() { EventBus.$on("message", (msg) => { console.log(msg); // 'Hello from A' }); }, beforeDestroy() { EventBus.$off("message"); // 记得移除监听 }, }; </script>
示例
<!-- vue 3 --><!-- vue 2 -->通用 crud
- element ui
...通用状态
<el-table-column label="审核状态" align="center">
<template slot-scope="scope">
{{ showStatus(scope.row.checkStatus) }}
</template>
</el-table-column>
<script>
export default {
methods: {
showStatus(status) {
switch (parseInt(status)) {
case 0:
return "审核中";
case 1:
return "已通过";
case 2:
return "已拒绝";
default:
return "未知";
}
},
},
};
</script><el-table-column label="审核状态" align="center">
<template slot-scope="scope">
<span :style="{ color: getStatusText(scope.row.checkStatus).color }">
{{ getStatusText(scope.row.checkStatus) }}
</span>
</template>
</el-table-column>
<script>
export default {
methods: {
getStatusText(status) {
const map = {
0: { text: "审核中", color: "#E6A23C" }, // 橙色
2: { text: "拒绝", color: "#F56C6C" }, // 红色
};
return map[status] || { text: "未知", color: "#909399" }; // 灰色
},
},
};
</script>
