vue3使用

0 阅读11分钟

vue是渐进式框架

  • 使用方式渐进:从CDN引入写简单交互,到CLI创建完整项目,再到Nuxt做SSR,每一步都是可选的。
  • 功能模块渐进:核心库只负责视图层,需要路由加Vue Router,需要状态管理加Pinia,不强求一次性配齐。
  • 学习曲线渐进:新手只需要会HTML/JS就能上手,随着项目复杂度提升,再逐步学习进阶特性。

Vue采用自动追踪的方式。它通过Proxy(Vue3)或Object.defineProperty(Vue2)拦截数据的读取和修改,在读取时收集依赖(当前正在运行的函数),在修改时通知所有依赖更新。这种方式的优点是精确——只有真正依赖这个数据的组件才会更新,而且开发者可以直接修改数据,不需要额外操作。

React则采用显式触发的方式。它没有自动追踪,而是通过setState手动触发更新。一旦setState调用,整个组件函数会重新执行,生成新的虚拟DOM,然后通过Diff算法找出变化的部分更新真实DOM。这种方式的优点是简单直观——数据变了就重新渲染,但缺点是需要开发者手动优化(memo/useMemo)避免不必要的渲染。

<script>
    export default {
        name: 'PP',
        // setup函数中的this是undefined,vue3中已经弱化this了,里边变量方法必须返回
        // 执行时机  早于beforeCreated()
        // setup返回对象,也可直接返回函数,页面直接渲染返回的内容
        // setup 和 data和method关系
        // setup()能和data\method同时存在
        // data和methods可以读取setup()中数据this.name,setup先执行,setup里读不到data里数据
        setup() {
            let name = ref('lili');
            let age = ref(18);
            function changeName {
                name.value = 'alice';
            }
            return {
                name,
                age,
                changeName,
            }
            // return () => 'hahhahahah' // 这个组件直接渲染hahahahah
        }
    }
</script>
// setup函数语法糖
// 设置组件名,可与setup语法糖同时存在
<script>
    export default {
        name: 'PP',
    }
</script>
// 上边不想再写个script单独设置组件名字,可以借助一个插件
// vite-plugin-vue-setup-extend  安装后在vite.config.ts中配置插件,即可name="person-123"
<script setup lang="ts" name="person-123">
    let name = 'lili';
    let age = 18;

    function fn() {}
</script>

ref和reactive

vue2中,数据写在data(){return {}}中就是响应式的,原理defineProperty劫持。
vue3响应式 数据实现响应式使
基本类型 + 对象类型 使用ref(初始值) let name = ref('ddd') name.value 需要.value取值
对象类型 let obj = reactive(初始值) 直接访问;嵌套深层的对象,建议用reactive,也可用ref
reactive定义后,不能直接再赋值整个对象。

let car = reactive({brand: 'bwp', price: 200});
// 错误
car = {brand: 'benci', price:300} // 错误,失去响应式,页面不更新
car = reactive({brand: 'aodi', price:300}) // 错误,原先的对象失去响应式,页面不更新
// 正确
Object.assign(car, {brand: 'aodi', price:300}) // 正确,页面更新,没有更新person的地址

// 如下可以,正确
const obj = ref({a: 123});
obj.value = {a: 567}; // 一个新对象赋值,obj的地址变了

toRefs和toRef

let person = reactive({name: 'll', age:18}); //将响应式对象所有属性都变成响应式
let { name, age } = toRefs(person);
console.log(name, age);
let n = toRef(person, 'name');一个一个解构成响应式

image.png

computed vue3的

计算属性有缓存

// 这么定义的计算属性不能修改
let fullName = computed(() => {
    return firstName.value + lastName.value;
})

// 这么定义的,可读可写
let fullName = computed({
    get() {
        return firstName.value + lastName.value;
    },
    // 赋值时调用
    set(newVal) {
        
    }
})

image.png

watch

监听数据变化,Vue3只能监听4种数据

  • ref定义的数据。
let sum = ref(0);
const addSum = () => {
  sum.value += 1;
};
// 解除监听
// 监听【ref】定义的【基本类型】
const stopWatch = watch(sum, (newVal, oldVal) => {
  console.log(newVal, oldVal);

  if (oldVal > 10) {
    stopWatch(); // 调用该函数解除监听
  }
});
// 监视【ref】定义的【对象类型】数据,监视的是对象的地址值,
// 若想监听对象内部属性发生的变化,需要【手动开启深度监听】
/** 监视ref定影的对象类型数据,监视的是对象的地址值,
    若想监听对象内部属性发生的变化,需要手动开启深度监听
    watch第一个参数:被监视的数据;
    第二个参数:监视的回调 
    第三个:配置的对象deep、immediate等
    */
