Vue3(学自尚硅谷)

一、基础准备工作

(一)过程

  1. 环境要求:有node.js环境、npm。
  2. 执行命令:
  3. npm create vue@latest

    而后选择:

  4. ✔ 请输入项目名称: … me_vue3
    ✔ 是否使用 TypeScript 语法? … 否 / 是
    ✔ 是否启用 JSX 支持? … 否 / 是
    ✔ 是否引入 Vue Router 进行单页面应用开发? … 否 / 是
    ✔ 是否引入 Pinia 用于状态管理? … 否 / 是
    ✔ 是否引入 Vitest 用于单元测试? … 否 / 是
    ✔ 是否要引入一款端到端(End to End)测试工具? › 不需要
    ✔ 是否引入 ESLint 用于代码质量检测? … 否 / 是
    

    只有TS选择是即可。

  5. 最后项目创建成功!
  6. 进入后根据提示安装两个插件,并执行命令:
    npm install

(二)文件结构

  1. .vscode文件中:extensions.json是官方给vscode做的插件信息("Vue.volar", "Vue.vscode-typescript-vue-plugin"),进入项目后会自动提示进行安装。
  2. node_modules中都是项目依赖的包。
  3. public中是公共资源,favicon.ico是网页的页签图标。
  4. src是项目的核心:assests是放静态资源的文件夹,里面是一些css样式等静态资源;components中是很多组件;App.vue是根组件,每个组件都挂载载App组件上;main.js中主要将根组件挂载。
  5. .gitgnore是git中一些忽略文件,里面出现的声明在推拉过程中会被忽略。
  6. env.d.ts中指定了很多文件类型(如jpg等),如果没有此文件,则ts不会认识jpg等格式的文件。
  7. index.html 程序的入口页面,里面有Id号为App的Div。
  8. package-lock.json package.json 一个关于webpack的配置信息。
  9. README.md是一些关于该项目的说明,文本文件。
  10. tsconfig.app.json ts tsconfig.json tsconfig.json tsconfig.node.json的配置文件。
  11. 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返回类型
  1. 返回对象类型,中间属性是要被渲染的属性。
  2. 返回方法,该方法是渲染函数,返回值会覆盖整个模版。
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定位。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/793851.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

干货| 这篇电商数据分析案例一定要看!

主流电商商品数据采集API接口 直播带货行业在经历了高端玩家的“春秋争霸”之后&#xff0c;逐渐进入到了一种“网红化”的阶段。人们正在将注意力从原来凤毛麟角的直播巨头逐渐转移到一些小主播身上。但近短时间却出现了网红带货营销额放缓的现象。因此商家必须要调整直播策略…

【数据库】数据库的介绍、分类、作用和特点,AI人工智能数据如何存储

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《数据库》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识…

互联网轻量级框架整合之JavaEE基础II

编写本篇代码并实际执行之前请仔细阅读前一篇互联网轻量级框架整合之JavaEE基础I Servlet 在Servlet容器中&#xff0c;Servlet是最基础的组件&#xff0c;也可以把JSP当做Servlet&#xff0c;JSP的存在意义只在于方便编写动态页面&#xff0c;使Java语言能和HTML相互结合&…

产品推荐 | 中科亿海微推出亿迅®A8000金融FPGA加速卡

01、产品概述 亿迅A8000金融加速卡&#xff0c;是中科亿海微联合金融证券领域的战略合作伙伴北京睿智融科&#xff0c;将可编程逻辑芯片与金融行业深度结合&#xff0c;通过可编程逻辑芯片对交易行情加速解码&#xff0c;实现低至纳秒级的解码引擎&#xff0c;端到端的处理时延…

Linux gcc day3

find命令&#xff08;importance&#xff09;&#xff1a; 语法&#xff1a;find pathname -options find /root -name test.c which命令&#xff1a; which [指令] 只搜索指令&#xff0c;在什么位置下 为什么文件夹带有颜色呢&#xff1f; 科普补充alias命令&#xff1a; ali…

C++:赋值运算符(17)

赋值也就是将后面的值赋值给变量&#xff0c;这里最常用的就是 &#xff0c;a1那么a就是1&#xff0c;此外还包含以下的赋值运算 等于int a 1; a10 a10加等于int a 1; a1;a2-减等于int a 1; a-1;a0*乘等于int a 2; a*5;a10/除等于int a 10; a/2;a5%模等于int a 10; a%…

kafka集群介绍+部署Filebeat+Kafka+ELK

一、消息队列 1、为什么需要消息队列&#xff08;MQ&#xff09; 主要原因是由于在高并发环境下&#xff0c;同步请求来不及处理&#xff0c;请求往往会发生阻塞。比如大量的请求并发访问数据库&#xff0c;导致行锁表锁&#xff0c;最后请求线程会堆积过多&#xff0c;从而触…

Mac电脑清理垃圾软件 Mac电脑清理垃圾的文件在哪 cleanMyMac X 4.8.0激活号码

