vue3基础知识

1,227 阅读9分钟

vue3的备战

虚拟dom树对比

image.png虚拟 image.png

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.tsimport {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

两张图对比: composition api.jpg

option api.jpeg



对于rective定义的属性,无法整体替换
例如 let obj=rective(a:1,b:2let 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 是钻石键 (◆)。在某些键盘上,特别是 MITLisp 机器的键盘及其后代版本的键盘,如 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 时,即使同时按下 AltShift 也会触发 -->
<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中是难以做到的。