let person = ref({ name: 'lisi', age: 18 });
watch(
  person,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  { deep: true, immediate: true }
  // deep开启,监听内部属性,
  // immediate值表示立即执行一次,数据未变化时就执行一次
);
  • reactive定义的数据
// 监视【reactive】定义的对象,默认开启深度监听,不用手动开启,不能关闭
let person = reactive({ name: 'lisi', age: 18 });
watch(
  person,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  { immediate: true }
  // 此时deep默认开启,可监听内部属性
  // immediate值表示立即执行一次,数据未变化时就执行一次
);
  • 函数返回的一个值 -》getter函数(能返回一个值的函数)。 监视ref或reactive定义的对象类型中的某个属性(属性为基本类型的或者对象类型,属性为对象的也可以直接监视这个属性,建议写成函数式
let person = reactive({
    name: 'lisi',
    car: {c1: 'yadi', c2: 'baoma'}
})
watch(() => person.name, () => {}, {})
// 下面情况能监听到car中单个属性的变化,但是car整体赋值监听不到,car = {c1; 'rr', c2: 'ee'}
watch(person.car, () => {}, {})
// 下面情况能监听到car整体赋值,不加deep参数,car的单个属性变化监听不到,所以要加deep参数
// 函数的写法,要深度监听,写deep参数。即地址上想监听内部属性变化,需加deep参数
watch(() => person.car /** 该函数返回car的地址 */, () => {}, {deep: true})
  • 上述组成的数组
let person = reactive({
    name: 'lisi',
    age: 18,
    car: {c1: 'yadi', c2: 'baoma'}
})

watch([() => person.name, () => person.car], () => {}, {deep: true})

watchEffect 副作用

watch必须明确指出监视谁。 watchEffect不用写监视谁,直接回调,回调中用哪些属性到就监视哪些

let height = ref(0);
let width = ref(0);
// 会立即调用回调函数,响应式追踪变化
watchEffect(() => {
    if (heigth.value > 10 || width.value > 5) {
        console.log('超过标准了');
    }
})

ref容器

<h2 ref='title'>nihao</h2>

let title = ref(); // title.value就是拿到h2这个Dom元素【普通标签】

<Person ref='personRef'></Person>

let personRef = ref(null);
personNull.value 就是person组件实例,可以拿到该组件defineExpose的东西【组件】

ts规范

// 接口,用于限制person对象的具体属性
// src/types/index.ts
export interface PersonInterface {
    name: string;
    age: number;
}
// 一个自定义类型
export type Persons = Array<PersonInterface>
// export type Persons = PersonInterface[] // 或者这种写法


// src/components/Person.vue
import {type PersonInterface, type Persons} from '@/types'

let person:PersonInterface = {age: 19, name: 'lisi'};
let personList2 = reactive<Persons>([]);
let personList: Persons = [];
let personList1: Array<PersonInterface> = [];

组件生命周期

v-if 创建销毁组件 v-show 隐藏使用display:none 元素还在
生命周期函数,生命周期钩子
vue2的生命周期 创建:created(创建前beforeCreate,创建完毕created)
挂载:mounted(挂载前beforeMount,挂载完毕-组件显示在页面上mounted)
更新:updated(更新前beforeUpdate,更新完毕 updated)
销毁:destroyed(销毁前beforeDestory,销毁完毕destroyed)

vue3的生命周期
创建:setup()替代了,模拟创建前和创建完
挂载:onBeforeMount(() => {}) onMounted(() => {})
更新:onBeforeUpdate(() => {}) onUpdated(() => {})
卸载:onBeforeUnmount(() => {}) onUnmounted(() => {})

父子生命周期顺序:
子挂载完--》父挂载完 父组件是最后挂载完的

hooks

本质是一个返回值的函数。 使用时引入,可解构获取hook中暴露的数据

// 将逻辑抽离出来,放到一个ts或js文件中
// 里边可以使用生命周期函数、或者computed、watch等vue中的东西
// src/hooks/sumHook.ts
import { ref } from 'vue'
export default function() {
    let sum = ref('')
    let add = () => {
        sum.value += 1;
    }
    
    return {
        sum,
        add
    }
}

// 引用处
import useSum from '@/hooks/sumHook.ts'
let { sum, add } = useSum();

路由router

import { RouterView, RouterLink} from 'vue-router'
 
<RouterView></RouterView> // 加载的路由组件显示区域占位

// 路由跳转组件
<RouterLink to='/home' active-class='actived-class'></RouterLink>
<RouterLink :to={path: '/home'} active-class='actived-class'></RouterLink>
<RouterLink :to={name: '/zhuye'} active-class='actived-class'></RouterLink>

路由组件:靠路由规则渲染出来的。一般写在pages或view文件夹下
routes: [{ path: '/home', component: Home, name='zhuye' }]
路由切换时,视觉消失的路由组件,是被卸载了
一般组件:手动写标签,一般写在components下 <person></person>

路由工作模式
history模式
优点:URL更美观,不带#,更接近传统网站的URL。 缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404错误,可在nginx等服务器上配置

vue2: mode: 'history'  
vue3: history: createWebHistory()  
const router = createRouter({
     history: createWebHistory(),
     routes: [],
})

hash模式
优点:兼容性更好,因为不需要服务器处理路径
缺点:url上带#不美观,且在SEO优化方面相对较差

vue2: mode: 'hash'  
vue3: history: createWebHashHistory() 
const router = createRouter({
     history: createWebHashHistory(),
     routes: [],
})

路由参数

import { useRoute, useRouter } from 'vue-router';
let route = useRoute();
// route.query
<RouterLink :to={path: '/zhuye', query: {id: xxx, title: xxx}} active-class='actived-class'></RouterLink>
<RouterLink :to=`/news/detail?id=${id}&title=${title}` active-class='actived-class'></RouterLink>
// /news/detail?id=119&title=万万没想到 // id=119&title=万万没想到 query参数

// parmas传参 to中路由必须写name,不能是path;且params中不能传对象和数组
<RouterLink :to={name: '/zhuye', params: {id: xx, title: xx}} active-class='actived-class'></RouterLink>
<RouterLink :to=`/new/detail/${id}/${title}` active-class='actived-class'></RouterLink>
// route.params    路由处占位: /news/detail/:id/:title

路由的props

routes: [{ 
    path: 'news',
    component: News,
    name='zhuye',
    children: [
        {
            name: 'xiang',
            path: 'detail/:id/:title',
            component: Detail,
            // 第一种写法:将路由收到的所有【params参数】作为props传给路由组件
            // <Detail id=xx title=xx />
            // props: true, 
            
            // 第二种写法:函数写法,可以自己决定将什么作为props传给路由组件
            //props(route){ // 参数为route路由信息
            //    return route.query
            //}
            
            // 第三种写法:对象写法,可以自己决定将什么作为props传给路由组件
            //props: { // 这种写法传固定值
            //    a: 100
            //    b: 200
            //}
        }
    ]
}] 

路由的replace属性

// replace替换,不能回退到上一个访问的路由 ;不加默认是push,可以回到上一个访问的路由
<RouterLink replace :to=`/new/detail/${id}/${title}` active-class='actived-class'></RouterLink>

编程式路由导航

import { useRouter } from 'vue-router';
const router = useRouter();

router.push('/news');
router.replace('/news');

vuex与pinia 集中式状态(数据)管理

多个组件共享数据

import { defineStore } from 'pinia';
// 选项式
export const useCountStore = defineStore('count', {
    state() {
        return {
            sum: 6,
            school: 'cc',
            address: 'ww'
        }
    },
    // actions中放置的一个一个的方法,用于响应组件中的动作
    actions: {
            increment(value) {
                console.log('ii调用了', value);
            }
    }

});

// setup写法 组合式
export const useCountStore = defineStore('count', () => {
    // state
    let sum = ref(6),
    let school = ref('cc'),
    let address = ref('ww')

    // actions
    const increment = (value) => {
       console.log('ii调用了', value);
    }
    
    return {
        sum,
        school,
        address,
        increment,
    }
});
import { useCountStore } from '@/store/count';
const countStore = useCountStore();
// 拿到store中数据
// countStore 是Proxy包裹的对象,里面的ref会自动解包,不用再.value
console.log(countStore.sum)
// 第一种修改方法
countStore.sum = 9;
// // 第一种修改方法, 批量变更 store
countStore.$patch({
    sum: 8,
    school: 'dd'
});
// 第三种修改方法,调用store的actions中定义的修改方法
countStore.increment('+++');

// import { storeToRefs } from 'pinia';
// storeToRefs 只会关注store中的数据,不会对方法进行ref包裹

const { sum, scheool } = storeToRefs(useCountStore());

组件间通信

  • props,emit 父子组件
  • mitt 引入mitt,订阅取消订阅;事件总线
  • v-model 此通信方式在UI组件库大量使用双向绑定
<input type='text' v-model="username"> 等价于下边  
<input type='text' :value="username" @input="username = (<HTMLInputElement>$event.target).value">  
<my-input v-model="username">
<my-input :modelValue="username" @update:modelValue="username = $event">
<input type='text' :value="username" @input="username = (<HTMLInputElement>$event.target).value">  

defineProps(['modelValue])
  • $attrs 用在模版中,子组件用这个获取副组件传过来的未使用props接收的其他所有属性 然后子组件可以使用v-bind=attrs将其未显示接收的参数传给他的子组件,及父传孙子组件vbind=key:value,....===>vbind=attrs将其未显示接收的参数传给他的子组件,及父传孙子组件 `v-bind={key: value, ....}` ===> `v-bind=attrs`
    用在js上时
<script setup>
import { useAttrs } from 'vue' 
const attrs = useAttrs() 
</script>
// 或
export default { 
    setup(props, ctx) { // 透传 attribute 被暴露为 ctx.attrs 
        console.log(ctx.attrs) 
    }
}
  • $ref $parents $ref 父组件获取所有的子组件;父-》子 子组件使用ref <child ref='child1Ref'/> $parents 子组件中获取到父组件 子-》父
    注意点: 一个响应式对象中的属性是ref()定义,读取时不用再.value,底层会自动获取数据
  • provide/reject 嵌套较深的组件间 祖先-子孙 project('moneyContext', {money, updateMoney}); 父 let {money, updateMoney} = reject('moneyContext', {}) // 可以给个默认值,孙子组件可以使用updateMoney通信给父组件

插槽
默认插槽
<slot>默认内容</slot> ==> <slot name='default'>默认内容</slot> 插槽没用到就显示默认内容
具名插槽

<slot name='header'></slot>

<template v-slot:header><div>menu</div></template>  
<Category v-slot:header><div>menu</div></Category>

作用域插槽 v-slot="params"
数据在子那边,但根据数据生成的结构,却由父决定,即需要用到zi的数据

// 子组件的数据可以绑定到slot上,传给父组件使用
<slot name='header' :youxi=games :a='123'></slot>
// 使用
<template v-slot:header><div>menu</div></template>  
<Category v-slot="params"><div>{{params.youxi}}</div></Category> // 默认插槽
<Category v-slot:header="{youxi}"><div>{{params.youxi}}</div></Category> // 解构 header插槽
v-slot:header="{youxi}" ===》 #header={youxi}

shallowRef与shallowReactive 用法和ref和reactive一样,只是监听的顶层属性

两者用来绕开深度响应,避免每个内部属性都做响应式带来的性能成本,使得属性访问更快,可提升性能。

  • shallowRef:浅层ref 只关注引用层的变化,不关心内部属性的变化; 只监听.value这层的改变,如果是对象,car.value.a,这个监听不到
  • shallowReactive:对象的顶层属性是响应式的,但嵌套属性不是。

readonly及shallowReadonly

readonly所有层都只读

let sum1 = ref(0);
let sum2 = readonly(sum1); // sum2关联了sum1为只读,但sum1变化时,sum2也会变化,sum1自己维护,sum2给别人使用,防止改坏了

shallowReadonly只限制第一层为只读,可以修改第二层数据

toRaw与markRaw

let person = ref({name: 'ii', age: 18});
let p2 = toRaw(person); // 变成了普通对象,无响应式了,用在作为参数传给非vue库去做处理,如lodash库的函数处理数据

let c = {a: 99, b:0};
let c1 = reactive(c); // 响应式
// markRaw 标记一个对象,使其永远不能成为响应式
let car = markRaw({b: ''qq', c: 22});

customRef

自定义ref

let initValue = '你好‘;
// track跟踪, trigger触发
let msg = customRef((track, trigger) => {
    // 读取
    get() {
        track(); // 告诉vue数据msg很重要,你要对msg进行持续关注,一旦msg变化就去更新
        reutrn initValue;
    },
    // 修改
    set(value) {
        initValue = value;
        trigger(); // 通知vue一下数据msg变化了
    }
})

Teleport 传送

将结构传送到body下,里面的元素就能插入到body元素标签下
<Teleport to='body'>
    <div>你好</div>
</Teleport>

<Teleport to='.m-box'>
    <div>你好</div>
</Teleport>