vue3的知识整理

作者: miao8862 | 来源:发表于2021-06-10 23:05 被阅读0次

    1. vue3的生命周期

    vue3的生命周期一般有2种形式写法,一种是基于vue2options API的写法,一种是vue3特有的Composition API

    options API的生命周期
    基本同vue2的生命周期基础,只是为了与生命周期beforeCreatecreated对应,将beforeDestroydestroyed更名为beforeUnmountunmounted,使用方法同vue2

    <template>
      <p>生命周期</p>
      <p>{{msg}}</p>
      <button @click="changeMsg">修改值</button>
      <button @click="toHome">跳转页面</button>
    </template>
    <script>
    import { useRouter } from 'vue-router'
    
    export default {
      name: 'lifeCycles',
      data() {
        return  {
          msg: 'hello vue3'
        }
      },
      setup() {
        console.log('setup')
    
        // 在`setup`函数中创建`router`对象,相当于vue2中的`this.router`
        const router = useRouter()
        const toHome = () => {
          router.push({
            path: '/'
          })
        }
        return {toHome}
      },
      beforeCreate() {
        console.log('beforeCreate');
      },
      created() {
        console.log('created');
      },
      beforeMount() {
        console.log('beforeMount');
      },
      mounted() {
        console.log('mounted');
      },
      beforeUpdate() {
        console.log('beforeUpdate');
      },
      updated() {
        console.log('updated');
      },
      // 由vue2 beforeDestroy改名
      beforeUnmount() {
        console.log('beforeUnmount');
      },
      // 由vue2 destroyed改名
      unmounted() {
        console.log('unmounted');
      },
      methods: {
        changeMsg() {
          this.msg = 'after changed'
        }
      }
    }
    </script>
    
    初次渲染时
    点击修改值的生命周期
    点击跳转,组件销毁的生命周期

    composition API的生命周期
    composition API的生命周期钩子函数是写在setup函数中的,它所有生命周期是在vue2生命周期名字前加on,且必须先导入才可使用

    在这种写法中,是没有onBeforeCreateonCreated周期的,setup等同于(或者说是介于)这两个生命周期

    <template>
      <p>composition API生命周期</p>
      <p>{{msg}}</p>
      <button @click="changeMsg">修改值</button>
      <button @click="toHome">跳转页面</button>
    </template>
    <script>
    import { useRouter } from 'vue-router'
    // 必须先导入生命周期
    import { 
      onBeforeMount, 
      onMounted, 
      onBeforeUpdate,
      onUpdated,
      onBeforeUnmount,
      onUnmounted
    } from 'vue'
    export default {
      name: 'lifeCycles',
      data() {
        return  {
          msg: 'hello vue3'
        }
      },
      setup() {
        console.log('setup')
        onBeforeMount(() => {
          console.log('onBeforeMount');
        })
        onMounted(() => {
          console.log('onMounted');
        })
        onBeforeUpdate(() => {
          console.log('onBeforeUpdate');
        })
        onUpdated(() => {
          console.log('onUpdated');
        })
        onBeforeUnmount(() => {
          console.log('onBeforeUnmount');
        })
        onUnmounted(() => {
          console.log('onUnmounted');
        })
    
        // 在`setup`函数中创建`router`对象,相当于vue2中的`this.router`
        const router = useRouter()
        const toHome = () => {
          router.push({
            path: '/'
          })
        }
        return {toHome}
      },
      
      methods: {
        changeMsg() {
          this.msg = 'after changed'
        }
      }
    }
    
    </script>
    
    初次渲染
    点击修改data中的值
    页面跳转,组件销毁

    2. 如何理解 Composition API 和 options API ?

    Composition API带来了什么:

    • 更好的代码组织
    • 更好的逻辑复用,避免mixins混入时带来的命名冲突和维护困难问题
    • 更好的类型推导

    options API
    使用options API,当代码很多时,即当data, watch, methods等有很多内容时,业务逻辑比较复杂,我们需要修改一部分时,可能要分别到data/methods/模板中对应修改,可能需要我们在页面反复横跳来修改,逻辑块会比较散乱。

    Composition API
    Composition API则会将业务相关的部分整合到一起,作为一个模块,当要修改,统一到一处修改,代码看起来会更有条理

    image.png
    它包含的内容包括:
    • reactive
    • ref相关(ref, toRef, toRefs,后面会具体介绍)
    • readonly
    • watch和watchEffect
    • setup
    • 生命周期钩子函数

    两者的选择:

    • 不建议共用,否则容易引起混乱(思路、组织方式、写法都不太一样)
    • 小型项目,业务逻辑简单的,建议用options API,对新手也比较友好
    • 中大型项目、逻辑复杂,建议使用Composition API

    Composition API它属于高阶技巧,不是基础必会的,有一定的学习成本,是为了解决复杂业务逻辑而设计,就像hooksReact中的地位一样

    3. 如何理解ref,toRef,toRefs

    ref

    • 通过ref方式创建响应式的值类型,并赋予初始值,并通过.value的方式获取值或修改值
    • 通过reactive方式创建响应式的引用类型,并赋予初始值,修改和获取方式同普通对象一样
    • 除了以上两种用法,还可以使用ref来声明dom元素,也就是类似vue2中的用法
    <template>
      <p>ref demo</p>
      <p>{{nameRef}}今年{{ageRef}}岁了</p>
      <p>他喜欢{{hobbies.type}}</p>
    </template>
    
    <script>
    // 导入ref, reactive, onMounted
    <template>
      <p>ref demo</p>
      <p>{{nameRef}}今年{{ageRef}}岁了</p>
      <p>他喜欢{{hobbies.type}}</p>
      <p ref="eleRef">我是refTemplate使用方式的内容</p>
    </template>
    
    <script>
    import { ref, reactive, onMounted } from 'vue'
    export default {
      name: 'ref',
      setup() {
        // ref
        const ageRef = ref(3); // ref创建响应式的值类型,并赋予初始值
        const nameRef = ref('小花')
        console.log(ageRef.value) // 通过.value方式获取值
        ageRef.value = 18 // 通过.value方式修改值
    
        // reactive
        const hobbies = reactive({
          type: 'basketball'
        })
        console.log(hobbies.type) // basketball,通过obj[key]方式获取值
        hobbies.type = 'aaaaa' // 通过obj[key]=xxx方式修改值
    
        // refTemplate
        const eleRef = ref(null)
        onMounted(() => {
          // 跟vue2的区别在于,vue2使用this.$refs['eleRef']方式获取dom,这里通过eleRef.value方式获取
          console.log(eleRef.value) // dom
          console.log(eleRef.value.innerHTML) // 我是refTemplate使用方式的内容
    
        })
        
        // 注意,这些对象都要return出去,否则就不是响应式
        return {
          ageRef,
          nameRef,
          hobbies,
          eleRef
        }
      }
    }
    </script>
    
    image.png
    PS: 小建议,ref定义的数据可以加个Ref后缀,这样就能区分refreactive定义的变量了

    toRef

    看定义有点绕

    • 针对一个响应式对象(reactive封装)的属性prop
    • 通过toRef创建一个ref对象,这个ref对象和reactive对象的某属性两者保持引用关系

    注意,如果toRef是通过普通对象来生成ref对象,那么普通对象和ref对象都将不是响应式的

    <template>
      <p>toRef demo</p>
      <p>小花今年 - {{ageRef}}岁 - {{state.age}}岁</p>
    </template>
    
    <script>
    import { toRef, reactive } from 'vue'
    export default {
      name: 'toRef',
      setup() {
        // 定义一个响应式的reactive引用对象
        const state = reactive({
          name: '小花', 
          age: 3
        })
    
        // 如果是普通对象,使用toRef,那么它们将都不是响应式的
        // 也就是说ageRef和state都不是响应式
        // const state = {
        //   name: '小花', 
        //   age: 3
        // }
    
        // 通过toRef创建一个响应值类型ageRef, 这个ageRef和state.age属性保持双向引用
        const ageRef = toRef(state, 'age')
    
        // 修改state.age值时,ageRef也会跟着改
        setTimeout(() => {
          state.age = 25
        }, 1000)
    
        // 修改ageRef值时,state.age也会跟着改
        setTimeout(() => {
          ageRef.value = 30
        }, 2000)
    
        return {
          state, ageRef
        }
      }
    }
    </script>
    
    初始时
    1s后,state.age改了,ageRef也跟着改了
    2s后,ageRef改了,state.age也跟着改了

    toRefs

    • 将响应式对象(reactive)的所有属性prop,转换为对应prop名字的ref对象
    • 两者保持引用关系
    <template>
      <p>toRef demo</p>
      <!-- 这样,模板中就不用写state.name, state.age了,直接写name和age即可 -->
      <p>{{name}}今年 - {{age}}岁</p>
    </template>
    
    <script>
    import { toRefs, reactive } from 'vue'
    export default {
      name: 'toRef',
      setup() {
        // 定义一个响应式的reactive引用对象
        const state = reactive({
          name: '小花', 
          age: 3
        })
    
        // 相当于
        // const age = toRef(state, 'age')
        // const name = toRef(state, 'name')
        // const stateAsRefs = { age, name }
        const stateAsRefs = toRefs(state)
    
        // 修改state.age值时,就会映射到ref类型的age上
        setTimeout(() => {
          state.age = 25
        }, 1000)
    
        // return stateAsRefs 等同于:
        // const { age: age, name: name } = stateAsRefs
        // return { age, name }
        return stateAsRefs
      }
    }
    </script>
    

    应用:
    当使用composition API时,抽象出一个模块,使用toRefs返回响应式对象,这样,在接收的时候,我们就可以使用解构的方式获取到对象里面的内容,这也是比较符合我们常用的方式

    // 封装一个模块,使用toRefs导出对象
    export function useFeature() {
      const state = reactive({
        x: 1,
        y: 2
      })
      // ...
      return toRefs(state)
    }
    
    // 导入时,可以使用解构方式
    import { useFeature } from './features'
      
    export default {
      setup() {
        // 可以在不丢失响应式的情况下解构
        const  { x, y } = useFeature()
        return { x, y }
      }
    }
    

    ref, toRef, toRefs 使用小结:

    • reactive做对象的响应式,用ref做值类型的响应式
    • setup中返回toRefs(state),或toRef(state, prop)
    • ref变量命名建议用xxxRef
    • 合成函数返回响应式对象时,使用toRefs

    为什么需要 ref ?

    • 如果没有ref,普通的值类型定义,没法做响应式
    • computed,setup,合成函数,都有可能返回值类型,要保证其返回是响应式的
    • 如果vue不定义ref,用户可能会自己造ref,反而更加混乱

    为什么需要.value ?

    • ref是一个对象(保证响应式),value用来存储值
    • 通过.value属性的getset实现响应式
    • 用于模板、reactive时,不需要.value,这是因为vue编译会自动识别,其他情况则需要使用

    为什么需要 toRef 和 toRefs ?

    • 目的:为了不丢失响应式的情况下,把对象数据分散、扩散(或者说是解构)
    • 前提:针对的是响应式对象(reactive封装的对象)
    • 本质:不创建响应式(创建是ref和reactive的事),而是延续响应式

    4. watch和watchEffect的区别

    1. watchwatchEffect都可以监听data的变化
    2. watch需要指定监听的属性,默认初始时不会触发,如果初始要触发,需要配置immediate: true
    3. watchEffect是不需要指定监听的属性,而是自动监听其用到的属性,它初始化时,一定会执行一次,这是为了收集要监听的属性
    <template>
      <p>watch 的使用</p>
      <p>numberRef: {{numberRef}}</p>
      <p>{{name}}-{{age}}</p>
    </template>
    
    <script>
    import { ref, reactive, toRefs, watch, watchEffect } from 'vue'
    export default {
      name:'Watch',
      setup() {
        const numberRef = ref(1000)
        const state = reactive({
          name: '小花', 
          age: 3
        })
        // 监听ref变量
        watch(numberRef, (newVal, oldVal) => {
          console.log('watch:', newVal, oldVal);  
          // watch: 1000 undefined
          // watch: 200 1000
        }, {
          immediate: true // 第三个参数可选,是一些配置项,immediate表示初始化时就执行监听
        })
    
        setTimeout(() => {
          numberRef.value = 200
        }, 1000)
    
        // 监听对象
        watch(
          // 第一参数是监听对象,如果是对象需要使用函数返回形式
          () => state.age,
          // 第二个参数是监听的变化值
          (newVal, oldVal) => {
            console.log('watch:', newVal, oldVal);  
            // watch: 3 undefined
            // watch: 18 3
          },
          // 第三个参数是配置项
          {
            immediate: true, // 初始变化就监听
            deep: true // 深度监听
          }
        )
        setTimeout(() => {
          state.age = 18
        }, 1000)
    
        return {
          numberRef,
          ...toRefs(state)
        }
      }
    }
    </script>
    
    watch
      // watchEffect监听
        watchEffect(() => {
          console.log('watchEffect');
          console.log(numberRef.value);
          console.log(state.age);
        })
    
    watchEffect

    5. 在setup中怎么获取组件实例

    • setup和其它compostion API中没有this
    • 如果一定要获取,要使用getCurrentInstance获取,并且在挂载后才可获取数据
    • 如果是options API,则可以像vue2一样正常使用this
    <template>
      <p>get instance</p>
    </template>
    
    <script>
    import { getCurrentInstance, onMounted } from 'vue'
    export default {
      name: 'GetInstance',
      data() {
        return  {
          x: 1,
          y: 2
        }
      },
      // composition API
      // 没有this,需要getCurrentInstance来获取组件实例
      // 且setup本身是beforeCreate和Created生命周期间的钩子,拿不到data,所以要在onMounted中获取
      setup() {
        console.log('this', this);  // this undefined
    
        const instance = getCurrentInstance()
        console.log('instance', instance.data.x); //  instance undefined
    
        onMounted(() => {
          console.log('instance', instance.data.x); // instance 1
        }) 
      },
      // options API
      mounted() {
        console.log(this.x);  // 1
      }
    }
    </script>
    

    6. vue3升级了哪些重要的功能

    参考官网升迁指南

    createApp

    // vue2
    const app = new Vue({/*options*/})
    Vue.use(/*...*/)
    Vue.mixin(/*...*/)
    Vue.component(/*...*/)
    Vue.directive(/*...*/)
    
    // vue3
    const app = Vue.createApp({/*options*/})
    app.use(/*...*/)
    app.mixin(/*...*/)
    app.component(/*...*/)
    app.directive(/*...*/)
    

    emits属性

    1. setup中可以使用emit向父组件发出事件
    2. 在子组件中,需要emits声明向父组件发出的事件集合
    <template>
      parent
      <Child msg="hello" @log="log" />
    </template>
    <script>
    import Child from './child.vue'
    export default {
      name: 'emits',
      components:{
        Child
      },
      methods: {
        log() {
          console.log('child emit me!')
        }
      }
    }
    </script>
    
    <!-- Child.vue -->
    <script>
    export default {
      name: 'child',
      props: {
        msg: {
          type: String
        }
      },
      emits: ['log'],  // 需要声明接收的父组件传递的方法
      // 在setup方法中,可以使用emit方法与父组件通信
      setup(props, {emit}) {
        emit('log')
      },
      methods: {
        one(e) {
          console.log('one');
        },
        two(e) {
          console.log('two');
        }
      }
    }
    </script>
    

    多事件处理

    <template>
      <!-- 可以同时触发多个事件 -->
      <button @click="one($event), two($event)">触发多事件</button>
    </template>
    

    fragment

    vue2只允许template中只有一个元素,如果多个元素,必须用一个元素包裹
    vue3则允许template中可以直接有多个元素,这样就可以减少dom层级

    移除.sync

    vue2中的.sync

    vue2中使用.sync是对以下语句的语法糖,父组件通过v-bind:xxx.sync='xxx'来向子组件说明这个属性是双向绑定的,子组件中通过$emit('update:xxx', newVal)来更新这个值

    <text-document
      v-bind:title="doc.title"
      v-on:update:title="doc.title = $event"
    ></text-document>
    
    <!-- sync语法糖 -->
    <text-document v-bind:title.sync="doc.title"></text-document>
    

    vue3中,废除了.sync的写法,换成一种更具有语义的写法v-model:xxx,在父组件中使用v-model:xxx方式说明这个属性是双向绑定的,子组件中通过$emit('update:xxx', newVal)来更新这个值

    <template>
      <p>{{name}}-{{age}}</p>
    
      <UserInfo 
        v-model:name="name"
        v-model:age="age"
      />
    </template>
    
    <script>
    import { reactive, toRefs } from 'vue'
    import UserInfo from './userInfo.vue'
    export default {
      name: 'vmodel',
      components: {
        UserInfo
      },
      setup() {
        const userInfo = reactive({
          name: '小花',
          age: 3
        })
        return toRefs(userInfo)
      }
    }
    </script>
    
    <!-- userInfo.vue -->
    <template>
      <input type="text" :value="name" @input="$emit('update:name', $event.target.value)">
      <input type="text" :value="age" @input="$emit('update:age', $event.target.value)">
    </template> 
    <script>
    export default {
      props: {
        name: {
          type: String
        },
        age: {
          type: String
        }
      }
    }
    </script>
    

    异步组件的导入方式不一样

    vue2child: () => import('child.vue')
    vue3:需要defineAsyncComponent导入,child: defineAsyncComponent(() => import('child.vue'))

    teleport

    teleport将我们的模板移动到DOMVue app之外的其他位置,比如可以使用teleport标签将组件在body

    <template>
      <p>这是放在当前组件下的内容</p>
      <teleport to="body">
        <p>假设这是个弹窗,直接放到body下</p> 
      </teleport>
    </template>
    
    image.png

    相关文章

      网友评论

        本文标题:vue3的知识整理

        本文链接:https://www.haomeiwen.com/subject/yweeeltx.html