vue3的备战
虚拟dom树对比
虚拟
1通讯方式
0 props :
在父组件定义的响应式数据,通过在组件上写上属性进行传递
parent:
<child a="date">
child:
//子组件需要以这样的形式进行接收父组件传递的数据,同时可以接收多个数据,他返回一个对象,里面包含所有传来的数据,仍然是响应式的
使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露
注意注意,如果你使用的是传统的comp api使用的是setup函数,需要这么写
在setup函数情况下,会传入两个参数,props里面有你传入的所有数据
context上下文包括arrts,父组件给子组件但是没有接受,slots插槽,emit触发自定义事件,expose函数
export default {
props: ["temp"],
setup(props, context) {
console.log(props.temp, context.attrs);
return {};
},
};
而使用setup语法糖的时候需要这么写,默认宏函数,不需要导入
let x=defineProps(['a'])
两个例子 进行对比
父亲
<childrenVue :date="arr" :change="change"></childrenVue>
<children :date="arr" :change="change"></children>
let arr = ref(["a", "b", "c", "d", "e"]);
function change(val) {
arr.value.push(val);
}
setup语法糖方式
let pro = defineProps(["date", "change"]);
let { date, change } = pro;
setup函数形式
export default {
props: ["date", "change"],
setup(props, ctx) {
let { date, change } = props;
return { date, change };
},
};
1:v-model形式
对于v-model的传递形式用在父子 子父都可以
用在自定义组件时
例如
parent
<fuzujian v-model='name'>
child
首先需要声明接收
defineProps(['modelValue'])
const emit=defineEmits([update:modelValue])
触发的时候需要写
emit('update:modelValue',date)
注意,在这种情况下,modelvalue无法改变,如果在父元素 v-model:qw 这样写,则后面的modelValue可以更替
2:props形式
父传子:
//父亲
<child :obj="obj"></child>
//子组件
let b= defineProps(["obj"]); 父亲通过props的形式传递给了子组件数据,子组件需要使用defineProps来接收
子传父:
需要父亲提前准备一个方法
父亲:
<child :name='a'></child>
function a(val){
console.log(val)
}
子:
<button @click='test'>
let s=defineProps(['name']) //注意,这里接收的名字应当为:name而非='a'里面的名字
function test(){
s.name(val)
}
自定义事件:
\\通过给子组件添加自定义事件来实现传参 只能实现子串夫
子传父亲:
父亲:
<child @test="test1"></child> //@test:时间名 test1 触发时调用哪个函数
function test1(val){console.log(val)}
子组件:
const emit=defineEmits(["test"]);
可以在适当的时候调用emit('test')参数作为emit的第二个参数传入
onMounted(()=>{
emit('test',666)
})
setup函数写法
在函数写法中,调用自定义事件需要使用context中的emit方法
export default {
props: ["temp", "test"],
setup(props, context) {
let s = ref("s");
function chi(x) {
props.test(s);
context.emit("test2", "val");
}
return {
chi,
};
},
};
两个例子
父组件:
<childrenVue :date="arr" :change="change" @addFirst="addFirst"></childrenVue>
<children :date="arr" :change="change" @addFirst="addFirst"></children>
function addFirst(val) {
arr.value.unshift(val);
}
setup语法糖
<button @click="add(0)">头加一2个</button>
let emit = defineEmits(["addFirst"]);
function add(val) {
emit("addFirst", val);
}
setup函数
<button @click="add(0)">头加一个</button>
emits: ["addFirst"],
function add(val) {
ctx.emit("addFirst", val);
}
mitt 任意组件的传递
与vue2的 $bus一样
消息订阅与发布:
npm i mitt
新建utills文件
xx.ts
import mitt from 'mitt'
export const emitter=mitt()
main : import xx.ts
基础语法
import mitt from "mitt";
const mitter = mitt()
// 绑定事件
mitter.on('a1', (val) => {
console.log('a1')
})
//触发事件
mitter.emit('a1')
//解绑
mitter.off('a1', () => {
console.log('a1解绑')
})
export default mitter
只需要在相应的组件导入使用即可
父亲:
import mitter from "../utills/mit";
<button @click="mit">点我触发mit的子串夫</button>
mitter.on("all", (val) => {
console.log("all", val); //用于绑定一个事件all,当子组件触发all时,可以实现子组件的数据传递到父组件中来
});
function mit(val) {
mitter.emit("allchild", obj); //用于触发allchild事件,通过父组件点击触发事件,将父组件的数据传递过去
}
子组件:
<button @click="act">传递给父亲</button>
import mitter from "../utills/mit";
mitter.on("allchild", (val) => {
console.log("allchild", val); //用于绑定allchild事件,通过父组件点击触发事件,将父组件的数据传递过去
});
function act() {
fn.have(a.value);
emit("test", a.value);
mitter.emit("all", a.value); //用来触发all事件,当子组件点击时,触发all事件,并且将数据传递给父组件
}
$attrs 祖孙组件通讯
$attrs 其实就是props传递的另一种形式,借助子组件传递给孙子,当然也可以传递方法,让孙组件将数据传递过来
父亲:
<child :aaa="aaa" :bbb="bbb" :ccc="ccc" :test='test'></child>
function test(val){
console.log(val)
}
子组件:
<sun :attr="$attrs"></sun> //子组件一个都不接受,直接传递给孙组件
孙组件:
let attr = defineProps(["attr"]);
let active=defineProps(['test'])
onMounted(() => {
console.log("@@@@",attr.attr);
active.test('val')
});//孙组件拿到数据
$refs
$parent
$refs 用于父传子,$parent 用于子传父
refs类似于ref拿到子组件的实例
父组件:
<child1 ref='r1'>
<child2 ref='r2'>
<button @click='test($refs)'>
let r1=ref()
let r2=ref()
function test($refs){
console.log($refs) //拿到子组件希望父组件看到的数据
//c1:Proxy(Object) {child1: RefImpl, , __v_skip: true}
//c2:Proxy(Object) {child2a: RefImpl, __v_skip: true}
}
子组件1
let child1 = ref("child1");
let child1a=ref('child1a')
defineExpose({ child1 }); //使用此方法需要子组件进行对外暴露自身的数据,采用difineExprose的方法传入对象的形式
子组件2
let child2a = ref("child2");
defineExpose({ child2a });
$parent:
在子组件的方法中传入¥parent !!不允许写错 可以拿到父组件向外暴露的数据
子组件2
<button @click="haveParent($parent)">拿到父组件的数据</button>
let child2a = ref("child2");
defineExpose({ child2a });
function haveParent($parent) {
console.log($parent); //Proxy(Object) {aaa: RefImpl, __v_skip: true}
}
父组件:
let aaa=ref(123)
defineExprose({aaa})
provide与inject 实现爷->孙传递
provide 向后代(注意,后代不仅仅是儿子和孙子,向下传递)提供数据,inject,孙子接收爷爷提供的数据
爷爷组件:
let sundate = ref(4);
provide("sundate", sundate); //provide('name',date)
注意,这里不可以写成sundate.value,否则会丢失响应式 //否则会是 sundate 4
function updateSundate() {
sundate.value += 1;
}
孙子组件:
let sundate = inject("sundate"); //inject('name')
onMounted(() => {
console.log('sundate',sundate)
//RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 4, _value: 4}
});
如果需要传递多个值或者方法,则需要写成对象形式
爷爷:
provide("sundate", { sundate, updateSundate }); //第二个参数需要传递成对象的形式,名字唯一就可以
孙子:
let sundate = inject("sundate"); //当然可用结构出来
onMounted(() => {
console.log('sundate',sundate) //接收的时候仍然需要保存到变量中去
sundate.updateSundate()
console.log(sundate.sundate)
//sundate {sundate: RefImpl, updateSundate: ƒ}
//sun.vue:13 RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 5, _value: 5}
});
生命周期:
创建:setup() --->beforecreate created
挂在前:onBeforeMount(()=>{})
挂载:onMounted( async()=>{ await...})
更新:onBeforeUpdate(()=>{}) onUpdated(()=>{})
卸载{销毁} onBeforeUnMount(()=>{}) onUnMounted(()=>{})
//子组件先挂在,然后父组件在挂载,深度优先遍历 app最后挂载
当你修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:
路由:
vue3的路由区别于vue2的路由
新建 router:文件夹
路由创建与暴露
import {createRouter,createWebHistory,createWebHashHistory} from 'vue-router'
const router=createRouter({
history:createWebHistory() //工作模式 createWebHashHistory() 哈希模式
routes:[
//路由
{
path:''
component:.vue
name:
}
]
})
export default router //对外暴露
main:
//进行挂在
createApp(App).use(router).mount('#app')
active-calss只能用在routerlink标签上
放在哪:
<RouterView>//展示用的
<routerLink :to=`路径\name?a=${date}&b=${date}` active-class='激活样式'> //标签跳转 针对link组件,里面内置了一个active-class的属性,用来方便设置激活时候的样式,动态路由传参
<routerLink :to={path\name:'',query:{参数}} active-class='激活样式'>
params参数:
需要在路径里面进行占位
在路由组件中 path:'/news/:id/:text'
这样在<routerLink :to=`/news/id=${1}/text=${2}' active-class='激活样式`> 1 2为参数
而在对象方法中
<routerLink :to={ name:xxx,params:{date}} active-class='激活样式`> 使用对象的写法时,只能使用name作为导航的地址
接受参数
const route=useRoute()
从响应式对象身上结构属性会丢失响应式,记得上torefs
//let {query}=route 会丢失响应式,需要包裹上torefs
let {query}=toRefs(route)
//使用parmas的时候
let {params}=toRefs(route)
··· 路由规则的props方法
当使用params方法时候,
{
path:'/news/:id/:text'
component:.vue
name:
props:true //开启props方法,之后在子组件里通过difeinprops来接收参数
或者 props(route){
return { //通过这样的形式来传递query的参数
route.query
}
}
}
那么在后续接受参数时
使用defineProps(['id','text']) 通过这样的形式将传递的参数形成一个单独的prop,可以直接在模板使用
replace:
写在<routerLink replace> //无法浏览器后退
编程式路由导航
import { useRouter} from 'vue-router'
const router=useRouter()
router.push('/path') // router.replace('/path)
router.push({
path:'/path',
query:{[参数]}
})
重定向:
{
path:'/',
redirect
}
路由守卫
beforeEach
aftereach
entereach
pinia
main:
import {createPinia} from 'pinia'
const pinia=createPinia()
app.use(pinia)
创建store文件夹
xxx.ts:
import {defineStore} from 'pinia'
export const usexxxStore=defineStore('xxx',{
state(){
return{
a:1
}
},
actions:{
function a(){
this.a=this.a+1
}
},
getters:{
big(state){
return state.a*10
}
}
})
//组合式
export const usexxxStore=defineStore('xxx',()=>{
let a=ref(1)
//方法
//actions:
function a2(){
this.a=this.a+1
},
//getters:
let b=computed(()=>{
return ???
})
return{
a,
a2,
b
}
})
vue中读取
import { usexxxStore} from ''
const store= usexxxStore()
const store2=storeToRefs(store) //保持数据!!!的响应式,store的方法不会包裹响应式
let {a,big}=store2
pinia的订阅
$subscribe
直接对store仓库使用 类似于watch
store.$subscribe((mutate,state)=>{
mutate 本次修改的信息
state 数据
localStorage.setItem('val', JSON.stringify(state.shuju))
})
1 toRef与toRefs
toRef 与toRefs
toRef是针对单个数据做响应式处理,而toRefs是针对全部做响应式处理,对于结构时,一定要注意响应式是否丢失
例子:
let obj=ref({
a:1,
b:2
})
let {a}=obj //此时响应式就已经丢失了,你拿到的只是里面的一个值
toRef: let {a}=toRef(obj,'a')
toRefs: let {a}=toRefs(obj)
2组合式api
两张图对比:
对于rective定义的属性,无法整体替换
例如 let obj=rective(a:1,b:2)
let obj={a:2,b:3}
不可这样写,需要 Object,assgin(obj,{??})这样
computed在vue3中变成了组合式api
计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算
计算属性正常来说是只读的,如果需要修改,需要写成get和set 的方式
let a=computed(()=>{
计算属性方法具有缓存,而methods里的函数没有缓存,且只读不能改,如果需要改的话则需要写成
computed({
get()
set()
})
return ?? 应该写成这样的形式
})
watch监视的是
1一个函数返回一个值(就是写成[()=>{return??},()=>{return}]的形式)
2一个ref
3一个响应式对象
或者由以上类型组成的数组
watch ,监视ref定义的基本类型和对象类型,如果需要监视对象内部的属性,需要手动开启深度监视
注意,监视的时候不需要写value,否则则无法正常监视
如果使用reactive定义的话,那么不可以直接对对象进行整体赋值,(如需要请使用object.assig n)可以对某个具体的属性进行修改 ,并且reactive定义的响应数据默认就是开启深度监视的,地址没有变,所以新旧值是相同的
let op=watch(()=>{return??},(new,old)=>{
//watch的用法,监视的数据要注意有时需要用函数返回的形式来写
op() //达到条件可用关闭监视
})
当监视reactive定义的具体属性时
watch([()=>{return??},()=>{return}],(new,old)=>{
//当要监视多个属性时需要这样
//当修改的是对象属性里面的某一个值的时候,如果监视的是整个对象,那么新旧value是一样的
如果修改的是整个对象,那么新值是新值,旧是旧的
})
watch(rective对象,(new,old)=>{
//当监视的是rective的对象时,需要开启deep深度监视
},{
deep:ture
immediate:true 进来先执行一下
})
watchEffect //区别于watch,他不需要写监视谁,在里面用到的数据他默认会给你监视
watchEffect(()=>{
里面用到的数据都自动进行了监视
})
watch vs. watchEffect
watch 和 watchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
部分变更方法
Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换一个数组
变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变 (immutable) 方法,例如 filter(),concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的:
js
// `items` 是一个数组的 ref
items.value = items.value.filter((item) => item.message.match(/Foo/))
你可能认为这将导致 Vue 丢弃现有的 DOM 并重新渲染整个列表——幸运的是,情况并非如此。Vue 实现了一些巧妙的方法来最大化对 DOM 元素的重用,因此用另一个包含部分重叠对象的数组来做替换,仍会是一种非常高效的操作。
v-model 还可以用于各种不同类型的输入,<textarea>、<select> 元素。它会根据所使用的元素自动使用对应的 DOM 属性和事件组合:
文本类型的 <input> 和 <textarea> 元素会绑定 value property 并侦听 input 事件;
注意在 <textarea> 中是不支持插值表达式的。请使用 v-model 来替代: <textarea v-model="text"></textarea>
<input type="checkbox"> 和 <input type="radio"> 会绑定 checked property 并侦听 change 事件;
<select> 会绑定 value property 并侦听 change 事件
<!-- `picked` 在被选择时是字符串 "a" -->
<input type="radio" v-model="picked" value="a" />
<!-- `toggle` 只会为 true 或 false -->
<input type="checkbox" v-model="toggle" />
<!-- `selected` 在第一项被选中时为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
部分指令与修饰符
v-if vs. v-show
v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
v-if与v-for问题
当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行。请查看列表渲染指南获取更多细节。而在vue2,v-for的优先级高于v-if
key的问题
Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
默认模式是高效的,但只适用于列表渲染输出的结果不依赖子组件状态或者临时 DOM 状态 (例如表单输入值) 的情况。
为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute
在处理事件时调用 event.preventDefault() 或 event.stopPropagation() 是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理 DOM 事件的细节会更好。
为解决这一问题,Vue 为 v-on 提供了事件修饰符。修饰符是用 . 表示的指令后缀,包含以下这些:
.stop
.prevent
.self
.capture
.once
.passive
template
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
使用修饰符时需要注意调用顺序,因为相关代码是以相同的顺序生成的。因此使用 @click.prevent.self 会阻止元素及其子元素的所有点击事件的默认行为,而 @click.self.prevent 则只会阻止对元素本身的点击事件的默认行为。
.capture、.once 和 .passive 修饰符与原生 addEventListener 事件相对应:
template
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
.passive 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。
TIP
请勿同时使用 .passive 和 .prevent,因为 .passive 已经向浏览器表明了你不想阻止事件的默认行为。如果你这么做了,则 .prevent 会被忽略,并且浏览器会抛出警告。
按键修饰符
在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在 v-on 或 @ 监听按键事件时添加按键修饰符。
template
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
你可以直接使用 KeyboardEvent.key 暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。
template
<input @keyup.page-down="onPageDown" />
在上面的例子中,仅会在 $event.key 为 'PageDown' 时调用事件处理。
按键别名
Vue 为一些常用的按键提供了别名:
.enter
.tab
.delete (捕获“Delete”和“Backspace”两个按键)
.esc
.space
.up
.down
.left
.right
系统按键修饰符
你可以使用以下系统按键修饰符来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发。
.ctrl
.alt
.shift
.meta
注意
在 Mac 键盘上,meta 是 Command 键 (⌘)。在 Windows 键盘上,meta 键是 Windows 键 (⊞)。在 Sun 微机系统键盘上,meta 是钻石键 (◆)。在某些键盘上,特别是 MIT 和 Lisp 机器的键盘及其后代版本的键盘,如 Knight 键盘,space-cadet 键盘,meta 都被标记为“META”。在 Symbolics 键盘上,meta 也被标识为“META”或“Meta”。
举例来说:
template
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
TIP
请注意,系统按键修饰符和常规按键不同。与 keyup 事件一起使用时,该按键必须在事件发出时处于按下状态。换句话说,keyup.ctrl 只会在你仍然按住 ctrl 但松开了另一个键时被触发。若你单独松开 ctrl 键将不会触发。
.exact 修饰符
.exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。
template
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>
鼠标按键修饰符
.left
.right
.middle
这些修饰符将处理程序限定为由特定鼠标按键触发的事件。
饰符
.lazy
默认情况下,v-model 会在每次 input 事件后更新数据 (IME 拼字阶段的状态例外)。你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据:
template
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
.number
如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入:
template
<input v-model.number="age" />
如果该值无法被 parseFloat() 处理,那么将返回原始值。
number 修饰符会在输入框有 type="number" 时自动启用。
.trim
如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符:
template
<input v-model.trim="msg" />
3 ref属性
在vue3中
在标签上写ref属性可以拿到dom操作
<button ref="a">
let a=ref()
那么这个a就是哪个标签
当ref写在组件上时,拿到的就是组件实例
例如
<BUTTON ref='btn'>
let btn=ref()
此时,就拿到了这个组件的实例,当拿到组件实例的时候可以看到组件里面的数据,当使用setup的时候
我们需要在子组件用defineExpose进行暴露出去,这样才能看到
如果没有使用setup语法糖的时候,则不需要这么做
例
child :
<template><h1>这里是stu1</h1></template>
<script lang="ts">
export default {};
</script>
<script lang="ts" setup>
import { ref } from "vue";
let ss1 = ref("asdasd");
defineExpose({ ss1 });
parent:
<child ref='ch'>
<script lang="ts" setup>
let ch=ref()
插槽:
默认插槽
默认插槽需要在子组件使用双标签的形式
parent:
<chacao>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</chacao>
<chacao>
<img src="https://img0.baidu.com/it/u=2565809048,1400176152&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" alt="">
</chacao>
父组件使用了两个一样的插槽,但里面的内容是不同的,展示到页面上的效果也不同
子组件:
<h1>这里是插槽</h1> 而子组件只需要写上slot标签告诉父组件插槽的内容往哪里展示即可
<slot></slot>
具名插槽
当我们要向插槽里面添加的样式和结构不是单一的结构时,我们就需要使用具名插槽,来告诉具体的结构展示在具体的哪个位置
parent:
<template>
<chacao>
<template v-slot:s1>
//<template #s1> 具名插槽的简写形式
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</template>
<template v-slot:s2>
// <template #s2> 简写形式
<h1>这里是具名插槽s2</h1>
</template>
</chacao>
<chacao>
<template v-slot:s1>
//<template #s1> 简写形式
<img
src="https://img0.baidu.com/it/u=2565809048,1400176152&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"
alt=""
/>
</template>
</chacao>
</template>
子组件:
<template>
<h1>这里是插槽</h1>
<slot name="s1"></slot>
<slot name="s2"></slot>
</template>
// 可用看到,子组件写了两个插槽,并且命名为s1和s2
//在父组件中,具体的结构使用了template进行包裹,并且在template上使用了v-slot:name 指令
//注意 v-solt只能写在组件标签或者template标签上,语法为v-slot:插槽名字
//这样,插槽就和结构一一对应起来
作用域插槽:
作用域插槽用于结构由父组件决定,但是数据在子组件身上,从而形成的尴尬局面,当然具名插槽可以和作用域插槽一起使用
例如: <template #s1="data">
父亲:
<chacao>
<template #="{youxi}"> //这样的形式进行解构拿到data的数据
<ul>
<li v-for="(item, index) in youxi" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
</chacao>
子组件
<h1>这里是作用域插槽</h1>
<slot :youxi="game"></slot> //直接在slot标签上传递数据,可以传递多个数据,slot会将所有的数据打包成一个对象传递给父组件
let game = reactive([
{ id: 1, name: "benghuai" },
{id: 2,name: "LOL",},
{id: 3, name: "yuanshen" },
{id: 4,name: "王者",},
]);
效果:
//benghuai
//LOL
//yuanshen
//王者
具名插槽与作用域插槽的联合使用
子组件:
<slot name="sl1" :arr="game"></slot> //将game传给组件的使用者
let game = reactive([
{ id: 1, name: "benghuai" },
{id: 2,name: "LOL",},
{id: 3, name: "yuanshen" },
{id: 4,name: "王者",},
]);
父组件
<soltss>
<template #sl2> <h1>asdas</h1> </template>
<template #sl1="{ arr }"> //作用域插槽与具名插槽的结合形式 #==v-solt: 这个指令 #sl1="{ arr }"其实就是
v-solt:sl1='{arr}'
<h2>asdas</h2>
<h2 v-for="item in arr" :key="item.id">
{{ item.name }}
</h2>
</template>
</soltss>
一些面试题
面试题1:为什么vue3中去掉了vue构造函数?
vue2的全局构造函数带来了诸多问题:
1. 调用构造函数的静态方法会对所有vue应用生效,不利于隔离不同应用
2. vue2的构造函数集成了太多功能,不利于tree shaking,vue3把这些功能使用普通函数导出,能够充分利用tree shaking优化打包体积
3. vue2没有把组件实例和vue应用两个概念区分开,在vue2中,通过new Vue创建的对象,既是一个vue应用,同时又是一个特殊的vue组件。vue3中,把两个概念区别开来,通过createApp创建的对象,是一个vue应用,它内部提供的方法是针对整个应用的,而不再是一个特殊的组件。
面试题2:谈谈你对vue3数据响应式的理解
vue3不再使用Object.defineProperty的方式定义完成数据响应式,而是使用Proxy。
除了Proxy本身效率比Object.defineProperty更高之外,由于不必递归遍历所有属性,而是直接得到一个Proxy。所以在vue3中,对数据的访问是动态的,当访问某个属性的时候,再动态的获取和设置,这就极大的提升了在组件初始阶段的效率。
同时,由于Proxy可以监控到成员的新增和删除,因此,在vue3中新增成员、删除成员、索引访问等均可以触发重新渲染,而这些在vue2中是难以做到的。