• 做项目必读的vue3基础知识


    1.响应式

    1.1 两者实现原理

    • vue2 利用es5的 Object.defineProperty() 对数据进行劫持结合发布订阅模式来实现
    • vue3 利用es6的 proxy 对数据代理,通过 reactive() 函数给每一个对象都包一层 proxy,通过 proxy 监听属性的变化,从而实现对数据的监控

    1.2 vue2响应式缺陷

    缺陷

    对象新增、删除属性没有响应式数组新增、删除元素没有响应式;通过下标修改某个元素没有响应式;通过.length改变数组长度没有响应式。只有实例创建时 data 中有的数据实例创建后才是响应式的,给已创建好的 vue 实例 data 对象中添加属性时,数据虽然会更新,但视图不会更新,不具有响应式

    解决

    • 使用 this.$forceUpdate() 强制更新视图和数据(不推荐)
    • 使用具有响应式的函数来操作对象:
    [Vue | this].$set(object,key,value),实例中添加响应式属性;
    [Vue | this].$delete(object,key),实例中删除属性;
    Object.assign(),将多个对象属性合并到目标对象中,具有响应式;
    Object.freeze(),将对象冻结,防止任何改变。使得对象成为只读,无法添加、删除或更新;
    Object.keys(),返回对象的所有属性;
    Object.values(),返回对象的所有属性值;
    Object.entries(),返回对象的所有键值对;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 使用具有响应式的函数来操作数组:
    pop(),尾部删除元素;
    push(),尾部添加元素;
    unshift(),首部添加元素;
    shift(),首部删除元素;
    sort(),排序;
    reverse(),翻转;
    splice(index[必填,位置],howmany[必填,要删除的数量],item1...itemx[可选,向数组中添加的新元素])
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    【补充】清空对象,置空数组操作

    • 清空对象
    this.form = {}
    this.$refs.form.resetFields()
    this.form.name = ""
    
    • 1
    • 2
    • 3
    • 置空数组
    this.arrayList = [] 
    this.arrayList.splice(0,this.arrayList.length)
    // this.arrayList.length = 0  不具有响应式,无法实现
    
    • 1
    • 2
    • 3

    1.3 vue3响应式优势

    • proxy性能整体上优于Object.defineProperty
    • vue3支持更多数据类型的劫持(vue2只支持Object、Array;vue3支持Object、Array、Map、WeakMap、Set、WeakSet)
    • vue3支持更多时机来进行依赖收集和触发通知(vue2只在get时进行依赖收集,vue3在get/has/iterate时进行依赖收集;vue2只在set时触发通知,vue3在set/add/delete/clear时触发通知),所以vue2中的响应式缺陷vue3可以实现
    • vue3做到了“精准数据”的数据劫持(vue2会把整个data进行递归数据劫持,而vue3只有在用到某个对象时,才进行数据劫持,所以响应式更快并且占内存更小)
    • vue3的依赖收集器更容易维护(vue3监听和操作的是原生数组;vue2是通过重写的方法实现对数组的监控)

    2.生命周期

    vue2vue3说明
    beforeCreatesetup()组件创建之前,执行初始化任务
    createdsetup()组件创建完成,访问数据、获取接口数据
    beforeMountonBeforeMount组件挂载之前
    mountedonMounted组件挂载完成,DOM已创建,访问数据或DOM元素,访问子组件
    beforeUpdateonBeforeUpdate未更新,获取更新前所有状态
    updatedonUpdated已更新,获取更新后所有状态
    beforeDestroyonBeforeUnmount组件销毁之前,清空定时器,取消订阅消息
    destroyedonUnmounted组件销毁之后
    activatedonActivatedkeep-alive包含,组件被激活时
    deactivatedonDeactivatedkeep-alive包含,发生组件切换,组件消失时

    2.1 初始化

    • vue2 一般在 created、mounted 中初始化
    • vue3 可以直接在 setup 中,或者放在 onBeforeMount、onMounted 中初始化
    <script setup>
    const getList = () => {}
    
    getList()
    
    onMounted(() => {
      getList()
    }),
    
    onBeforeMount(() => {
      getList()
    }),
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.2 解除绑定

    • vue2 中操作:
    <script>
    export default {
      mounted() {
        // 开启定时器
        let timer = setInterval(() => {
          console.log('---定时器在触发---')
        }, 1000)
       
       //这下面的代码不可以省略
        this.$on('hook:activated', () => {
          if (timer === null) { // 避免重复开启定时器
            timer = setInterval(() => {
              console.log('setInterval')
            }, 1000)
          }
        })
    
        this.$on('hook:deactivated', () => {
          clearInterval(timer)
          timer = null
        })
      }
    }
    <script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • vue3 中操作:
    <script setup>
    import { onBeforeUnmount, onDeactivated } from 'vue'
    
    // 组件卸载前,对应 Vue2 的 beforeDestroy
    onBeforeUnmount(() => {
        clearTimeout(timer)
        window.removeAddEventListener('...')
    })
    
    // 退出缓存组件,对应 Vue2 的 deactivated
    onDeactivated(() => {
        clearTimeout(timer)
        window.removeAddEventListener('...')
    })
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.this指向

    • vue2中可以调用this来指向当前实例,this上挂载了路由、状态管理、公共的组件、方法等可以访问、使用
    • 通过上面的生命周期可以看出来,vue3中setup()在解析其他组件选项(data、methods、computed 等都没解析)之前调用,在beforeCreate()之前执行,所以this指向undefined,vue3中不能通过this进行访问
    • vue3想要执行类似vue2调用this的用法可以进行如下操作:
    <script setup>
    import { getCurrentInstance } from "vue";
    
    // proxy 为当前组件实例;global 为全局组件实例
    const { proxy, appContext } = getCurrentInstance();
    const global = appContext.config.globalProperties;
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.变量

    4.1 ref

    • ref 定义基本类型生成 RefImpl 实例;定义复合类型生成 Proxy 实例
    • template 渲染直接使用,js中修改通过 .value 调用
    const count = ref(0)
    const user = ref({
        name:'falcon',
        age:20
    })
    
    const addCount = () => count.value++
    const addAge = () => user.value.age++
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.2 reactive

    • reactive 只能定义对象类型的数据,生成 Proxy 实例
    • template、js中可以直接调用
    • shallowReactive 生成非递归响应数据,只监听第一层数据的变化
    const stu = reactive({
        name:'falcon',
        major:'Chinese',
        score:80
    })
    
    const addScore = () => stu.score++
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.3 转化响应式

    • toRef(),单个转化为响应式
    • toRefs(),多个转化为响应式
    • unref(),是 val = isRef(val) ? val.value : val 的语法糖;如果参数是一个ref就返回其 value,否则返回参数本身

    【注】针对一个响应式对象(reactive封装)的prop(属性)创建一个ref,且保持响应式

    const stu = reactive({
        name:'falcon',
        age:20,
        major:'Chinese',
        score:80
    })
    const age = toRef(stu,'age')
    const {name,major,score} = toRefs(stu)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.4 只读

    • readonly,创建只读对象(递归只读)
    • isReadonly,判断是否是readonly对象
    • shallowReadonly,只对最外层响应式只读,深层次不转换
    let status = readonly(true);
    const changeStatus = () => (status = !status);
    
    let info = reactive({
      username: "falcon",
      password: "123456",
      role: {
        roleId: 123,
        roleName: "系统管理员",
      },
    });
    info = shallowReadonly(info);
    const changeRole = () => {
      info.role.roleId++;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.Fragment

    • vue2 中只能有一个根节点,因为vdom是一颗单根树,patch方法在遍历的时候从根节点开始,所以要求template只有一个根元素
    • vue3 中可以有多个根节点,因为如果template不只有一个根元素时,就会添加一个fragment组件将多个根组件包起来
    <template>
      <div>demo1 text</div>
      <h2>h2 text</h2>
      <p>p text</p>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.Teleport

    • teleport 瞬移组件,能将我们的元素移动到DOM中vue app之外的其他位置(有时用于页面需要弹框且弹框不影响布局的情况,相对于body进行定位)
    <template>
      <div class="app-container">
        <el-button type="primary" @click="showToast">打开弹框</el-button>
      </div>
      <teleport to="body">
        <div v-if="visible" class="modal_class">
          A man who has not climbed the granted wall is not a true man
          <el-button
            style="width: 50%; margin-top: 20px"
            type="primary"
            @click="closeToast"
            >关闭弹框</el-button
          >
        </div>
      </teleport>
    </template>
    
    <script setup>
    import { ref } from "vue";
    
    const visible = ref(false);
    const showToast = () => {
      visible.value = true;
    };
    const closeToast = () => {
      visible.value = false;
    };
    </script>
    
    <style scoped>
    .modal_class {
      position: absolute;
      width: 300px;
      height: 200px;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      border: 1px solid #ccc;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-content: center;
      padding: 30px;
    }
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    7.Suspense

    • suspense 允许程序在等待一些异步组件时加载一些后备内容
    • 在异步加载请求网络数据时,使用suspense组件,可以很好的实现loading效果
    • #default 初始化模板组件;#fallback 异步请求中处理的ui
    <template>
      <div class="app-container">
        <Suspense>
          <template #default>
            <SyncApi />
          template>
          <template #fallback>
            <h3 style="color: blue">数据加载中...h3>
          template>
        Suspense>
      div>
    template>
    
    <script setup>
    import SyncApi from "./SyncApi.vue";
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    // SyncApi 组件内容
    <template>
      <div v-for="(people, index) in peoples.results" :key="index">
        {{ people.name }} {{ people.birth_year }}
      div>
    template>
    
    <script setup>
    const peoples = ref({
      results: [],
    });
    const headers = { "Content-Type": "application/json" };
    const fetchPeoples = await fetch("https://swapi.dev/api/people", {
      headers,
    });
    peoples.value = await fetchPeoples.json();
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    8.组件

    • vue2 中组件引入后需在components中注册后才能使用
    • vue3 中组件引入后直接使用无需注册
    <template>
      <Table />
    </template>
    
    <script setup>
    import Table from "@/components/Table";
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    9.获取DOM

    <template>
      <el-form ref="formRef">el-form>
    template>
    
    <script setup>
    // 1. 变量名和 DOM 上的 ref 属性必须同名,自动形成绑定
    const formRef = ref(null)
    console.log(formRef.value)
    
    // 2. 通过当前组件实例来获取DOM元素
    const { proxy } = getCurrentInstance()
    proxy.$refs.formRef.validate((valid) => { ... })
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    10.watch、watchEffect

    10.1 watch

    // vue2 中用法
    watch:{
        // 第一种
        flag(newValue,oldValue){},
        
        // 第二种
        user:{
            handler(newValue,oldValue){},
            immediate:true,
            deep:true
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    // vue3 中用法
    <script setup>
    const count = ref(0)
    const status = ref(false)
    
    // 监听一个
    watch(count,(newValue,oldValue) => {})
    // 监听多个
    watch([count,status],([newCount,oldCount],[newStatus,oldStatus]) => {})
    
    const user = reactive({
        name:'falcon',
        age:20,
        sex:'female',
        hobbies:[]
    })
    
    // 监听一个
    watch(() => user.age,(newValue,oldValue) => {})
    // 监听多个
    watch([() => user.name,() => user.sex],(newValue,oldValue) => {})
    
    // 添加配置参数
    watch(() => user.hobbies,(newValue,oldValue)=> {},{
        immediate:true,
        deep:true,
        // 回调函数的执行时机,默认在组件更新之前执行,更新之后执行参数为‘post’
        flush:'pre'
    })
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    10.2 watchEffect

    // 正常情况组件销毁自动停止监听
    watchEffect(() => {})
    
    // 异步方式手动停止监听
    const stopWatch = watch(() => user.hobbies,(newValue,oldValue)=>{},{deep:true})
    setTimeout(() => {
        stopWatch()
    },3000)
    
    const stopWatchEffect = watchEffect(() => {})
    setTimeout(() => {
        stopWatchEffect()
    },3000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    10.3 两者区别

    • watch 对传入的一个或多个值进行监听,触发时会返回新值和旧值,且默认第一次不会执行
    • watchEffect 是传入一个立即执行函数,默认第一次会执行,且不需要传入监听的内容,会自动收集函数内的数据源作为依赖,当依赖发生变化时会重新执行函数(类似computed),并且不会返回旧值
    • 正常情况下,组件销毁/卸载后这两种方式都会停止监听,但是异步方式例如setTimeout里创建的监听需要手动停止

    11.computed

    • 默认只改变数据的值,如果想改变后的值具有响应性,利用其set()方法
    • vue3.x中移除过滤器filter,建议使用computed
    • 回调函数必须return,结果是计算结果
    • 计算属性依赖的数据项发生变化时,重新计算,具有缓存性
    • 不能执行异步操作
    const names = reactive({
        firstName:'',
        lastName:'',
        fullName:''
    })
    
    // 通过此种方式定义的fullName,想要修改的时候后台警告:Write operation failed: computed value is readonly;想要修改fullName,通过set()方法
    const fullName = computed(() => {
      return names.firstName + " " + names.lastName;
    });
    
    const fullName = computed({
        get(){
            return names.firstName + " " + names.lastName
        },
        set(value){
            
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    12.可组合函数

    • vue3 中组合式api使用hooks代替 vue2 中的mixin。hooks 约定用驼峰命名法,并以“use”作为开头;将可复用的功能抽离为外部js文件;引用时将定义的属性和方法响应式地解构暴露出来;避免了vue2的碎片化,实现低内聚高耦合
    • 传统mixin的缺陷:
      • mixin可能会引起命名冲突和重复代码,后期很难维护
      • mixin可能会导致组件之间的依赖关系不清楚,不好追溯源
    • mixin生命周期函数:先执行mixin中生命周期函数;后执行组件内部代码mixin中的data数据和组件中的data数据冲突时,组件中的data数据会覆盖mixin中数据
    // useCount.js 
    const useCount = (initValue = 1) => {
        const count = ref(initValue)
        
        const increase = (delta) => {
            if(typeof delta !== 'undefined'){
                count.value += delta
            }else{
                count.value++
            }
        }
        
        const multiple = computed(() => count.value * 2)
        
        const decrease = (delta) => {
            if(typeof delta !== 'undefined'){
                count.value -= delta
            }else{
                count.value--
            }
        }
        
        const reset = () => count.value = initValue 
        
        return {
            count,
            multiple,
            increase,
            decrease,
            reset
        }
    }
    
    export default useCount
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    <template>
        <p>{{count}}p>
        <p>{{multiple}}p>
        <el-button @click="addCount">count++el-button>
        <el-button @click="subCount">count--el-button>
        <el-button @click="resetCount">resetel-button>
    template>
    
    <script setup>
    import useCount from "@/hooks/useCount"
    const {count,multiple,increase,decrease,reset} = useCount(10)
    const addCount = () => increase()
    const subCount = () => decrease()
    const resetCount = () => reset()
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    13.懒加载组件

    // Demo.vue
    <template>
        <div>异步加载组件的内容div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    // ErrorComponent.vue
    <template>
        <div>Warning:组件加载异常div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    // LoadingComponent.vue
    <template>
        <div>组件正在加载...<div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    <template>
        <AsyncDemo />
    template>
    
    <script setup>
    import LoadingComponent from './LoadingComponent.vue'
    import ErrorComponent from './ErrorComponent.vue'
    
    const time = (t,callback = () => {}) => {
        return new Promise((resolve) => {
            setTimeout(() => {
                callback()
                resolve()
            },t)
        })
    }
    
    const AsyncDemo = defineAsyncComponent({
        // 要加载的组件
        loader:() => {
            return new Promise((resolve) => {
                async function(){
                    await time(3000)
                    const res = await import("./Demo.vue")
                    resolve(res)
                }
            })
        },
        // 加载异步组件时使用的组件
        loadingComponent:LoadingComponent,
        // 加载失败时使用的组件
        errorComponent:ErrorComponent,
        // 加载延迟(在显示loadingComponent之前的延迟),默认200
        delay:0,
        // 超时显示组件错误,默认永不超时
        timeout:5000
    })
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    14.插槽

    • 具名插槽使用方式不同:vue2 中使用slot='插槽名称',vue3 中使用v-slot:插槽名称
    • 作用域插槽使用方式不同:vue2 中在父组件中使用slot-scope="data"从子组件获取数据,vue3 中在父组件中使用#data或者#default="{data}"获取
    <template>
        <div>
            
            <slot />
            
            <slot name="slotName" />
            
            <slot :data="user" name="propsSlot" />
        div>
    template>
    
    <script>
    const user = reactive({
        name:'falcon',
        age:20
    })
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    <template>
        <Son>
            <template #default><div>默认插槽内容div>template>
            <template #slotName><div>具名插槽内容div>template>
            <template #propsSlot="scope">
                <div>
                    作用域插槽内容:name,{{scope.data.name}};age,{{scope.data.age}}
                div>
            template>
        Son>
    template>
    
    <script setup>
    import Son from './Son.vue'
    <script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    15.自定义指令

    • 全局自定义指令在main.js中定义
    • 局部自定义指令在当前组件中定义
    // main.js
    app.directive("focus",{
        mounted(el,bingings,vnode,preVnode){
            el.focus()
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    <template>
        <div>
            <input type="text" v-focus />
        div>
    template>
    
    <script setup>
    const vFocus = {
        mounted:(el) => el.focus()
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    16.v-model

    • vue2 中.syncv-model都是语法糖,都可以实现父子组件中数据的双向通信
    • vue2两种格式差别:v-model="num",:num.sync="num";v-model:@input+value,:num.sync:@update:num
    • vue2中v-model只能用一次,.sync可以有多个
    • vue3中取消了 .sync,合并到v-model,vue3中v-model可以有多个
    <template>
        <p>name:{{name}}p>
        <p>age:{{age}}p>
        <Son v-model:name="name" v-model:age="age" />
    template>
    
    <script setup>
    import Son from './Son.vue'
    
    const user = reactive({
        name:'falcon',
        age:20
    })
    
    const {name,age} = toRefs(user)
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    <template>
        <input type="text" :value="name" @input="onNameInput" />
        <input type="number" :value="age" @change="onAgeInput" />
    template>
    
    <script setup>
    defineProps({
        name:{
            type:String,
            default:() => ""
        },
        age:{
            type:String,
            default:() => ""
        }
    })
    
    const emit = defineEmits(["update:name"],["update:age"])
    
    const onNameInput = (e) => emit("update:name",e.target.value)
    const onAgeInput = (e) => emit("update:age",e.target.value)
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    17.v-if/v-for

    • 不建议 v-for 与 v-if 一起使用
    • vue2 中优先级v-for 高于 v-if。如果执行过滤列表项操作,配合computed;如果条件判断来显隐循环列表,将v-if提前,包裹v-for
    • vue3 中优先级v-if 高于 v-for
    <template>
        <div v-if="flag">
            <div v-for="item in dataList" :key="item.id">{{item.id}} - {{item.label}}div>
        div>
    template>
    
    <script setup>
    const flag = ref(true)
    const dataList = reactive([
        {
            id:1,
            label:'list-01'
        },
        {
            id:2,
            label:'list-02'
        }
    ])
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    18.v-bind

    • vue2 中单独声明优先,并且重复定义会出发出警告
    • vue3 中绑定值是否生效遵循就近原则
    <template>
        <div>
            <input type="text" v-bind:disabled="false" :disabled="disabled" />
            <input type="text" :disabled="disabled" v-bind:disabled="false" />
        div>
    template>
    
    <script setup>
    const disabled = ref(true)
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    19.组件通信

    19.1 props/$emit

    父组件传值,子组件通过props接受;子组件想改变父组件中数值,通过$emit调用父组件中方法

    • 父组件
    <template>
      <Child :count="count" :name="name" :age="age" @add="add" @sub="sub" />
    template>
    
    <script setup>
    import Child from "./Child.vue";
    
    const count = ref(0);
    const user = reactive({
      name: "falcon",
      age: 20,
    });
    
    const add = () => count.value++;
    const sub = () => count.value--;
    
    const { name, age } = toRefs(user);
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 子组件
    <template>
      <p>接受到的参数为:name,{{ name }},age,{{ age }},count,{{ count }}p>
      <el-button type="primary" size="small" @click="add">count++el-button>
      <el-button type="primary" size="small" @click="sub">count--el-button>
    template>
    
    <script setup>
    defineProps({
      name: {
        type: String,
        default: () => "",
      },
      age: {
        type: Number,
        default: () => 0,
      },
      count: {
        type: Number,
        default: () => 0,
      },
    });
    
    const emits = defineEmits(["add", "sub"]);
    
    const add = () => emits("add");
    const sub = () => emits("sub");
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    19.2 attrs

    传递属性或方法给子组件下级组件,传递子组件中没有被props定义的属性,传递子组件中没有被emits定义的方法

    • 父组件
    <template>
      <Child :count="count" :name="name" :age="age" @add="add" @sub="sub" />
    template>
    
    <script setup>
    import Child from "./Child.vue";
    
    const count = ref(0);
    const user = reactive({
      name: "falcon",
      age: 20,
    });
    
    const add = () => count.value++;
    const sub = () => count.value--;
    
    const { name, age } = toRefs(user);
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 子组件
    <template>
      <p>子组件接收:{{ count }}p>
      <el-button type="primary" size="small" @click="add">count++el-button>
      <GrandChild v-bind="$attrs" />
    template>
    
    <script setup>
    import GrandChild from "./GrandChild.vue";
    
    defineProps({
      count: {
        type: Number,
        default: () => 0,
      },
    });
    
    const emits = defineEmits(["add"]);
    
    const add = () => emits("add");
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 孙组件
    <template>
      <p>孙组件接受:name,{{ name }},age,{{ age }}p>
      <el-button type="primary" size="small" @click="sub">count--el-button>
    template>
    
    <script setup>
    defineProps({
      name: {
        type: String,
        default: () => "",
      },
      age: {
        type: Number,
        default: () => 0,
      },
    });
    
    const emits = defineEmits(["sub"]);
    
    const sub = () => emits("sub");
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    19.3 v-model

    • 父组件
    <template>
      <Child v-model:name="name" v-model:count="count" v-model:salary="salary" />
    template>
    
    <script setup>
    import Child from "./Child.vue";
    
    const name = ref("falcon");
    const count = ref(0);
    const salary = ref(3000);
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 子组件
    <template>
      <p>
        子组件接受到的v-model参数:name,{{ name }},count,{{ count }},salary,{{
          salary
        }}
      p>
      <el-button type="primary" size="small" @click="changeCount"
        >count++el-button
      >
      <el-button type="primary" size="small" @click="changeSalary"
        >salary1000+el-button
      >
    template>
    
    <script setup>
    const props = defineProps({
      name: {
        type: String,
        default: () => "",
      },
      count: {
        type: Number,
        default: () => "",
      },
      salary: {
        type: Number,
        default: () => "",
      },
    });
    
    const emits = defineEmits(["update:count", "update:salary"]);
    const changeCount = () => emits("update:count", props.count + 1);
    const changeSalary = () => emits("update:salary", props.salary + 1000);
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    19.4 ref/expose

    通过ref获取指定的DOM元素或组件,结合defineExpose暴露出来的属性和方法实现通信

    • 父组件
    <template>
      <div>title:{{ title }}div>
      <Child ref="child" />
      <el-button type="primary" size="small" @click="add">count++el-button>
      <el-button type="primary" size="small" @click="sub">count--el-button>
      <el-button type="primary" size="small" @click="receive"
        >receive msgel-button
      >
    template>
    
    <script setup>
    import Child from "./Child.vue";
    
    const child = ref(null);
    const title = ref("暂无数据");
    const add = () => child.value.add();
    const sub = () => child.value.sub();
    const receive = () => (title.value = child.value.msg);
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 子组件
    <template>
      <p>子组件:count,{{ count }}p>
    template>
    
    <script setup>
    const count = ref(0);
    const msg = "expose message";
    const add = () => count.value++;
    const sub = () => count.value--;
    
    defineExpose({
      msg,
      add,
      sub,
    });
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    19.5 provide/inject

    祖先向下级传递参数,无论层级多深,都可以传递

    • 父组件
    <template>
      <Child />
    template>
    
    <script setup>
    import Child from "./Child.vue";
    
    const user = reactive({
      name: "falcon",
      age: 20,
    });
    provide("user", user);
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 子组件
    <template>
      <p>子组件接受:name,{{ user.name }}p>
      <GrandChild />
    template>
    
    <script setup>
    import GrandChild from "./GrandChild.vue";
    
    const user = inject("user");
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 孙组件
    <template>
      <p>孙组件接受:age,{{ user.age }}p>
    template>
    
    <script setup>
    const user = inject("user");
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    19.6 mixin

    不建议使用,建议使用可组合函数完成组件间通信和复用

    • mixin.js
    const count = ref(20);
    const name = ref("falcon");
    
    const addCount = () => count.value++;
    const subCount = () => count.value--;
    
    export default { count, name, addCount, subCount };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 组件使用
    <template>
      <p>name:{{ name }},count:{{ count }}p>
      <el-button @click="addCount" type="primary" size="small">count++el-button>
      <el-button @click="subCount" type="primary" size="small">count--el-button>
    template>
    
    <script setup>
    import mixins from "./mixin";
    const { count, name, addCount, subCount } = mixins;
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    19.7 mitt

    vue3 中废除api:$on$once$off;不再支持Event Bus,选用替代方案mitt.js,原理还是Event Bus

    • bus.js
    import mitt from 'mitt';
    export default mitt()
    
    • 1
    • 2
    • 父组件
    <template>
      <Brother1 />
      <Brother2 />
    template>
    
    <script setup>
    import Brother1 from "./Brother1.vue";
    import Brother2 from "./Brother2.vue";
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 兄弟组件1
    <template>
        <p>brother1 发送事件p>
        <el-button type="primary" size="small" @click="handleClick">发送事件el-button>
    template>
    
    <script setup>
    import mybus from './bus.js';
    
    const handleClick = () => {
        mybus.emit("title",{title:"hello world"});
        mybus.emit("user",{user:{name:"falcon",age:20}})
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 兄弟组件2
    <template>
        <p>brother2 接受事件p>
        <p>title:{{title}}p>
        <p>user:name,{{name}};age,{{age}}p>
    template>
    
    <script setup>
    import mybus from './bus.js'
    
    const title = ref("")
    const user = reactive({
        name:"",
        age:null
    })
    
    mybus.on("title",(data) => {
        title.value = data.title
    })
    mybus.on("user",(data) => {
        user.name = data.user.name
        user.age = data.user.age 
    })
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    20.状态管理 pinia

    pinia 是 vue 的存储库,允许跨组件/跨页面共享状态。具有以下优点:

    • 轻量,约1kb
    • 去除Mutation,Actions支持同步和异步
    • 无需手动注册store,store仅在需要时才自动注册
    • 没有模块嵌套,store之间可以自由使用
    • 支持模块热更新

    20.1 创建

    import { createPinia } from 'pinia'
    
    const store = createPinia()
    export default store
    
    • 1
    • 2
    • 3
    • 4

    20.2 定义

    // 引入store定义函数
    import { defineStore } from 'pinia'
    
    // 定义store实例并导出
    // 第一个参数,字符串类型,唯一不可重复,作为库id来区分不同库
    // 第二个参数,以对象形式配置存储库的state、getters、actions
    
    export const useStore = defineStore('useCount',{
        /**
            state,存储全局状态
            必须是箭头函数:为了在服务器端渲染的时候避免交叉请求导致数据状态污染
        */
        state:() => {
            return {
                count:0
            }
        },
        /**
            getters,封装计算属性
            具有缓存功能,类似于computed;需要传入state才能拿到数据;不能传递任何参数,但是可以返回一个函数接受任何参数
        */
        getters:{
            doubleCount:(state) => state.count * 2,
            powCount(){
                return this.doubleCount ** 2
            }
        },
        /**
            actions,编辑业务逻辑
            类似于methods,支持同步和异步;获取state里的数据不需要传入直接使用this
        */
        actions:{
            addCount(){
                this.count++
            },
            subCount(){
                this.count--
            }
        },
        /**
            配置数据持久化需要进行的操作
        */
        persist:{}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    20.3 页面使用

    <template>
        <p>{{useStoreDemo.count}}<p>
        <p>{{useStoreDemo.doubleCount}}</p>
        <p>{{useStoreDemo.powCount}}</p>
        <el-button @click="toAdd">count++</el-button>
        <el-button @click="toSub">count--</el-button>
    </template>
    
    <script setup>
    import {useStore} from '../store'
    const useStoreDemo = useStore()
    
    // 也可以解构出来想要使用的count,但直接解构不具有响应式,想要具有响应式,可以执行如下操作:
    const {count} = storeToRefs(useStore())
    
    const toAdd = () => useStoreDemo.addCount()
    const toSub = () => useStoreDemo.subCount()
    <script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    20.4 数据持久化

    pinia的数据是存储在内存中的,页面刷新后数据会丢失;可以支持扩展插件,实现数据持久化

    • npm i pinia-plugin-persist,默认使用sessionStorage
    • 配置使用代码如下:
    persist:{
        enabled:true,
        strategies:[
            {
                storage:localStorage,
                paths:["num","user"]
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    21.路由

    • query传参配置path,params传参配置name,且params中配置path无效
    • query传参显示在地址栏,params传参不会
    • query传参刷新页面数据不会消失,params传参刷新页面数据消失
    • params可以使用动态参数(“/path/:params”),动态参数会显示在地址栏中,且刷新页面数据不会消失
    • name为路由中定义的name属性,严格区分大小写
    • 路由跳转:前进router.go(1)、后退router.go(-1)、刷新router.go(0)
    • 使用案例:
    <template>
        <el-button @click="TransByQuery">通过query传参</el-button>
        <el-button @click="TransByParams">通过params传参</el-button>
        <el-button @click="TransByDynamic">动态传递参数</el-button>
    </template>
    
    <script setup>
    const queryParams = reactive({
        name:'falcon',
        age:20
    })
    
    const id = ref('2023')
    
    const router = useRouter()
    
    const TransByQuery = () => {
        router.push({
            path:'/basic/querydemo'query:queryParams
        })
    }
        
    const TransByParams = () => {
        router.push({
            name:'ParamsDemo',
            params:queryParams
        })
    }
    
    const TransByDynamic = () => {
        router.push({
            name:'DynamicDemo',
            params:{id:id.value}
        })
    }
    <script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • query 接受参数
    const route = useRoute()
    console.log(route.query.name,route.query.age)
    
    • 1
    • 2
    • params 接受参数
    const route = useRoute()
    console.log(route.params.name,route.params.age)
    
    • 1
    • 2
    • 动态传递 接受参数
    const route = useRoute()
    console.log(route.params.id)
    
    • 1
    • 2
    • 相应的路由
     {
      name: "QueryDemo",
      path: "querydemo",
      redirect: null,
      component: "basic/userouter/querydemo",
      hidden: true,
      meta: {
        title: "query样例",
        icon: null,
      },
    },
    {
      name: "ParamsDemo",
      path: "paramsdemo",
      redirect: null,
      component: "basic/userouter/paramsdemo",
      hidden: true,
      meta: {
        title: "params样例",
        icon: null,
      },
    },
    {
      name: "DynamicDemo",
      path: "dynamicdemo/:id",
      redirect: null,
      component: "basic/userouter/dynamicdemo",
      hidden: true,
      meta: {
        title: "dynamic样例",
        icon: null,
      },
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    22.css补充

    22.1 样式穿透

    • css >>> className,less /deep/ className, scss ::v-deep className
    • vue3中css使用::deep(className)

    22.2 绑定变量

    <template>
        <div class="name">falcondiv>
    template>
    
    <script setup>
    const str = ref('#f00')
    script>
    
    <style lang="scss" scoped>
    .name{
        background-color:v-bind(str)
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    网工内推 | 国企专场,网络运维工程师,华为/思科认证优先
    Springboot毕设项目管易tms运输智能监控管理系统663kq(java+VUE+Mybatis+Maven+Mysql)
    电脑如何不断网切换IP:实用方法与注意事项‌
    高性能计算软件与开源生态| ChinaOSC
    【论文阅读】DiffusionDet: Diffusion Model for Object Detection
    如何将谷歌浏览器设置为默认浏览器
    [C++随笔录] vector使用
    白给的ROS编程笔记——vscode+ros工程建立以及ros package中的python脚本封装成模块被其他脚本调用
    ExcelServer EXCEL服务器使用- 用户、角色权限配置
    Unity Inspector编辑器扩展,枚举显示中文,枚举值自定义显示内容
  • 原文地址:https://blog.csdn.net/weixin_45506717/article/details/132883249