一、基础准备工作
(一)过程
- 环境要求:有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 scriptlet 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,testTwodefineProps(['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对象,并获取对应querylet 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:{//该参数是调用时传入滴,注意使用普通函数,因要调用thiscutMyNumFun(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接触到myNumstore中的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定位。