Vue3新特性(学习笔记)

本文介绍了在Vue3中使用Vite构建项目时模板写法的变化,不再需要父元素包裹。Vite配置中解决@别名问题,以及Vue3的setup语法糖,包括ref和reactive的使用。文章还涉及到了Vue3的计算属性、监听器、生命周期钩子、组件间数据传输的各种方式,如props、emit、v-model、ref和Bus插件。另外,讨论了插槽的使用,包括匿名、具名、作用域和动态插槽。最后提到了Teleport、动态组件和异步组件的加载优化。

二、学习笔记

1、写法

过去写Vue项目的时候在template必须有父元素,例如:

<template>
    <div>
        首页
    </div>
</template>

那么在Vite构建的Vue3项目中,无需父元素,完全可以这样写:

<template>
    首页
</template>

2、Vite解决@问题

过去写Vue项目的时候,@代表src目录,例如引入文件:

import About from '@/views/About.vue'

那么在Vite构建的Vue3项目中需要先配置一下

打开Vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

const path = require('path');

export default defineConfig({
    plugins: [vue())],
    resolve: {
        //配置路径别名
        alias:{
            '@': path.resolve(_dirname, './src'),        
        }    
    }
})

3、Vue3语法糖

Vue3语法糖写法,setup组合式API

<template>
    <h1>{{ str }}</h1>
    <News></News>
</template>

// Vue3写法:
<script setup>
import News from '@/components/News.vue'
let str = '张小三333';
</script>

// Vue2写法:
<script type="text/javascript">
export default{
    data () {
        //定义数据
        return{
                    
        }    
    },
    methods:{
        //定义方法    
    },
    computed:{
        //计算属性
    }
}
</script>

Vue官方API说明:API 参考 | Vue.js

3.1 定义数据

1> 死数据,不可以修改之类的,但是可以展示视图层

        如let str = '1';

2> 响应式数据 : ref

        2>1 在使用的时候需要 : x.value

3>响应式数据 : reactive

        3>1 在使用的时候需要 : 不像ref一样,不需要.value

        3>2 reactive “只能” 写数组或对象,不能定义基本数据类型

        (写基本数据类型,会报黄标,不能修改数据)

3.2 数据拦截方式(Vue2与Vue3)

        Vue2.x ==> Object.defineProperty

        Vue3.x ==> new Proxy

3.3 toRefs

        解决结构化数据,无法直接修改数据的问题,如:

        let { name, age } = reactive({ name:'张三', age:20 })

        这里如果通过方法修改name或age的数值,是没法得到响应

        用toRefs解决该问题:

        let obj = reactive({ name:'张三', age:20 })

        let { name, age } = toRefs(obj);

        注意,转化成ref响应式数据,需要.value才是数值。

4、setup语法糖插件

解决import { ref, reactive ... } 引入的问题

4.1 下载安装

npm i unplugin-auto-import -D

4.2 配置vite.config.js

详细参考(一、项目搭建)的第3点

5、computed(计算)

5.1 第一种写法(相当于get方法,仅可同步)

let changeStr = computed({
        return str.value;
})

5.2 第二种写法(可同步可修改)

let changeStr = computed({
    get(){
        return str.value;
    }
    set(val){
        str.value = val;
    }
})

6、watch(监听)

6.1 监听一个

watch( str , (newVal, oldVal) => {
    console.log(newVal, oldVal);
})

6.2 (同时)监听多个

watch( [str, num], (newVal, oldVal) => {
    // ...
})

6.3 初始化监听

watch( str , (newVal, oldVal) => {
    console.log(newVal, oldVal);
},{
    immediate:true, //这里是初始化监听
    deep:true //这里是深度监听
})

6.4监听对象

let obj = reactive({
    a: 1,
    b: 2,
    m:{
        c:3
    }
})

watch(obj, (newVal) =>{
    console.log( newVal);
})

watch( ()=>obj.m, (newVal, oldVal) => {
    console.log( newVal, oldVal);
},{
    deep:true //当使用() =>对象,监听需要深度监听
})

6.5 立即监听

watchEffect(() => {
    console.log( Str.value )
})

6.6 监听路由对象

let router = useRouter();
watch(() => router.currentRoute.value, (newVal) => {
    cosole.log(newVal);
})

7、生命周期钩子

参考官网:组合式 API:生命周期钩子 | Vue.js

onBeforeMount()

在组件被挂载之前被调用。

onMounted()

在组件挂载完成后执行。

onBeforeUnmount()

在组件实例被卸载之前调用。

onUnmounted()

在组件实例被卸载之后调用。

