一、基础准备工作
(一)过程
- 环境要求:有node.js环境、npm。
- 执行命令:
-
npm create vue@latest
而后选择:
-
✔ 请输入项目名称: … me_vue3 ✔ 是否使用 TypeScript 语法? … 否 / 是 ✔ 是否启用 JSX 支持? … 否 / 是 ✔ 是否引入 Vue Router 进行单页面应用开发? … 否 / 是 ✔ 是否引入 Pinia 用于状态管理? … 否 / 是 ✔ 是否引入 Vitest 用于单元测试? … 否 / 是 ✔ 是否要引入一款端到端(End to End)测试工具? › 不需要 ✔ 是否引入 ESLint 用于代码质量检测? … 否 / 是
只有TS选择是即可。
- 最后项目创建成功!
- 进入后根据提示安装两个插件,并执行命令:
npm install
(二)文件结构
- .vscode文件中:extensions.json是官方给vscode做的插件信息("Vue.volar", "Vue.vscode-typescript-vue-plugin"),进入项目后会自动提示进行安装。
- node_modules中都是项目依赖的包。
- public中是公共资源,favicon.ico是网页的页签图标。
- src是项目的核心:assests是放静态资源的文件夹,里面是一些css样式等静态资源;components中是很多组件;App.vue是根组件,每个组件都挂载载App组件上;main.js中主要将根组件挂载。
- .gitgnore是git中一些忽略文件,里面出现的声明在推拉过程中会被忽略。
- env.d.ts中指定了很多文件类型(如jpg等),如果没有此文件,则ts不会认识jpg等格式的文件。
- index.html 程序的入口页面,里面有Id号为App的Div。
- package-lock.json package.json 一个关于webpack的配置信息。
- README.md是一些关于该项目的说明,文本文件。
- tsconfig.app.json ts tsconfig.json tsconfig.json tsconfig.node.json的配置文件。
- vite.config.ts,vite的配置文件。
这里介绍下vite,vite是Vue3新引入的项目构建工具,构建速度要比Vue2更快,同时实现了按需加载,当我们需要哪些时,哪些部分才会被加载。
(三)重要文件的简单认识
index.html
<!DOCTYPE html>
<html lang="en">
<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><!--命名为app,是App组件加载的地方-->
<script type="module" src="/src/main.ts"></script><!--引入main.ts,由其负责将App组件加入到该位置,挂载-->
</body>
</html>
main.ts
import './assets/main.css'
import { createApp } from 'vue'//从vue中引入createApp负责创建组件,并挂载到#app元素。
import App from './App.vue' //引入App组件
createApp(App).mount('#app'); //创建App组件(根组件),并挂载到#app元素。
(四)重写src文件
重写APP组件(Vue2写法):
main.ts无变化;
App.vue:
<template>
<!--vue3不需要最外层的根元素-->
<div id="idContain">
破天荒!
<Person></Person>
</div>
</template>
<script lang="ts">
import Person from "./components/Person.vue";
export default {
name: "App",
components:{
Person,
}
}
</script>
<style>
#idContain{
border-radius:5px;
border: gray solid 2px;
background-color: burlywood;
box-shadow: 3px 4px black;
}
</style>
Person.vue:
<template>
<div id="personContain">
<h2>{{ name }}</h2>
<h2>{{ sex }}</h2>
<button @click="getTel">点我获取联系方式</button>
<button @click="changeName">点我改变姓名</button>
<button @click="changeSex">点我改变性别</button>
</div>
</template>
<script lang="ts">
export default {
name:'Person',
data(){
return{
name:'小明',
sex: '男',
tel: '19999999999'
}
},
methods:{
changeName(){
console.log("one");
if(this.name == "小李"){
this.name = "小飞";
}else{
this.name == "小胡";
}
},
changeSex(){
this.sex=='女'?'男':'女';
},
getTel(){
alert(this.tel);
},
}
}
</script>
<style>
#personContain{
border: 2px solid black;
background-color: yellowgreen;
border-radius: 2px;
}
</style>
注意:Vue3中可以使用Vue2语法,但是现在我们这种写法下的数据不是响应式的,虽然函数会执行,data中的数据发生了改变,但是并未被渲染到页面中!
选项式API和组合式API:
Vue2是选项式写法;Vue3是组合式写法;
Vue2选项式的缺点:每当有某个业务需要改变时,关涉到数据信息,业务逻辑等信息,要在data、method等等选项中寻找相关内容,增加维护成本。
Vue3组合式写法的优点:所有关于一个业务的数据,方法等等都写在一块儿,这样就方便业务维护。
再重写组件内容(Vue3写法):
export default {
name:'Person',
setup(){
let name = '小明';//因为数据和逻辑在一块,所以this的作用被弱化
let sex='女'; //注意这里声明的数据需要返回才能被模版捕获
let tel = '1999999999';//注意数据仍然不是响应式的
function getTel(){
alert(tel);
}
function changeName(){
name = '小李';
}
function changeSex(){
sex=="女"?'男':'女';
}
//返回
return {name,sex,tel,changeName,changeSex,getTel}
}
}
//未出现部分不变
</script>
通过测试得知,setup函数的执行时机要在beforeCreate之前,这进一步可以看出是弱化了this的作用。
data、method、setup同用
setup是否能够得到data中的数据?那反过来data是否能够得到setup中的数据?
因为setup要在beforeCreate声明周期之前,所以setup中不能获取data或者method的数据或方法;同样因为data和method是在beforeCreate方法之后声明的,所以它们可以得到setup中的数据。
data中使用setup中的数据:
data(){
return{
name:this.name,
}
},
setup返回类型
- 返回对象类型,中间属性是要被渲染的属性。
- 返回方法,该方法是渲染函数,返回值会覆盖整个模版。
setup语法糖
考虑到大量业务书写时,极易忘记将属性和方法返回,所以可以用如下方式代替setup函数:
<script setup>
let name = '小明';//因为数据和逻辑在一块,所以this的作用被弱化
let sex='女'; //注意这里声明的数据需要返回才能被模版捕获
let tel = '1999999999';//注意数据仍然不是响应式的
function getTel(){
alert(tel);
}
function changeName(){
name = '小李';
}
function changeSex(){
sex=="女"?'男':'女';
}
</script>
其中内容和setup内容一致,同时不需要我们进行返回处理,自动返回变量和函数!这样我们项目中一个vue文件就两个script标签了!
缩写script
sudo npm i vite-plugin-vue-setup-extend
在vite.config.ts中引入并使用:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueViteExtend from "vite-plugin-vue-setup-extend" //引入
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
VueViteExtend(),//使用
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
二、ref、reactive与响应式
(一)ref使用(基本类型响应式、对象类型亦可)
import {ref} from 'vue';
把要变为响应式的数据用ref函数包裹,即可让数据成为响应式数据。
let name = ref('小明');
结构:
通过变量的value属性更改,引起模版自动变化。
function changeName(){
name.value = '小李';
}
ref中也是调用了reactive的方法!
(一)a 利用ref防止命名冲突
场景模拟:App组件和Person组件中都有id为divOne的元素要输出,利用getElementById获取元素输出会产生冲突
App组件:
<div id="idContain">
破天荒!
<Person></Person>
<div id="divOne">我是app组件中的元素</div>
<button @click="outElement">输出元素</button>
</div>
==========
function outElement(){
let elementOne = document.getElementById("divOne");
if(elementOne != null){
console.log(elementOne.innerHTML);
}
}
Person组件:
<div id="personContain">
<div id="divOne" ref="divRefOne">我想要更好的生活</div>
<button @click="outElementOne">输出元素</button>
</div>
=======
function outElementOne(){
let elementOne = document.getElementById("divOne");
if(elementOne != null){
console.log(elementOne.innerHTML);
}
}
输出结果:
解决方法:使用ref标记,修改如下(以Person为例,二者一致)
//1
<div ref="divOne">我是app组件中的元素</div>
//2
let divOne = ref();
function outElement(){
console.log(divOne.value)
}
(一)b当ref标记的是子组件时,需要子组件暴露后,才能输出具体内容
//app
<Person ref="divOne"></Person>
<button @click="outElement">输出元素</button>
//app script
let divOne = ref();
function outElement(){
console.log(divOne.value)
}
子组件不暴露输出结果:
子组件暴露:
let one = "我是即将要暴露的数据";
let two = "我是也是即将要暴露的数据";
defineExpose({one,two});
结果:
(二)reactive使用(对象类型响应式)
import {reactive} from "vue";
对象深度响应,无论多深,都能实时响应到模版
对象:
模版:
<div>
<h2>我的家人有{{ family.one.name}}和{{ family.two.name }}</h2>
<button @click="changeFamilyName">改变家人姓名</button>
</div>
===============================================
script:
//验证对象响应式
let family = reactive({one:{name:"张金平",age:74},two:{name:"陈玉丽",age:45}});
function changeFamilyName(){
family.one.name = "张金平和陈志俭";
family.one.age = 53;
}
数组亦然:
模版:
<h2>
我喜欢的动漫角色有{{ cotton[0].name }}、{{ cotton[1].name }}、{{ cotton[2].name }}
</h2>
<button @click="changeCottonName">改变漫角姓名</button>
script:
let cotton = reactive([{name:'美杜莎女王',age:23},{name:'山内樱良',age:18},{name:'02',age:22}])
function changeCottonName(){
cotton[0].name = '阳菜';
}
(三)两个方法的区别
a、对象整体替换
ref(可以直接替换,仍然响应式):
let cotton = ref([{name:'美杜莎女王',age:23},{name:'山内樱良',age:18},{name:'02',age:22}]) function changeCottonName(){ cotton.value = [{name:'清漪',age:24},{name:'小鸟游六花',age:18},{name:'daring',age:24}]; //响应式 }
reative(不可以直接替换,要使用Object.assign方法)
let cotton = reactive([{name:'美杜莎女王',age:23},{name:'山内樱良',age:18},{name:'02',age:22}]) function changeCottonName(){ // cotton = [{name:'清漪',age:24},{name:'小鸟游六花',age:18},{name:'daring',age:24}];//非响应式 Object.assign(cotton,[{name:'清漪',age:24},{name:'小鸟游六花',age:18},{name:'daring',age:24}]); //响应式 }
b、对象属性的传递响应
深拷贝:
//一般深拷贝(地址值)
let arrOne = cotton[0];
function changArrOne(){
arrOne.name="彩鳞";//改掉后,cotton也会受影响。
}
浅拷贝(用到toRefs方法,地址附着,但不会实时响应):
//一般深拷贝(地址值)
let cotton = {name:"美杜莎",age:33};
let {name,age} = toRefs(cotton);
// let name = toRef(cotton,"name");
function changArrOne(){
name.value = "彩鳞";//改掉后,cotton也会受影响。
console.log(name.value);
}
三、computed函数、watch函数
(一)computed函数
let firstName = "chen";
let endName = "pei";
let allName=computed(()=>{return firstName + endName});
computed整体被修改可以触发setter,获取出发getter
let firstName = ref("chen");
let endName = ref("pei");
const allName=computed({
get(){
return firstName.value + endName.value;
},
set(value){
firstName.value=value.split(",")[0];
endName.value=value.split(",")[1];
}
});
function changAllName(){
allName.value="陈,佩";
}
(二)watch函数、watchEffect函数
- 能够监视的四种类型:ref定义的数据、reactive定义的数据、函数返回一个值(getter函数)、一个包含上述内容的数组
- 监视普通变量:
let firstName = ref("chen");
let endName = ref("pei");
function changFirstName(){
firstName.value = "张";
}
watch(firstName,(newValue,oldValue)=>{
console.log("姓发生了变化!");
})
- 监视对象(ref)
let allName = ref({
firstName:"陈",
endName:"佩"
})
function changFirstName(){
allName.value.firstName = "张";
}
//第三个参数中使用deep配置项开启深度监视
watch(allName,(newValue,oldValue)=>{
console.log("姓发生了变化!");
},{deep: true})
//默认没有深度监视,只会监视整个对象全部的变化。
注意:监视对象整体改变后,newValue和oldValue分别是之前对象和新对象;如果对象某个属性发生改变,新旧值相同。
- 监视对象(reactive)
let allName = reactive({
firstName:"陈",
endName:"佩",
lover:{
name:"傻妞",
birth: 2008
}
})
function changeFirstName(){
allName.firstName = "张";
}
function changeLoverName(){
allName.lover.name = "陆小千";
}
watch(allName,(newValue,oldValue)=>{
console.log("姓或者lover名字发生了变化!");
})
//默认深度监视,无法手动取消深度监视
- 仅监视对象中某个属性
//只需把要监视的属性用函数返回即可完成监视
watch(()=> allName.lover.name,(newValue,oldValue)=>{
console.log("lover名字发生了变化!");
})
- 监视多个可监视类型组成的集合
let allName = reactive({
firstName:"陈",
endName:"佩",
lover:{
name:"傻妞",
birth: 2008
}
})
let outB = ref({
number:2,
age: 23
});
function changeLoverName(){
allName.lover.name = "陆小千";
}
function addNumberOne(){
outB.value.age += 1;
}
watch([()=> allName.lover.name,outB,()=>outB.value.number],(newValue,oldValue)=>{
console.log("lover名字,姓名,或者outB发生了变化!");
},{deep: true})
//这里有ref数据,所以要开启深度监视
- watch解除监视
//返回一个函数类型,用于解除监视
let closeWatch =watch(outB,(newValue,oldValue)=>{
console.log("lover名字,姓名,或者outB发生了变化!");
if(newValue.age == 24){
closeWatch();//关闭监视
}
},{deep: true})
- watchEffect自动监视
//会自动判断出现在条件中需要监视的数据并进行监视
watchEffect(()=>{
if(outB.value.age >= 28){
alert("年龄有点大了,可以考虑结婚了,朋友!");
}else if(allName.firstName == "张"){
alert("大哥,姓也要改呀!");
}
})
四、数据暴露与接收
(一)父组件向子组件传递数据
传递:
<Person :testOne="testOne" :testTwo="testTwo"></Person>
//子组件
接收:
// 接收父组件传来的数据testOne,testTwo
defineProps(['testOne','testTwo']);
注:define开头函数为宏函数,不需要import即可直接使用!
(二)子组件向父组件传递数据
暴露(子组件):
//测试子组件暴露数据
let testSonOne = 'i am super man!';
let testSonTwo = 'i am your lover!';
//暴露属性
defineExpose({testSonOne,testSonTwo});
使用(父组件):
<Person ref="sonComponent"></Person>
<!--父组件中子组件形式-->
//获取子组件,根据子组件标签中ref标识;
let sonComponent = ref();
//获取子组件暴露属性
function showData(){
console.log(sonComponent.value.testSonOne);
}
注意模版加载顺序为:子组件-》父组件!
四、生命周期
(一)Vue2和Vue3对比
beforeCreat 创建前 | setup |
created 创建后 | |
beforeMount 挂载前 | onBeforeMount |
mounted 挂载后 | onMounted |
beforeUpdate 更新前 | onBeforeUpdate |
updated 更新后 | onUpdated |
beforeDestroy 销毁前 | onBeforeUnmount 卸载前 |
destroyed 销毁后 | onUnmounted 卸载后 |
五、hooks与组合式API
(一)两个功能实现,原方式:
<div id="personContain">
<div id="numDiv">
<!-- 功能一部分 -->
<h3>现在的数字是:{{ numNow }}</h3>
<button @click="numAddOne">数字加一</button>
</div>
<div id="mottoDiv">
<!-- 功能二部分 -->
<li v-for="(motto,index) in mottoList" :key="index">{{ motto }}</li>
<button @click="mottoAddOne">增加格言</button>
</div>
</div>
//结构部分
=================
//数据
let numNow = ref(0);
let mottoList = ref(["早起的鸟儿有虫吃!"]);
//方法
function mottoAddOne(){
//从网站获取格言
axios.get("https://v1.hitokoto.cn/?c=f&encode=text").then((data)=>{
//将数据插入数组
mottoList.value.push(data.data);
},(error)=>{alert("出错了好小子!" + error)});
}
function numAddOne(){
numNow.value += 1;
}
- 数据和方法在一块,维护有困难!
(二)使用hook方式
- 创建文件夹,并创建两个功能相关ts,注意文件命名使用驼峰式(非大驼峰)
- 将两个功能相关的数据放入到文件中
mottoAdd:
import {ref,onMounted} from 'vue';
import axios from 'axios';
export default function(){
//数据
let mottoList = ref(["早起的鸟儿有虫吃!"]);
//方法
function mottoAddOne(){
//从网站获取格言
axios.get("https://v1.hitokoto.cn/?c=f&encode=text").then((data)=>{
//将数据插入数组
mottoList.value.push(data.data);
},(error)=>{alert("出错了好小子!" + error)});
};
//同样可以调用生命周期函数
onMounted(()=>{console.log("我是生命周期函数,我被挂在后函数调用了!")});
return {mottoList,mottoAddOne}
}
=================
numAdd:
import {ref} from 'vue';
//hooks中的ts文件也可以调用vue的生命周期函数
export default function(){
let numNow=ref(0);
//方法
function numAddOne(){
numNow.value += 1;
}
return {numNow,numAddOne}
}
- 文件中使用(Person.vue)
import mottoAdd from '../hooks/mottoAdd';
import numAdd from '../hooks/numAdd';
// 使用
let {numNow,numAddOne} = numAdd();
let {mottoList,mottoAddOne} = mottoAdd();
六、路由的使用
(一)创建路由需要的路由组件(放在views文件夹中)
(二)创建路由器(在router文件夹中)
import Index from '../views/Index.vue';
import News from '../views/News.vue';
import Play from '../views/Play.vue';
//引入创建router的函数,创建路由器
import { createWebHashHistory,createRouter } from 'vue-router';
let routerFirst = createRouter({
history:createWebHashHistory(),
routes:[
{
name:'xinwen',
path:'/news',
component:News
},
{
name:'shouye',
path:'/',
component:Index
},
{
name:'yule',
path:'/play',
component:Play
}
]
})
export default routerFirst;
(三)在main.ts中使用路由
//引入
import routerFirst from './router/index';
import {createApp} from "vue";
import App from "./App.vue";
let app = createApp(App);
//使用
app.use(routerFirst);
app.mount('#app');
(四)声明route-link和route-view
<div id="navDiv">
<!-- 用来跳转到指定路由 -->
<router-link to='/' active-class="active">首页</router-link>
<router-link to='/news' active-class="active">新闻</router-link>
<router-link to='/play' active-class="active">娱乐</router-link>
</div>
<!-- 声明路由组件出现的位置 -->
<router-view></router-view>
七、路由相关知识
(一)query传参
父级组件内发送参数:
<router-link
:to="{
path:'/news',
query:{
oneData:'若非群玉山头见',
twoData:'会向瑶台月下逢'
}
}" active-class="active">新闻</router-link>
路由组件内接收参数:
//第一步引入useRouter函数
import {useRoute} from 'vue-router'
//第二步创建Route对象,并获取对应query
let route = useRoute();
let oneData= route.query.oneData;
let twoData= route.query.twoData;
(二)params传参
在路由配置中定义传参参数名:
{
name:'xinwen',
//?表示该属性可有可与
path:'/news/:oneData/:twoData/threeData?',//此行
component:News
},
组件接收参数:
let oneData= route.params.oneData;
let twoData= route.params.twoData;
(三)props传参
router配置中:
{
name:'xinwen',
path:'/news/:oneData/:twoData',
component:News,
props:true //开启后,路径中的参数被作为props参数传入
}
接收:
defineProps(['oneData','twoData']);
(四)自定义传递参数
router配置中:
props(route){
return route.query;
}
Person组件中:
<router-link
:to="{
path:'/news/若非群玉山头见/会向瑶台月下逢',
query:{
oneData:'云想衣裳花想容',
twoData:'春风拂槛露华浓'
}
}" active-class="active">新闻</router-link>
八、编程式路由导航
标签:
<button @click="toNews">新闻</button>
script:
import {useRouter} from 'vue-router';
let router = useRouter();
function toNews(){
router.push(
{
path:'/new',
query:{
oneData:'昨夜雨疏风骤',
twoData:'浓睡不消残酒'
}
}
);
}
小点:redirect重定向到新的路径!
九、状态管理
(一)对比
Vue2使用vuex进行集中式状态管理!Vue3使用pinia进行集中式状态管理!
共同点是,当有数据是全局共享的时候,就要用到集中式状态管理来共同操纵共享的数据。
(二)使用
安装
sudo npm i pinia
引入
//引入集中式状态管理库pinia
import {createPinia} from 'pinia';
应用(main.js)
//创建库
let pinia = createPinia();
//使用pinia库
app.use(pinia);
模块化声明store(创建store文件夹/创建useXXX.ts文件)
模块化要求store命名都为usexxx.ts
//引入pinia中store选项
import {defineStore} from 'pinia';
//调用
//第一参数为唯一固定id号,命名必须为useXXX,这是规定哦
export const useStore = defineStore('useNum',{
state(){
return{
myNum: 0
}//保存在集中状态管理中的数据。
},
actions:{
//该参数是调用时传入滴,注意使用普通函数,因要调用this
cutMyNumFun(numMid:number){
this.$state.myNum -= numMid;
}
},
/* getters 中定义的函数都可以直接在store中调用,类似于计算属性
*/
getters:{
tenNum(state){
return state.myNum * 10;
}
}
});
============
函数式写法:
export const useStore = defineStore('useNum',()=>{
let myNum = ref(0);
function cutMyNumFun(numMid:number){
myNum.value -= numMid;
}
/* getters 中定义的函数都可以直接在store中调用,类似于计算属性
*/
let tenNum = myNum.value * 10;
return {tenNum,myNum,cutMyNumFun};
});
引入并使用:
//引入storeToRefs,让store解构出的属性同样是响应式滴!
import {storeToRefs} from 'pinia';
//引入store
import {useStore} from '../store/numStore';
//产生store
let useNumStore = useStore();
let myNumMid= ref(0);//此数据用作临时中转,下面用到,这里不用关注!
let {myNum,tenNum} = storeToRefs(useNumStore);
- 这样公用数据就被我们响应式引入到组件中了!
在下面结构中使用和更改数据:
结构:
<div id="numAdd">
<h3>现在数字是:{{ myNum }}</h3>
<h3>该数字的十倍是:{{ tenNum }}</h3>
<!-- myNum需要集中管理,另一组件也需要使用 -->
n:<select v-model="myNumMid">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
<option :value="4">4</option>
</select>
<button @click="addNum">点我为数字加n</button>
<button @click="cutNum">点我为数字减n</button>
</div>
script:
//方法逻辑
function addNum(){
/* 修改数据方法一,直接通过store接触到myNum
store中的myNum也是ref响应式,但因为其在对象中,所以会自动解包,不用.value
*/
useNumStore.myNum += myNumMid.value;
}
function cutNum(){
/*数据修改方法二,通过state接触到myNum */
// useNumStore.$state.myNum -= myNumMid.value
/*数据修改方法三,在store配置中增加actions,然后调用actions中相关方法
这种方式一般用在逻辑比较复杂的时候,*/
useNumStore.cutMyNumFun(myNumMid.value);
}
(三)store.$subscribe订阅
当咱创建的store对象中state数据有变化时,该函数会被调用!
useNumStore.$subscribe((mutations,state)=>{
//state中的数据发生了变化
console.log("数据发生了变化!" + state.myNum + "\n" );
console.dir(mutations)
});
state:变化后的数据。
mutations:事件对象、target.newValue、target.oldValue
十、组件间通信
(一)父传子、子传父(自定义事件)
父传子:
父组件:
<Person ref="sonComponent" :fatherData="{name:'路飞',age:23,text:'hhhh嘿嘿'}"></Person> <!--子组件-->
子组件:
<h2>从父组件中获得的数据:{{ fatherData.name }}</h2>
defineProps(['fatherData']);
子传父:
父组件:
结构:
<h2>从子组件获取的数据:{{ mySonData.name }}</h2>
script:
let mySonData:any = ref({});
function getSonData(value:object){
mySonData.value = value
}
===========
子组件:
结构:
<button @click="give">送数据</button>
script:
//接收到父组件传递的自定义事件,可以多个!
let emits = defineEmits(['get']);
//根据指定自定义事件执行,后面参数是传递的参数
function give(){
emits('get',{name:'儿子',age:33})
}
(二)引入外部库mitt实现数据传递
引入:npm i mitt
创建utils文件夹,创建emittev.ts并导出实例
import mitt from 'mitt';
export const bus = mitt();
//四个方法:all所有数据、emit执行事件、off解绑事件、on绑定事件。
数据获取方:
import {bus} from './utils/emittev';
let mySonData:any = ref(0);
bus.on("get",(value:any)=>{
mySonData.value = value;
})
数据提供方:
bus.emit('get',{name:'儿子',age:24})
(三)利用$attrs实现爷孙数据传递。
注明,当父组件给子组件传递数据时,子组件若不使用defineProps接收,则在子组件的$attrs中,利用此点,实现爷传孙!
传递数据时 :name="小李" :age=23 等同于 v-bind="{name:"小李" :age:23 }"
爷:
<Person v-bind="{name:'我是你爸爸的爸爸',say:'好好生活,未来会更好!'}"></Person>
儿子:
<PersonSon v-bind="$attrs"></PersonSon>
孙子:
<h2>这是从爷爷那里获取的数据:{{ name + say }}</h2>
defineProps(['name','say']);
(四)provide、inject(vue提供)
主要用于隔代数据传递,该方式不需要中间组件参与,有利于降低逻辑复杂性!
提供方结构(爷):
<div id="idContain">
<Person ></Person>
<h2>这是传递给孙子的数据:{{ giveSonData }}</h2>
<button @click="changeDataInGrd">点我改变爷爷的数据!</button>
</div>
供方js:
import {ref,provide} from "vue";
let giveSonData = ref({name:'韩少功',age:34,dream:'成为一个大作家!'})
//将数据给出去。
provide('giveDataToMySon',giveSonData);
function changeDataInGrd(){
giveSonData.value.dream = '看看我是不是响应式的!';
}
provide('changeGrdData',changeDataInGrd);
接收方结构:
<div id="personDiv">
<h2>接收到来自爷爷的数据:{{ dataFromGrd }}</h2>
<button @click="changeData">改变来自爷爷的数据</button>
</div>
接收方js:
import {ref,inject,toRefs} from 'vue'
//接收,第二个参数是默认值,当该标签数据不存在时就使用默认值
let dataFromGrd = inject('giveDataToMySon',{name:'孤胆英雄',dream:'成为一个真正自立自强的人!'});
//检验是否是响应式数据
let changeData = inject('changeGrdData',()=>{});
以上方式数据通过爷组件来修改,所以数据都是响应式的!
要想是响应式数据,要是对象,要么是一个响应对象的整体(ref,reative包裹),若是响应式对象中某个具体的值则在接收方不会是响应式!
十一、插槽的使用(跟vue无二致,不详细解释)
插槽就是当有同样的形式需要插入不同的数据,可以利用插槽,省写组件,仅用一个组件,来装入不同数据。
(一)默认插槽
首先需要声明一个通用的插槽形式:
<template>
<div id="slotDiv">
<!-- 标明默认插槽的位置 -->
<slot></slot>
</div>
</template>
<script lang="ts">
export default {
name:'SlotNews'
}
</script>
<script setup lang="ts">
</script>
<style>
</style>
引入并使用三次:
<div id="personContain">
<SlotNews>
<li v-for="(say,index) in loveSaysList" :key="index">{{ say }}</li>
</SlotNews>
<SlotNews>
<h2>{{ name }}</h2>
</SlotNews>
<SlotNews>
<h4>{{ people }}</h4>
</SlotNews>
<button @click="getNewLoveSays">获取情话</button>
</div>
//数据一
let loveSaysList:any = ref([]);
async function getNewLoveSays(){
axios.get('https://api.uomg.com/api/rand.qinghua?format=json').then(
(dataFrom)=>{
let {data:{content}} = dataFrom;
loveSaysList.value.push(content);
},
(error)=>{
console.log("出现错误!" + error)
}
)
}
//数据二
let name = ref("少年")
//数据三
let people = ref({
name:'陈大炮',
age: 23
})
默认插槽就是,你在组件标签中加入的元素都会被添加到<slot>标签的位置。
(二)命名插槽
由名知意,当组件标签中要插入多个内容,且插入位置不同时,需要多个<slot>标签来表标识,slot标签使用 name属性标识。
<div id="slotDiv">
<!-- 标明默认插槽的位置 -->
<slot name="one"></slot>
<hr>
<slot name="two"></slot>
</div>
父组件中使用v-slot或者#插槽名将元素插入指定位置(注意只能在template标签中使用)
<SlotNews>
<template #two>
<li v-for="(say,index) in loveSaysList" :key='index' >{{ say }}</li>
</template>
<template #one>
<h2>哈哈哈哈</h2>
</template>
</SlotNews>
<SlotNews>
<template #one><h2>{{ name }}</h2></template>
</SlotNews>
(三)作用域插槽
当数据在子组件中,父组件需要获取时,就是作用域插槽的用武之地。
子组件通过<slot>标签将数据传递到父组件。
<slot name="one" :trueSay="'我爱你像火焰一般炽烈'"></slot>
父组件接收:
<SlotNews>
<!--注意结束数据的格式-->
<template #one="trueSay">
<h2>{{ trueSay.trueSay }}</h2>
</template>
</SlotNews>
十二、其他API
(一)shallowRef、shallowReactive
只建立第一层数据的响应式,更深层次不具有响应式,在接收返回对象类型较大且有特殊要求时,能够提高效率!
(二)readonly shallowReadonly toRaw martRaw
给响应式数据加上只读限制,方式他人使用误修改!
let name = ref("少年");
let nameReadOnly = readonly(name);
//报错
nameReadOnly.value = "dksjl" ;
let people = ref({
name:'陈大炮',
age: 23,
lover:{
name:'枪炮',
age:45
}
});
let peopleShallowReadOnly = shallowReadonly(people);
peopleShallowReadOnly.value.lover.age = 66;
//报错,只读属性
peopleShallowReadOnly.value ={};
toRaw | 从一个响应式数据中抽取其原始数据 |
markRaw | 标记一个对象,使其永远无法成为响应式数据 |
(三)customRef(自定义响应式数据)
let name = "意气风发";
let nameRef = customRef((track,trigger)=>{
return{
get(){
track();//一旦数据被修改就会调用该函数
return name;
},
//当数据被修改时调用,传入修改后的值
set(newValue){
console.log("数据被修改了,修改后的数据是" + newValue);
name = newValue + "*";
trigger();//告诉get数据被修改了。
}
}
});
function changeName(){
nameRef.value = "无敌好吧"
}
结构:
<h2>{{ nameRef }}</h2>
<button @click="changeName">修改name</button>
(四)<teleport>
<Teleport to="body">
<!--将被包裹的元素传送到指定元素中,但是真实父子关系及数据处理逻辑不变-->
</Teleport>
对于一些特殊样式设计有很大作用,如图片使用filter属性导致fix失效,定位无法以视口为基准,这时就可以用该组件,将其传入body中进行fix定位。