Mac用户经常会有这样一些烦恼&#xff0c;比如软件之间的管理&#xff0c;应用生成的缓冲文件怎样删除&#xff0c;还有软件的卸载等等... 如何有效清理Mac中的垃圾文件&#xff0c;删除多余的软件成为Mac用户迫切的需求。本文就为大家介绍几款好用的Mac电脑清理垃圾软件&#…

在线考试|基于Springboot的在线考试管理系统设计与实现(源码+数据库+文档)

在线考试管理系统目录 目录 基于Springboot的在线考试管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、前台&#xff1a; 2、后台 管理员功能 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主…

Redis中的Sentinel(二)

Sentinel 初始化Sentinel状态。 在应用了Sentinel的专用代码之后&#xff0c;接下来&#xff0c;服务器会初始化一个sentinel.c/sentinelState结构(简称Sentinel状态),这个结构 保存了服务器中所有和Sentinel功能有关的状态(服务器的一般状态仍然由redis.h/redisServer保存);…

SaaS 电商设计 (十) 记一次 5000kw 商品数据ES迁移 (详细的集群搭建以及线上灰度过程设计)

目录 一.背景二.技术目标三.技术方案3.1 整体流程3.2 ES 切换前:完成整体新集群的搭建.i:拓扑结构设计ii: 如何选择整体的 **ES** 集群配置. 3.3 **ES** 版本切换中3.3.1 多client版本兼容3.3.2 Router的设计 3.4 ES 切换后3.5 开箱即用3.5.1 开箱使用 demo 演示3.5.2 使用过程…

团体程序设计天梯赛-练习集 01

天梯赛题解合集 团体程序设计天梯赛-练习集 (L1-001 - L1-012) 团体程序设计天梯赛-练习集 (L1-013 - L1-024) 团体程序设计天梯赛-练习集 (L1-025 - L1-036) 团体程序设计天梯赛-练习集 (L1-037 - L1-048) L1-001 Hello World 输出题 样例 输入 输出 Hello World!思…

图像处理ASIC设计方法 笔记13 图像旋转ASIC的输入输出电路

文章目录 1 DPRAM:双端口 RAM2 IDT Integrated Device Technology, Inc. 公司介绍3 IDT70T633S10DDI4 TMS320C64145 旋转ASIC的输入输出框图图像旋转ASIC的输入输出电路案例用到的芯片相关介绍如下。 1 DPRAM:双端口 RAM DPRAM 的特点是可以通过两个端口同时访问,具有两套完全…

数据结构入门系列-栈的结构及栈的实现

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 栈 栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一段进行插入和删除元素操作&#xff0c;进行数据输入和删除操作的一端称为栈顶&#xff0c;另…

InternLM2-Chat-1.8B 模型测试

在interStudio进行InternLM2-Chat-1.8B模型访问&#xff0c;进入开发机后 配置基础环境 新建conda环境并且进入 conda create -n demo python3.10 -y conda activate demo 下载pytorch等相关包 conda install pytorch2.0.1 torchvision0.15.2 torchaudio2.0.2 pytorch-cuda11.…

ChernoCPP 2

视频链接&#xff1a;【62】【Cherno C】【中字】C的线程_哔哩哔哩_bilibili 参考文章&#xff1a;TheChernoCppTutorial_the cherno-CSDN博客 Cherno的C教学视频笔记&#xff08;已完结&#xff09; - 知乎 (zhihu.com) C 的线程 #include<iostream> #include<th…

四、MySQL读写分离之MyCAT

一、读写分离概述 1、什么是读写分离&#xff1a; 读写分离&#xff1a;就是将读写操作分发到不同的服务器&#xff0c;读操作分发到对应的服务器 &#xff08;slave&#xff09;&#xff0c;写操作分发到对应的服务器&#xff08;master&#xff09; ① M-S (主从) 架构下&…

Java设计模式:外观模式之优雅门面(九)

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 在软件工程中&#xff0c;设计模式是解决常见设计问题的经验总结&#xff0c;它为开发者提供了一种通用的、可复用的解决方案。外…

书生浦语训练营2期-第二节课笔记作业

目录 一、前置准备 1.1 电脑操作系统&#xff1a;windows 11 1.2 前置服务安装&#xff08;避免访问127.0.0.1被拒绝&#xff09; 1.2.1 iis安装并重启 1.2.2 openssh安装 1.2.3 openssh服务更改为自动模式 1.2.4 书生浦语平台 ssh配置 1.3 补充&#xff08;前置服务ok…

Thread的基本用法

目录 正文&#xff1a; 1.线程创建 2.线程休眠 3.获取线程实例 4.线程中断 5.线程等待join() 总结&#xff1a; 正文&#xff1a; 1.线程创建 线程创建是多线程编程的第一步&#xff0c;它涉及到创建一个可以并行执行的新线程。在Java中&#xff0c;有几种不同的方法可…