onBeforeUpdate()

在组件即将因为响应式状态变更而更新其 DOM 树之前调用。

onUpdated()

在组件因为响应式状态变更而更新其 DOM 树之后调用。

onErrorCaptured()

在捕获了后代组件传递的错误时调用。

onRenderTracked()

当组件渲染过程中追踪到响应式依赖时调用。(Dev)

onRenderTriggered()

当响应式依赖的变更触发了组件渲染时调用。(Dev)

onActivated()

若组件实例是 缓存树的一部分,当组件被插入到 DOM 中时调用。

onDeactivated()

若组件实例是 缓存树的一部分,当组件从 DOM 中被移除时调用。

onServerPrefetch()

在组件实例在服务器上被渲染之前调用。(SSR)

8、组件间数据传输

8.1 组件——父传子

// 父组件
<template>
    <div>
        <List :msg='msg'></List>
    </div>
</template>
<script setup>
import List from '../components/List.vue'
let msg = ref('这是父的数据');
</script>


// 子组件
<template>
    <div>
        {{ msg }}
    </div>
</template>
<script setup>
import { defineProps } from 'vue'  //自动引入则忽略
defineProps({
    msg:{
        type:String,
        default:'111'  
    }
})
</script>

8.2 组件——子传父

// 子组件
<template>
    <div>
        这是子组件 ==>{{ num }}
        <button @click='changeNum'>按钮</button>
    </div>
</template>
<script setup>
import { defineEmits } from 'vue'  //自动引入则忽略
let num = ref(100);
const emit = defineEmits(['fn'])
const changeNum = () => {
    emit('fn', num);
} 
</script>


// 父组件
<template>
    <div>
        <List @fn='changeHome'></List>
    </div>
</template>
<script setup>
import List from '../components/List.vue'
const changeHome = (n) => {
    console.log(n.value)
}
</script>

8.3 组件 双向绑定v-model

// 父组件
<template>
    <div>
        <List v-model:msg='msg'></List>
    </div>
</template>
<script setup>
import List from '../components/List.vue'
let msg = ref('这是父的数据');
</script>


// 子组件
<template>
    <div>
        {{ msg }}
        <button @click='btn'></button>
    </div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'  //自动引入则忽略
const props = defineProps({
    msg:{
        type:String,
        default:'111'  
    }
const emit = defineEmits(['update:msg'])//update这里是写死的
const btn = () =>{
    emit('update:msg','这是子改的数据')
}
})
</script>

8.4 子传父(通过ref绑定)

1> 子组件通过defineExpose,将属性或方法暴露出去

2> 父组件可以通过绑定组件ref的方式,获取属性或方法

// 父组件
<template>
    <div>
        <List ref="list"></List>
        <button @click='btn'>按钮</button>
    </div>
</template>
<script setup>
import List from '../components/List.vue'
let list = ref();

const btn = () =>{
    console.log(list.value.a);
    console.log(list.value.b);
}
</script>


// 子组件
<template>

</template>
<script setup>

let a = 'a';
let b = ref('b');
defineExpose({
    a,
    b
})

</script>

8.5 兄弟组件之间传值

1> 子传父,父再传子,这是一种方式

2> 可以使用类似Bus的mitt插件,直接子传子

        npm install mitt -S

3> 配置plugins/Bus.js

import mitt from 'mitt';
const emitter = mitt();
export default emitter;

4> 使用例子

// 子组件A
<template>
    <div>
        <button @click='btn'>按钮</button>
    </div>
</template>
<script setup>
import emitter from '../plugins/Bus.js'
let str = ref('这是A组件的数据')
const btn = () =>{
    emitter.emit('fn', str)
}
</script>


// 子组件B
<template>
    <div>
        {{ msg }}
    </div>
</template>
<script setup>
import emitter from '../plugins/Bus.js'
let msg = ref();
onBeforeMount(() => {
    emitter.on('fn',e=>{
        msg.value = e.value;    
    })
})    
</script>

9、插槽

9.1 匿名插槽

父组件中,有一个List的子组件
<List>这是内容<List>

子组件中插入插槽才能显示内容
<slot>这是内容</slot>

9.2 具名插槽

父组件中,有一个List的子组件
<List>
    <template v-slot:A>
        AAAA
    </template>
    <template v-slot:B>
        BBBB
    </template>
<List>

子组件中,需要分别展示内容:
<slot name='A'></slot>    //展示AAAA
<slot name='B'></slot>    //展示BBBB
注意:v-slot:A可以简写成#A

9.3 作用域插槽

