前言
最近有个小朋友想了解Vue前端技术,但他只懂一些HTML基础,让我用最简单的方式讲解。于是就有了这篇面向初学者的博文。
老手请绕行,本文专为新手准备。如果发现用词不当的地方欢迎留言指正,觉得对新手有帮助的话请收藏点赞。
基础
-
什么是Vue
它是一个前端框架、目前最新版本是Vue3、本质上就是在单个页面上进行组件渲染。
-
为什么是Vue
原因很多,但最重要的是生态好,学、用的人多;(参照来源各招聘平台对于前端的要求)
-
Vue 版本选择
作为技术,当然选择新版本了,但相对旧版本有一些变化,新版本同时支持组合式API与选项式API,目前官方推荐采用组合式API(用到什么就引用什么速度更快)
项目\版本 | VUE2 | VUE3 |
---|---|---|
构建工具 | Vue CLI | Vite |
状态管理 | VueX | Pinia |
IDE 增强插件 | Vetur | vue-official |
静态生成 | VuePress | VitePress |
-
Vue 引擎选TS还是JS
JavaScript (JS):简单、广泛。TypeScript(TS):类型安全、更好的可维护性。同样都是要花精力学习的话还是推荐TS,尽管你学习了JS,还是建议你慢慢接受TS。
-
Vue Router
VUE Router 官言路由,用于跳转用;之前用MVC是用后端Controller层控制路由跳转,现在由前端控制。
-
Vue Panner
基于Vue3.0 状态管理工具
-
VitePress
静态网站成生器:VUE本质上就是在单个页面上组装拆解,所以它不利于SEO优化(换言之搜索引擎的网站爬不到),现在有了它就解决了这个问题。
-
开发工具选择
这个因人而异、可以是VSCode(免费)、也可以是WebStorm(收费),以下是常见的工具对比。
类型/常用工具 | VSCode | HBuilder | WebStrom | Trae | cursor |
---|---|---|---|---|---|
费用 | 免费 | 免费 | 收费 | 免费 | 免费 |
插件生态评分(个人) | 95 | 60 | 95 | 80 | 80 |
智能化(个人) | 80 | 60 | 70 | 90 | 90 |
-
准备
在开发的电脑上必有安装好Node,推荐使用新版本。
-
创建
npm init vue@3
npm init vue@3
Need to install the following packages:
create-vue@3.16.4
Ok to proceed? (y) y //是否要创建版本为3.16.4 的VUE┌ Vue.js - The Progressive JavaScript Framework
│
◆ 请输入项目名称:
│ demo█
-
选择
Need to install the following packages:
create-vue@3.16.4
Ok to proceed? (y) y
┌ Vue.js - The Progressive JavaScript Framework
│
◇ 请输入项目名称:
│ demo
│
◆ 请选择要包含的功能: (↑/↓ 切换,空格选择,a 全选,回车确认)
│ ◻ TypeScript
│ ◻ JSX 支持
│ ◻ Router(单页面应用开发)
│ ◻ Pinia(状态管理)
│ ◻ Vitest(单元测试)
│ ◻ 端到端测试
│ ◻ ESLint(错误预防)
│ ◻ Prettier(代码格式化)
-
目录
node_modules/ 存放NPM下载的依赖包 package.json 项目依赖配置文件 index.html VUE项目的入口文件 vite.config.ts vue的构建、打包配置文件 env.d.ts TypeScri环境变量,全局类型声明 public/ 静态资源目录 src/源码目录app.vue 应该主组件main.ts 应该入口文件,创建Vue实例与挂载assets/ 静态资源文件components/通常存放复用组件目录 main.css 全局样式文件 base.css 基础样式文件
项目运行流程
1.main.ts里声明一个VUE项目中的唯一一个实例
import './assets/main.css'import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
2.index.html 引用main.ts的实例,去挂载主组合app.vue
<!DOCTYPE html>
<html lang=""><head><meta charset="UTF-8"><link rel="icon" href="/favicon.ico"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vite App</title></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body>
</html>
3.app.vue 总父组件去挂载各种子组件
<!DOCTYPE html>
<html lang=""><head><meta charset="UTF-8"><link rel="icon" href="/favicon.ico"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vite App</title></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body>
</html>
VUE模板
app.vue是主组件,以下相关功能演示都在这个组件中测试。
一个完整Vue文件必须包含script与模板template,且忽略前后顺序(前后位置不影响)
<script setup lang="ts">//数据业务处理层
</script><template>//数据呈现
</template>
数据绑定渲染示例
<script setup lang="ts">const msg = 'Hello World'
</script><template><h1>{{ msg }}</h1>
</template>
组合式API
选项式API网上教程太多了不再说明,我这里只对ts下的组合式API进行示例。
<script setup lang="ts">
// setup 相关于 选项式中的default....那一套,setup 函数 ;lang="ts" 告诉编译器,这个文件是ts文件//推荐使用ref()来定义响应式数据import {computed, onMounted, onUnmounted, ref, watch} from "vue";const msg=ref('这是一个消息');let msg2="这也是一个消息"//定义方法function changeMsg(){msg.value="这是修改后的消息";msg2="这也是修改后的消息";}//监听msg数据watch(msg, (newVal) => {alert(newVal);})watch(() => msg2, (newVal) => {alert(newVal);})// 计算属性const Isok = computed(() => {return msg.value === "这是一个消息" ? "这是一个消息" : "";});// 生命组件周期..........onMounted(() => {alert("组件已挂载");});onUnmounted(() => {alert("组件已卸载");});</script><template><div><h1>{{msg}}</h1><h1>{{msg2}}</h1><button @click="changeMsg">修改消息</button></div>
</template>
VUE的基本操作
-
渲染
条件渲染
<script setup lang="ts">const code='A';const isShow=true;
</script><template><div ><!-- 条件渲染:if ---><h1 v-if="code === 'A'">A</h1><h1 v-else-if="code === 'B'">B</h1><h1 v-else>C</h1><!-- 条件渲染显示控制 ---><h1 v-show="isShow">isShow</h1></div></template>
列表渲染
可以将List和对象渲染出来,原理是:For循环方式处理
<script setup lang="ts">const list = [{id: 1,name: 'Todo 1',completed: false},{id: 2,name: 'Todo 2',completed: true},{id: 3,name: 'Todo 3',completed: false}];const obj={name:'test',age:18,sex:'男',height:1.8};
</script><template><div ><li v-for="(item,index) in list" :key="item.id">{{index}} - {{ item.name }}</li><li v-for="(item,index) in obj" :key="index">{{ item }}</li></div></template>
-
事件
无参
<script setup lang="ts">const showMessageWithParam = (message: string) => {alert(message);
};</script><template><div ><button @click="showMessageWithParam('TEST!')">点击我</button></div></template>
有参
<script setup lang="ts">
const msg="";
const showMessageWithParam = (message: string) => {alert(message);
};
</script>
<template><div ><input v-model="msg" placeholder="请输入内容" /><button @click="showMessageWithParam(msg)">传递输入内容</button></div>
</template>
Event处理
<script setup lang="ts">
const list = ["1", "2", "3"];
const showMessageWithParam = (e: Event, message: string) => {(e.target as HTMLButtonElement).innerHTML = "已点击";alert(message);
};
</script>
<template><div ><li v-for="item in list" :key="item" @click="showMessageWithParam($event,item)">{{ item }}</li></div>
</template>
事件修饰
对事件进行修饰以达到阻止事件或事件冒泡等问题
<script setup lang="ts">
const message = "hello world";
const showMessageWithParam = (e: Event, message: string) => {(e.target as HTMLButtonElement).innerHTML = "已点击";alert(message);
};
</script>
<template><div ><a href="https://www.baidu.com" @click.prevent="showMessageWithParam($event,message)">测试一下</a></div>
</template>
-
属性
class
在TS中定义一个变量去指向一个样式类,再模板中用这个定义的变量去渲染就实现了属性绑定;绑定方式以『:』+属性="定义变量的名"形式实现,如下所示。
<script setup lang="ts">
const classObj = 'test';
const demoid=11;
const msg = 'Hello World!';
</script><template><div :class="classObj" :id="demoid.toString()"> <h1>{{ msg }}</h1></div></template><style scoped>
.test {color: red;font-size: 50px;
}
</style>
以上可以把一组绑定直接封装一个对象进行绑定
<script setup lang="ts">
const msg = 'Hello World!';
const obj={class:'test',id:'abc'
};
</script><template><!-- 绑定对象 ---><div v-bind="obj"><h1>{{ msg }}</h1></div></template><style scoped>
.test {color: red;font-size: 50px;
}
</style>
以数组形式绑定
<script setup lang="ts">
const classObj = 'test';
const demoid=11;
const msg = 'Hello World!';
const t1="test2";
const t2="test3";
</script><template><!--- 以对象形式绑定 --><div :class="classObj" :id="demoid.toString()"><h1>{{ msg }}</h1></div><!--- 以数组形式绑定 --><div :class="[t1,t2]"><h1>{{ msg }}</h1></div></template><style scoped>
.test {color: red;font-size: 50px;
}
.test2{color: blue;
}
.test3{font-size: 30px;
}
</style>
style
以:style='类'进行绑定,也可以通过:style='[类1、类2]'以数组进行绑定
<script setup lang="ts">
const msg = 'Hello World!';
const obj={color :'red',fontSize :'30px'
};
const ob2={backgroundColor :'yellow'
};
</script><template><!--- 以对象形式绑定 --><div :style="obj"><h1>{{ msg }}</h1></div><div :style="[obj,ob2]"><h1>{{ msg }}</h1></div>
</template>
-
数组
方法 | 解释 |
---|---|
push() | 向数组中添加数据 |
pop() | 删除数组中的数据 |
shift() | 删除第一个 |
unshift() | 向数组中添加数据并放在第一位 |
splice() | splice(开始删除的位置, 数量(可选择), 删除的元素1(可选择), ..., 删除的元素N(可选择)) |
sort() | 排序(升) |
reverse() | 反转数组 |
concat() | 以替换数组形式添加数据 |
filter() |
<script setup lang="ts">
import { ref } from "vue";const newItem = ref("");
const list = ref(["a", "b", "c"]); // 使用 ref 包裹 list 以保证响应性
const addItem = () => {if (newItem.value.trim()) {list.value.push(newItem.value); // 向数组中添加数据newItem.value = "";}
};
const addItem2 = () => {if (newItem.value.trim()) {list.value = list.value.concat([newItem.value]);//以替换的方式添加到数组中newItem.value = "";}
};
</script><template><div><div><input v-model="newItem" placeholder="输入新项" /><button @click="addItem">添加</button><button @click="addItem2">添加2</button></div><ul><li v-for="item in list" :key="item">{{ item }}</li></ul></div>
</template>
-
侦听
通过watch定义要监听的对象实现更多实用的功能
<script setup lang="ts">
import { ref,watch } from 'vue';
const msg = ref('Hello World!');
function changeMsg() {msg.value = 'Hello TONGYI Lingma!';
}watch(() => msg.value,(newVal, oldVal) => {alert(`msg 从 ${oldVal} 变为 ${newVal}`);}
);</script><template><!--- 以对象形式绑定 --><div ><h1>{{ msg }}</h1> <button @click="changeMsg">change</button></div></template>
-
计算属性
为了解决复杂的计算,让代码具备一定的可维护性
<script setup lang="ts">
import { computed, ref } from "vue";const newItem = ref("");
// 计算属性
const Isok = computed(() => {return newItem.value === "ok" ? "ok" : "";
});</script><template><div><div><input v-model="newItem" placeholder="输入新项" /><!--- 计算属性 ---><p v-text="Isok"></p></div></div>
</template>
-
表单
表单:v-mode="数据"可以对数据进行双向绑定
<script setup lang="ts">
import { ref } from 'vue';
const msg = ref('Hello World!');
function changeMsg() {msg.value = 'Hello TONGYI Lingma!';
}
</script><template><!--- 以对象形式绑定 --><div ><h1>{{ msg }}</h1></div><div><form><input type="text" v-model="msg"></input></form></div>
</template>
-
DOM操作
尽管不提倡直接操作DOM,也有时我们需要对DOM进行操作;通过refs的方式就能实现;
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const msg = ref('Hello World!');
const testRef = ref<HTMLElement | null>(null);const sendMessage = () => {if (testRef.value) {testRef.value.innerHTML = '修改了!';}
};
</script><template><div ref="testRef"><h1>{{ msg }}</h1><button @click="sendMessage">操作</button></div>
</template>
组件
-
生命周期
组件类似于当年我在做CS程序的时候,窗口有创建事件、显示事件、关闭事件等,在相应的事件里写相应的代码;VUE也是同理,生命周期如下图所示。根据这些生命周期去处理相应周期的代码去实现相应的逻辑功能。
<script setup lang="ts" xmlns="http://www.w3.org/1999/html">
import {ref, onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUnmount, onBeforeUpdate} from 'vue';const msg = ref('199991111');
//组件创建前
onBeforeMount(() => {console.log('组件创建前');
})
// 组件创建后示例
onMounted(() => {console.log('组件创建后示例');
})
//组件挂载前
onBeforeMount(() => {console.log('组件挂载前');
})
//组件挂载后
onMounted(() => {console.log('组件挂载后');
})
//组件更新前
onUpdated(() => {console.log('组件更新前');
})
//组件更新后
onBeforeUpdate(() => {console.log('组件更新后');
})
//组件卸载前
onBeforeUnmount(() => {console.log('组件卸载前');
})
//组件卸载后
onUnmounted(() => {console.log('组件卸载后');
})</script>
<template></template>
-
构成部分
前面讲过VUE 本质上就是在一个HTML不断的组拆,而APP.VUE文件是一个入口文件,所以每一个vue文件视作一个组件。
组件包含必要的<template>负责数据呈现 、<script> 业务逻辑处理(如无逻辑与数据可以不包含)、样式<style>部分(如无样式或默认总样式可以不包含。
<script setup lang="ts"></script><template></template><style scoped></style>
-
引用
定义的组件:test.vue
<script setup lang="ts">const msg='测试一下';
</script>
<template><div><h1>{{msg}}</h1></div>
</template>
调用组件:app.vue
<script setup lang="ts">import Test from '@/com/test.vue'
</script><template><Test/>
</template>
-
嵌套
下面是一个关系图:
人(app.vue)-头(head.vue)-中部(mind.vue)-左手(leftHand.vue)-胸(chest.vue)-右手(rightHand.vue)-下部(boot.vue)-左腿(leftLeg)-右腿(rightLeg)
接下来我将用代码进行实现
head.vue:头部代码
<template><div><h1>------头部-------</h1></div>
</template>
leftHand.vue:左手
<template><div><h1>左手</h1></div>
</template>
chest.vue:胸
<template><div><h1>这是胸部</h1></div>
</template>
rightHand.vue:右手
<template><div><h1>右手</h1></div>
</template>
mind.vue:中部
<script setup lang="ts">
import LeftHand from "@/com/leftHand.vue";
import Chest from "@/com/chest.vue";
import RightHand from "@/com/rightHand.vue";
</script><template><div><h1>-----中部-------</h1><LeftHand /><Chest /><RightHand /></div>
</template>
leftLeg.vue:左腿
<template><div><h1>左腿</h1></div>
</template>
rightLeg.vue:右腿
<template><div><h1>右腿</h1></div>
</template>
boot.vue:下部
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";
</script>
<template><h1>---下部------</h1><LeftLeg /><RightLeg />
</template>
app.vue:整体
<script setup lang="ts">import Head from "@/com/head.vue";import Mind from "@/com/mind.vue";import Boot from "@/com/boot.vue";
</script><template><div><Head/><Mind/><Boot/></div></template>
-
全局注册
以上是做了个示例,如果你想不引用,实现处理调用那就要配置相应组为全局组件,那需要在main.ts里声明组件。以下是把test.vue注册为全局,然后用app.vue直接调用。
ps:非必要不提供注册全局组件
main.ts
import './assets/main.css'import { createApp } from 'vue'
import App from './App.vue'
import Test from "@/com/test.vue";createApp(App).mount('#app')
app.vue
<script setup lang="ts">import Head from "@/com/head.vue";import Mind from "@/com/mind.vue";import Boot from "@/com/boot.vue";import Test from "@/com/test.vue";
</script><template><div><test/><Head/><Mind/><Boot/></div></template>
-
传值
父传子
方案1:props 逐级传值
在父组件上调用子组件以:传递名="数据"的形式进行传值;在子组件上的props上用传递名接收,且接收到的数据无法被修改。
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";
import {ref} from "vue";// 自定义事件函数
const handleCustomEvent = (data :any) => {msgFormLeft.value = data;
};
const msgFormLeft=ref('空');
const test2="test2";
const test3={name : "test3",age : 18,
}
</script><template><!--- 只用test传数据 test2传绑定数据 test3传对象---><LeftLeg test="msg" :test2="test2" :test3="test3"/><a>{{msgFormLeft}}</a><RightLeg />
</template>
子组件
<script setup lang="ts">
import { defineEmits, ref, watch } from 'vue';// 定义 props 接收父组件传递的 PMSG 参数
const props = defineProps({test: {type: String,required: true},test2: {type: String},test3: {type: Object,default: () => ({ name: '', age: 0 })}
});// 将 PMSG 参数赋值给 msg
const msg = ref(props.test);</script>
<template><div><h1>左腿</h1><a>{{ msg }}</a><a>{{ test2 }}</a><a>{{ test3.name }}</a><a>{{ test3.age }}</a></div>
</template>
方案2:依赖注入
上面的方案虽然可以解决父传子,但很多项目是组件间层层嵌套。想像一下如果通过上述方式传值不仅代码量变大,而且可维护性也差。那么通过依赖注入的方式就可以实现父组件直传到基于父组件下的任何一个组件中。
首先provide在父组件中定义,子组件中用inject接收就可以了。
app.vue
<script setup lang="ts">import Boot from "@/com/boot.vue";import {provide} from "vue";
const myValue = "Hello from provide/inject";
const obj={name: 'John',age: 30
};
provide('myKey', myValue);
provide('myObj', obj);
</script><template><div><Boot/></div></template>
子子组件rightLeg.vue
<script setup lang="ts">
import {inject} from "vue";
const msg = inject<string>("myKey");
const obj = inject<{ name: string, age: number}>("myObj");
</script>
<template><div><h1>右腿</h1><hr><h1> {{ inject<string>("myKey") }} </h1><hr><h1>{{ msg }}</h1><hr><h1>{{ obj?.name }}</h1><hr><h1>{{ obj?.age }}</h1></div>
</template>
子传父
方案1:自定义事件传递
-
子组件里定义一个方法
-
在子组排方法里以emit('标识')的方式命名一个标识
-
父组件里在调用子组件那里用@标识名绑定个这标识并在后面对应父组件对应的事件
无参
子组件
<script setup lang="ts">import { defineEmits } from 'vue';const emit = defineEmits(['fTest']);const test = () => {emit('fTest');}
</script>
<template><div><h1>左腿</h1><button @click="test">回传调用父组件</button></div>
</template>
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";// 自定义事件函数
const handleCustomEvent = () => {alert("来自子数据的调用")
};
</script><template><LeftLeg @fTest="handleCustomEvent"/><RightLeg />
</template>
有参
子组件
<script setup lang="ts">import { defineEmits } from 'vue';const emit = defineEmits(['fTest']);const test = () => {emit('fTest', '左腿');}
</script>
<template><div><h1>左腿</h1><button @click="test">回传调用父组件</button></div>
</template>
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";// 自定义事件函数
const handleCustomEvent = (data :any) => {alert(data)
};
</script><template><LeftLeg @fTest="handleCustomEvent"/><RightLeg />
</template>
时时传
子组件
<script setup lang="ts">
import { defineEmits, ref, watch } from 'vue';const emit = defineEmits(['fTest']);
const msg = ref('');const test = () => {emit('fTest', msg.value);
};watch(msg, (newVal) => {// 当msg值发生变化时执行test方法test();
});
</script>
<template><div><h1>左腿</h1><input type="text" v-model="msg" @change="test"></div>
</template>
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";
import {ref} from "vue";// 自定义事件函数
const handleCustomEvent = (data :any) => {msgFormLeft.value = data;
};
const msgFormLeft=ref('空');
</script><template><LeftLeg @fTest="handleCustomEvent"/><a>{{msgFormLeft}}</a><RightLeg />
</template>
方案2:利用props传事件实现
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";
import {ref} from "vue";
// 自定义事件函数
const handleCustomEvent = (data :any) => {msgFormLeft.value = data;
};
const msgFormLeft=ref('空');
</script><template><!--- 只用test传数据 test2传绑定数据 test3传对象---><LeftLeg test="msg" :handleCustomEvent="handleCustomEvent"/><a>{{msgFormLeft}}</a><RightLeg />
</template>
子组件
<script setup lang="ts">
import { ref, watch } from 'vue';
const props = defineProps({handleCustomEvent: {type: Function,required: true}
});
const msg = ref('');watch(() => msg.value, (newValue, oldValue) => {props.handleCustomEvent(newValue)
});
// 如果需要在子组件中直接调用父组件函数,可以通过以下方式:
// 例如:props.handleCustomEvent('aaaaaaaaa');
</script>
<template><div><h1>左腿</h1><input type="text" v-model="msg"></div>
</template>
-
插槽
以<slot>为标签显示,把带有引用的子组件内容块显示
直接插
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";
import { ref } from "vue";const msgFormLeft = ref('空1111111');</script><template><LeftLeg><!-- 插槽A用于传递父组件的固定内容 --><div><h2>这是传过来的内容</h2></div></LeftLeg><RightLeg />
</template>
子组件
<script setup lang="ts" xmlns="http://www.w3.org/1999/html">
import { ref, watch } from 'vue';const msg = ref('');</script>
<template><div><h1>左腿</h1><slot ></slot><hr/></div>
</template>
插进去带数
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";
import { ref } from "vue";const msgFormLeft = ref('空1111111');</script><template><LeftLeg><!-- 插槽A用于传递父组件的固定内容 --><div><a>{{ msgFormLeft }}</a></div></LeftLeg><RightLeg />
</template>
子组件
<script setup lang="ts" xmlns="http://www.w3.org/1999/html">
import { ref, watch } from 'vue';const msg = ref('');</script>
<template><div><h1>左腿</h1><slot ></slot><hr/></div>
</template>
一次插入多个,分别显示
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";
import { ref } from "vue";const msgFormLeft = ref('空1111111');</script><template><LeftLeg><!-- 插槽A用于传递父组件的固定内容 --><template #A><div><h2>这是传过来的内容</h2></div></template><!-- 插槽B用于传递父组件的数据内容 --><template #B><div><a>{{ msgFormLeft }}</a></div></template></LeftLeg><RightLeg />
</template>
子组件
<script setup lang="ts" xmlns="http://www.w3.org/1999/html">
import { ref, watch } from 'vue';const msg = ref('');</script>
<template><div><h1>左腿</h1><slot name="A"></slot><hr/><slot name="B"></slot><hr/></div>
</template>
插进去的数据混合父数据与子数据
子组件
<script setup lang="ts" xmlns="http://www.w3.org/1999/html">
import { ref, watch } from 'vue';const msg = ref('199991111');</script>
<template><div><h1>左腿</h1><slot name="A"></slot><hr/><slot name="B"></slot><hr/><slot name="C" :msg="msg"></slot><hr/></div>
</template>
父组件
<script setup lang="ts">
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";
import { ref } from "vue";const msgFormLeft = ref('空1111111');</script><template><LeftLeg><!-- 插槽A用于传递父组件的固定内容 --><template #A><div><h2>这是传过来的内容</h2></div></template><!-- 插槽B用于传递父组件的数据内容 --><template #B><div><a>{{ msgFormLeft }}</a></div></template><!-- 插槽C用于接收来自子组件的消息 --><template #C="{ msg }"><div><p>来自子组件的消息: {{ msg || '暂无消息' }}</p></div></template></LeftLeg><RightLeg />
</template>
-
动态组件
我们大多数情况下不可能把所有组件都呈现在父组件中,而是希望动态实现它。
这时我们只需要通过<component :is="组件名或者动态数据中的组件名">就能实现;
基本动态组件
<script setup lang="ts">
import { ref } from "vue";
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";// 定义当前激活的组件名称,类型为组件对象的键,初始值为 "LeftLeg"
const tabComponent = ref<keyof typeof components>("LeftLeg");// 组件对象,包含 LeftLeg 和 RightLeg 两个组件,并通过 as const 确保其键值不可变
const components = {LeftLeg,RightLeg
} as const;// 切换组件函数,接收当前组件名称作为参数,切换为另一个组件
const switchComponent = (componentName: "LeftLeg" | "RightLeg") => {// 如果当前是 LeftLeg,则切换为 RightLeg,反之亦然tabComponent.value = componentName === "LeftLeg" ? "RightLeg" : "LeftLeg";
};</script><template><!-- 使用动态组件并确保正确绑定 --><component :is="components[tabComponent]" /><button @click="switchComponent(tabComponent)">切换</button></template>
动态组件驻留
以上组件每次加载都会完成一次初始化会让对应页面的数据还原,此时在外部增加keep-alive组件包含一下就可以了
父组件
父组件
<script setup lang="ts">
import { ref } from "vue";
import LeftLeg from "@/com/leftLeg.vue";
import RightLeg from "@/com/rightLeg.vue";// 定义当前激活的组件名称,类型为组件对象的键,初始值为 "LeftLeg"
const tabComponent = ref<keyof typeof components>("LeftLeg");// 组件对象,包含 LeftLeg 和 RightLeg 两个组件,并通过 as const 确保其键值不可变
const components = {LeftLeg,RightLeg
} as const;// 切换组件函数,接收当前组件名称作为参数,切换为另一个组件
const switchComponent = (componentName: "LeftLeg" | "RightLeg") => {// 如果当前是 LeftLeg,则切换为 RightLeg,反之亦然tabComponent.value = componentName === "LeftLeg" ? "RightLeg" : "LeftLeg";
};</script><template><!-- 使用动态组件并确保正确绑定 --><keep-alive><component :is="components[tabComponent]" /></keep-alive><button @click="switchComponent(tabComponent)">切换</button></template>
子组件
<script setup lang="ts">
import { ref } from "vue";const tttt = ref("左腿");function updateMsg(newMsg: string) {tttt.value = newMsg;
}
</script><template><h1>{{ tttt }}</h1><button @click="updateMsg('1111111')">更新</button>
</template>
动态组件懒加载
动态组件时会一次性把组件所有组件进行渲染,而用到什么就渲染什么其实才科学。
<script setup lang="ts">
import {defineAsyncComponent, ref} from "vue";
// 异步组件
const LeftLeg = defineAsyncComponent(() => import("@/com/leftLeg.vue"));
const RightLeg = defineAsyncComponent(() => import("@/com/rightLeg.vue"));// 定义当前激活的组件名称,类型为组件对象的键,初始值为 "LeftLeg"
const tabComponent = ref<keyof typeof components>("LeftLeg");// 组件对象,包含 LeftLeg 和 RightLeg 两个组件,并通过 as const 确保其键值不可变
const components = {LeftLeg,RightLeg
} as const;// 切换组件函数,接收当前组件名称作为参数,切换为另一个组件
const switchComponent = (componentName: "LeftLeg" | "RightLeg") => {// 如果当前是 LeftLeg,则切换为 RightLeg,反之亦然tabComponent.value = componentName === "LeftLeg" ? "RightLeg" : "LeftLeg";
};</script><template><!-- 使用动态组件并确保正确绑定 --><keep-alive><component :is="components[tabComponent]" /></keep-alive><button @click="switchComponent(tabComponent)">切换</button></template>
指令
所谓指令是指操作DOM元素的命令,VUE有内置的v-if、v-show就是指令;
自定义指令
-
以变量形式以对象方式出现,具有固定的生命周期
-
变量名命名以驼峰方式进行命令。
-
使用则驼峰以'-'拆分且全部小写。
示例
<script setup lang="ts">
import LeftHand from "@/com/leftHand.vue";
import Chest from "@/com/chest.vue";
import RightHand from "@/com/rightHand.vue";
const vTest={create:()=>{console.log("创建")},mounted:()=>{console.log("挂载")},unmounted:()=>{console.log("卸载")},beforeUpdate:()=>{console.log("更新前")},updated:()=>{console.log("更新")},update:() => {console.log("更新")},beforeUnmount:()=>{console.log("卸载前")}
}
</script><template><div><!-- 测试 --><h1 v-test>-----中部-------</h1>
<!-- <LeftHand />-->
<!-- <Chest />-->
<!-- <RightHand />--></div>
</template>
相应的周期函数都包含el对象,利用el对象可以实现具体的功能。
<script setup lang="ts">
import LeftHand from "@/com/leftHand.vue";
import Chest from "@/com/chest.vue";
import RightHand from "@/com/rightHand.vue";
const vTest = {created() {console.log("创建");},mounted(element, binding, vnode) {element.innerHTML=element.innerHTML+"众纳原创";},unmounted() {console.log("卸载");},beforeUpdate() {console.log("更新前");},updated() {console.log("更新");},beforeUnmount() {console.log("卸载前");}
};
</script><template><div><!-- 测试 --><h1 v-test>-----中部-------</h1>
<!-- <LeftHand />-->
<!-- <Chest />-->
<!-- <RightHand />--></div>
</template>
以上定义的指令都是局部指令,如果想整体项目都要用到需要到main.ts里进行定义
import './assets/main.css'import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive('focus', {mounted(element) {element.style.color='red';element.style.fontSize='90px';}
})
app.mount('#app')
使用时则以v-"定义的名称"进行绑定使用
```typescript
import './assets/main.css'import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive('focus', {mounted(element) {element.style.color='red';element.style.fontSize='90px';}
})
app.mount('#app')
```> 使用时则以v-"定义的名称"进行绑定使用```html
<script setup lang="ts">
import LeftHand from "@/com/leftHand.vue";
import Chest from "@/com/chest.vue";
import RightHand from "@/com/rightHand.vue";
const vTest = {created() {console.log("创建");},mounted(element, binding, vnode) {element.innerHTML=element.innerHTML+"众纳原创";},unmounted() {console.log("卸载");},beforeUpdate() {console.log("更新前");},updated() {console.log("更新");},beforeUnmount() {console.log("卸载前");}
};
</script><template><div><!-- 测试 --><h1 v-test v-focus>-----中部-------</h1>
<!-- <LeftHand />-->
<!-- <Chest />-->
<!-- <RightHand />--></div>
</template>
```
本篇完成 ,下篇会涉及路由、状态管理、Axios通信、Element UI等扩展知识,请关注博主,第一时间获取高品质的技术文章。