父组件
<template v-slot='{data}'>
    {{ data }}  ==>  {{ data.name }}
</template>

子组件
<div v-for='item in list' :key='item.id'>
    <slot :data='item'></slot>
</div>
<script setup>
let list = ref([
{id: 1, name: '张三', age: 18},
{id: 2, name: '李四', age: 19},
{id: 3, name: '赵五', age: 17}
])
注意:v-slot='{data}'可以简写成#default='{data}'
</script>

9.4 动态插槽

通过变量去定义插槽的name
父组件中,有一个List的子组件
<List>
    <template #[str]>
        AAAA
    </template>
<List>
<script setup>
let str = ref('A');
</script>


子组件中,需要分别展示内容:
<slot name='A'></slot>    //展示AAAA

10、Teleport传送

**Vue3新增的内容

***必须传送到有这个dom的内容中,需要注意顺序

父组件
<template>
    <div class='main'>
        这是main
    </div>
    <div id='container'>
        这是container
    </div>
</template>
<script setup>
import A from '../components/A.vue'
</script>


A组件
<template>
    <h1>A组件</h1>
    <teleport to='body'>
        <div>这是传送内容</div>
    </teleport>
</template>

to='body'    //传送到body主体中
to='.main'    //传送到main中,因为是class需要增加.
to='#container'    //传送到container中,因为是id

11、动态组件

动态加载多个组件,减少维护成本

<template>
    <ul>
        <li
            v-for='(item,index) in tabList'
            :key='index'
            @click='change(index)'
        >
            {{ item.name }}
        </li>
    </ul>
    <keep-alive>
        <component :is="currentComponent.com"></component>
    </keep-alive>
</template>

<script setup>
import A from '../components/A.vue'
import B from '../components/B.vue'
import C from '../components/C.vue'
let tabList = reactive([
    {name:'这是Atab', com:marKraw(A)},
    {name:'这是Btab', com:marKraw(B)},
    {name:'这是Ctab', com:marKraw(C)}
])
let currentComponent = reactive({
    com:tabList[0].com
})
const change = (idx) =>{
    currentComponent.com = tabList[idx].com
}
</script>
PS1:组件没必要作为响应数据,因此marKraw(A)
PS2:currentComponent.com=切换的组件

12、异步组件

***提升性能,如分步加载、js分包

12.1 如分步加载

实现分步加载,还需要添加一个插件vueuser

安装 npm install @vueuse/core -S

<template>
    <A></A>
    <B></B>
    <div ref='target'>
        <C v-if='targetIsVisible'></C>
    </div>
    
</template>

<script setup>
import { defineAsyncComponent } from 'vue' //自动导入忽略
import { useIntersectionObserver} from '@vueuse/core'
import A from '../components/A.vue'
import B from '../components/B.vue'

const C = defineAsyncComponent(() =>
    import C from '../components/C.vue'
)
const target = ref(null);  //绑定div块
const targetIsVisible = ref(false);
const { stop } = useIntersectionObserver(
    target,
    ([{ isIntersecting }], observerElement) =>{
        targetIsVisible.value = isIntersecting
    }
)
</script>
//observerElement可以不用

12.2 异步加载(分包加载)

可以结合插槽的形式,达成分步加载,组件的包会后置加载

<template>
    <A></A>
    <B></B>
    <Suspense>
        <template #default>
            <C></C>
        </template>
        <template #fallback>
            加载中...
        </template>
    </Suspense>
</template>

<script setup>a
import { defineAsyncComponent } from 'vue' //自动导入忽略
import A from '../components/A.vue'
import B from '../components/B.vue'

const C = defineAsyncComponent(() =>
    import C from '../components/C.vue'
)

)
</script>

13、Mixin 混入

作用:用于分发Vue组件中的可复用功能

在mixin.js中
import { ref } from 'vue'
export default function(){
    let num = ref(1);
    let fav = ref(false);
    const favBtn = () ={
        num.value++;
        fav.value = true;
        setTimeout(() =>{
            fav.value = false;
        },2000)    
    }
    return {
        num, fav, favBtn
    }
}
在A组件中
<template>
    <div>
        {{ num }}
        <button @click='favBtn'>
        {{ fav ? '收藏中...' : '收藏'}}
        </button>
    </div>
</template>
<script setup>
import mixin from '../mixins/mixin.js'
let {num, fav, favBtn} = mixin();
</script>

在B组件中(同样写法)
但运行的时候,A组件与B组件的num等值是独立的。

14、Provide/Inject 依赖注入

作用,用于将父数据直接传递给孙子(数据可改变)

父组件:
<template>
    <A></A>
</template>
<script setup>
import { provide } from 'vue'    //自动导入忽略
import A from '../components/A.vue'
let num = ref(100);
provide('changeNum', num);
</script>

子组件:
<template>
    
</template>
<script setup>
import { inject } from 'vue'    //自动导入忽略
const aNum = inject('changeNum')

</script>

15、路由

5.1 官网

API 文档 | Vue Router

5.2 路由跳转

跳转至about

注意:tag属性被取消了

5.3 路由跳转

跳转

let router = useRouter();

const goAbout = () =>{

router.push('/about');

}

5.4 导航守卫

1> 全局路由守卫

        全局前置守卫,路由跳转前触发

        beforeEach(to, from, next)

        全局解析守卫,在所有组件内守卫和异步路由组件被解析之后触发

        beforeResolve(to, from, next)

        全局后置守卫,路由跳转完成后触发

        afterEach(to, from)

2> 路由独享守卫

        路由对象单个路由配置,单个路由进入前触发

        beforeEnter(to, from, next)

3> 组件路由守卫

        beforeRouteEnter(to, from, next)

        beforeRouteUpdate(to, from, next)

        beforeRouteLeave(to, from, next)

4>整体触发顺序

        当点击切换路由时:beforeRouterLeave-->beforeEach-->beforeEnter-->beforeRouteEnter

        -->beforeResolve-->afterEach-->beforeCreate-->created-->beforeMount-->mounted

        -->beforeRouteEnter的next的回调

        当路由更新时:beforeRouteUpdate

5>写法

        beforeEnter:(to, from, next) => {

                console.log('进入前触发');

        }

16、Pinia 状态管理

16.1 Vuex和Pinia的区别

参考网址:[Vuex] Vuex 5 by kiaking · Pull Request #271 · vuejs/rfcs · GitHub

1. pinia没有mutations,只有:state、getters、actions

2. pinia分模块不需要modules(直接分js文件即可)

3. pinia体积更小(性能更好)

4. pinia可以直接修改数据

16.2 pinia配置

官网网址:Pinia | The intuitive store for Vue.js

1> 安装

npm install pinia

2> main.js配置

import { createPinia } from 'pinia'
app.use(createPinia())

3> 根目录新建store/index.js中写入

import { defineStore } from 'pinia'

export const useStore = defineStore('storeID',{
    state: () => {
        return{
            counter: 0,        
        }    
    },
    getters:{},
    actions:{}
})

16.3 pinia使用

<template>
    {{ name }}
    {{ num }}
    {{ changeNum }}
    <button @click='btn'></button>
</template>
<script setup>
import { useStroe } from '../store'
import { storeToRefs } from 'pinia'
const store = useStore();
let { name, num, changeNum } = storeToRefs(store);//套上即可修改数据
const changeName = () ={
    name.value = 'XX';
    num.value = 12;
    //或使用批量更新函数
    store.$patch(state=>{
        state.name = 'XX';
        state.num++;            
    })
}
const btn = () ={
    store.upNum( 200 );
}

</script>
import { defineStore } from 'pinia'

export const useStore = defineStore('storeID',{
    state: () => {
        return{
            num: 0,
            name: '张三'       
        }    
    },
    getters:{
        changeNum(){
            return this.num + 1000        
        }    
    },
    actions:{//支持同步异步操作
        upNum(val){
            this.num += val;
        }    
    }
})

16.4 持久化存储

1> 安装依赖

npm i pinia-plugin-persist --save

2> 配置

可以针对main.js改变维护store的结构

import { createApp } from 'vue'

import App from './App.vue'
import router from './router'

import store from './stores/index.js'

const app = createApp(App)

app.use(store)
app.use(router)

app.mount('#app')

然后在store目录下新建一个index.js

import { createPinia } from 'pinia'
//引入pinia的持久化存储插件
import piniaPluginPersist  from 'pinia-plugin-persist'

const store = createPinia();
//使用持久化存储插件
store.use(piniaPluginPersist)

export default store

3> 使用

在需要的store中指定模块的js文件进行配置

import { defineStore } from 'pinia'

export const useStore = defineStore('storeID',{
    state: () => {
        return{
            num: 0,
            name: '张三'       
        }    
    },
    getters:{
        changeNum(){
            return this.num + 1000        
        }    
    },
    actions:{//支持同步异步操作
        upNum(val){
            this.num += val;
        }    
    },
    // 开启数据缓存
    persist: {
        enabled: true,
        strategies: [{
            key: 'my_user',    //存储的key
            storage: localStorage    //存储的位置,默认session
        }]
    }
})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值