vue3的知識(shí)整理

1. vue3的生命周期

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

options API的生命周期
基本同vue2的生命周期基礎(chǔ),只是為了與生命周期beforeCreatecreated對(duì)應(yīng),將beforeDestroydestroyed更名為beforeUnmountunmounted,使用方法同vue2

<template>
  <p>生命周期</p>
  <p>{{msg}}</p>
  <button @click="changeMsg">修改值</button>
  <button @click="toHome">跳轉(zhuǎn)頁面</button>
</template>
<script>
import { useRouter } from 'vue-router'

export default {
  name: 'lifeCycles',
  data() {
    return  {
      msg: 'hello vue3'
    }
  },
  setup() {
    console.log('setup')

    // 在`setup`函數(shù)中創(chuàng)建`router`對(duì)象,相當(dāng)于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>
初次渲染時(shí)

點(diǎn)擊修改值的生命周期

點(diǎn)擊跳轉(zhuǎn),組件銷毀的生命周期

composition API的生命周期
composition API的生命周期鉤子函數(shù)是寫在setup函數(shù)中的,它所有生命周期是在vue2生命周期名字前加on,且必須先導(dǎo)入才可使用

在這種寫法中,是沒有onBeforeCreateonCreated周期的,setup等同于(或者說是介于)這兩個(gè)生命周期

<template>
  <p>composition API生命周期</p>
  <p>{{msg}}</p>
  <button @click="changeMsg">修改值</button>
  <button @click="toHome">跳轉(zhuǎn)頁面</button>
</template>
<script>
import { useRouter } from 'vue-router'
// 必須先導(dǎo)入生命周期
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`函數(shù)中創(chuàng)建`router`對(duì)象,相當(dāng)于vue2中的`this.router`
    const router = useRouter()
    const toHome = () => {
      router.push({
        path: '/'
      })
    }
    return {toHome}
  },
  
  methods: {
    changeMsg() {
      this.msg = 'after changed'
    }
  }
}

</script>
初次渲染

點(diǎn)擊修改data中的值

頁面跳轉(zhuǎn),組件銷毀

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

Composition API帶來了什么:

  • 更好的代碼組織
  • 更好的邏輯復(fù)用,避免mixins混入時(shí)帶來的命名沖突和維護(hù)困難問題
  • 更好的類型推導(dǎo)

options API
使用options API,當(dāng)代碼很多時(shí),即當(dāng)data, watch, methods等有很多內(nèi)容時(shí),業(yè)務(wù)邏輯比較復(fù)雜,我們需要修改一部分時(shí),可能要分別到data/methods/模板中對(duì)應(yīng)修改,可能需要我們?cè)陧撁娣磸?fù)橫跳來修改,邏輯塊會(huì)比較散亂。

Composition API
Composition API則會(huì)將業(yè)務(wù)相關(guān)的部分整合到一起,作為一個(gè)模塊,當(dāng)要修改,統(tǒng)一到一處修改,代碼看起來會(huì)更有條理

image.png

它包含的內(nèi)容包括:

  • reactive
  • ref相關(guān)(ref, toRef, toRefs,后面會(huì)具體介紹)
  • readonly
  • watch和watchEffect
  • setup
  • 生命周期鉤子函數(shù)

兩者的選擇:

  • 不建議共用,否則容易引起混亂(思路、組織方式、寫法都不太一樣)
  • 小型項(xiàng)目,業(yè)務(wù)邏輯簡(jiǎn)單的,建議用options API,對(duì)新手也比較友好
  • 中大型項(xiàng)目、邏輯復(fù)雜,建議使用Composition API

Composition API它屬于高階技巧,不是基礎(chǔ)必會(huì)的,有一定的學(xué)習(xí)成本,是為了解決復(fù)雜業(yè)務(wù)邏輯而設(shè)計(jì),就像hooksReact中的地位一樣

3. 如何理解ref,toRef,toRefs

ref

  • 通過ref方式創(chuàng)建響應(yīng)式的值類型,并賦予初始值,并通過.value的方式獲取值或修改值
  • 通過reactive方式創(chuàng)建響應(yīng)式的引用類型,并賦予初始值,修改和獲取方式同普通對(duì)象一樣
  • 除了以上兩種用法,還可以使用ref來聲明dom元素,也就是類似vue2中的用法
<template>
  <p>ref demo</p>
  <p>{{nameRef}}今年{{ageRef}}歲了</p>
  <p>他喜歡{{hobbies.type}}</p>
</template>

<script>
// 導(dǎo)入ref, reactive, onMounted
<template>
  <p>ref demo</p>
  <p>{{nameRef}}今年{{ageRef}}歲了</p>
  <p>他喜歡{{hobbies.type}}</p>
  <p ref="eleRef">我是refTemplate使用方式的內(nèi)容</p>
</template>

<script>
import { ref, reactive, onMounted } from 'vue'
export default {
  name: 'ref',
  setup() {
    // ref
    const ageRef = ref(3); // ref創(chuàng)建響應(yīng)式的值類型,并賦予初始值
    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的區(qū)別在于,vue2使用this.$refs['eleRef']方式獲取dom,這里通過eleRef.value方式獲取
      console.log(eleRef.value) // dom
      console.log(eleRef.value.innerHTML) // 我是refTemplate使用方式的內(nèi)容

    })
    
    // 注意,這些對(duì)象都要return出去,否則就不是響應(yīng)式
    return {
      ageRef,
      nameRef,
      hobbies,
      eleRef
    }
  }
}
</script>

image.png

PS: 小建議,ref定義的數(shù)據(jù)可以加個(gè)Ref后綴,這樣就能區(qū)分refreactive定義的變量了

toRef

看定義有點(diǎn)繞

  • 針對(duì)一個(gè)響應(yīng)式對(duì)象(reactive封裝)的屬性prop
  • 通過toRef創(chuàng)建一個(gè)ref對(duì)象,這個(gè)ref對(duì)象和reactive對(duì)象的某屬性兩者保持引用關(guān)系

注意,如果toRef是通過普通對(duì)象來生成ref對(duì)象,那么普通對(duì)象和ref對(duì)象都將不是響應(yīng)式的

<template>
  <p>toRef demo</p>
  <p>小花今年 - {{ageRef}}歲 - {{state.age}}歲</p>
</template>

<script>
import { toRef, reactive } from 'vue'
export default {
  name: 'toRef',
  setup() {
    // 定義一個(gè)響應(yīng)式的reactive引用對(duì)象
    const state = reactive({
      name: '小花', 
      age: 3
    })

    // 如果是普通對(duì)象,使用toRef,那么它們將都不是響應(yīng)式的
    // 也就是說ageRef和state都不是響應(yīng)式
    // const state = {
    //   name: '小花', 
    //   age: 3
    // }

    // 通過toRef創(chuàng)建一個(gè)響應(yīng)值類型ageRef, 這個(gè)ageRef和state.age屬性保持雙向引用
    const ageRef = toRef(state, 'age')

    // 修改state.age值時(shí),ageRef也會(huì)跟著改
    setTimeout(() => {
      state.age = 25
    }, 1000)

    // 修改ageRef值時(shí),state.age也會(huì)跟著改
    setTimeout(() => {
      ageRef.value = 30
    }, 2000)

    return {
      state, ageRef
    }
  }
}
</script>
初始時(shí)

1s后,state.age改了,ageRef也跟著改了

2s后,ageRef改了,state.age也跟著改了

toRefs

  • 將響應(yīng)式對(duì)象(reactive)的所有屬性prop,轉(zhuǎn)換為對(duì)應(yīng)prop名字的ref對(duì)象
  • 兩者保持引用關(guān)系
<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() {
    // 定義一個(gè)響應(yīng)式的reactive引用對(duì)象
    const state = reactive({
      name: '小花', 
      age: 3
    })

    // 相當(dāng)于
    // const age = toRef(state, 'age')
    // const name = toRef(state, 'name')
    // const stateAsRefs = { age, name }
    const stateAsRefs = toRefs(state)

    // 修改state.age值時(shí),就會(huì)映射到ref類型的age上
    setTimeout(() => {
      state.age = 25
    }, 1000)

    // return stateAsRefs 等同于:
    // const { age: age, name: name } = stateAsRefs
    // return { age, name }
    return stateAsRefs
  }
}
</script>

應(yīng)用:
當(dāng)使用composition API時(shí),抽象出一個(gè)模塊,使用toRefs返回響應(yīng)式對(duì)象,這樣,在接收的時(shí)候,我們就可以使用解構(gòu)的方式獲取到對(duì)象里面的內(nèi)容,這也是比較符合我們常用的方式

// 封裝一個(gè)模塊,使用toRefs導(dǎo)出對(duì)象
export function useFeature() {
  const state = reactive({
    x: 1,
    y: 2
  })
  // ...
  return toRefs(state)
}
// 導(dǎo)入時(shí),可以使用解構(gòu)方式
import { useFeature } from './features'
  
export default {
  setup() {
    // 可以在不丟失響應(yīng)式的情況下解構(gòu)
    const  { x, y } = useFeature()
    return { x, y }
  }
}

ref, toRef, toRefs 使用小結(jié):

  • reactive做對(duì)象的響應(yīng)式,用ref做值類型的響應(yīng)式
  • setup中返回toRefs(state),或toRef(state, prop)
  • ref變量命名建議用xxxRef
  • 合成函數(shù)返回響應(yīng)式對(duì)象時(shí),使用toRefs

為什么需要 ref ?

  • 如果沒有ref,普通的值類型定義,沒法做響應(yīng)式
  • computed,setup,合成函數(shù),都有可能返回值類型,要保證其返回是響應(yīng)式的
  • 如果vue不定義ref,用戶可能會(huì)自己造ref,反而更加混亂

為什么需要.value ?

  • ref是一個(gè)對(duì)象(保證響應(yīng)式),value用來存儲(chǔ)值
  • 通過.value屬性的getset實(shí)現(xiàn)響應(yīng)式
  • 用于模板、reactive時(shí),不需要.value,這是因?yàn)?code>vue編譯會(huì)自動(dòng)識(shí)別,其他情況則需要使用

為什么需要 toRef 和 toRefs ?

  • 目的:為了不丟失響應(yīng)式的情況下,把對(duì)象數(shù)據(jù)分散、擴(kuò)散(或者說是解構(gòu))
  • 前提:針對(duì)的是響應(yīng)式對(duì)象(reactive封裝的對(duì)象)
  • 本質(zhì):不創(chuàng)建響應(yīng)式(創(chuàng)建是ref和reactive的事),而是延續(xù)響應(yīng)式

4. watch和watchEffect的區(qū)別

  1. watchwatchEffect都可以監(jiān)聽data的變化
  2. watch需要指定監(jiān)聽的屬性,默認(rèn)初始時(shí)不會(huì)觸發(fā),如果初始要觸發(fā),需要配置immediate: true
  3. watchEffect是不需要指定監(jiān)聽的屬性,而是自動(dòng)監(jiān)聽其用到的屬性,它初始化時(shí),一定會(huì)執(zhí)行一次,這是為了收集要監(jiān)聽的屬性
<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
    })
    // 監(jiān)聽ref變量
    watch(numberRef, (newVal, oldVal) => {
      console.log('watch:', newVal, oldVal);  
      // watch: 1000 undefined
      // watch: 200 1000
    }, {
      immediate: true // 第三個(gè)參數(shù)可選,是一些配置項(xiàng),immediate表示初始化時(shí)就執(zhí)行監(jiān)聽
    })

    setTimeout(() => {
      numberRef.value = 200
    }, 1000)

    // 監(jiān)聽對(duì)象
    watch(
      // 第一參數(shù)是監(jiān)聽對(duì)象,如果是對(duì)象需要使用函數(shù)返回形式
      () => state.age,
      // 第二個(gè)參數(shù)是監(jiān)聽的變化值
      (newVal, oldVal) => {
        console.log('watch:', newVal, oldVal);  
        // watch: 3 undefined
        // watch: 18 3
      },
      // 第三個(gè)參數(shù)是配置項(xiàng)
      {
        immediate: true, // 初始變化就監(jiān)聽
        deep: true // 深度監(jiān)聽
      }
    )
    setTimeout(() => {
      state.age = 18
    }, 1000)

    return {
      numberRef,
      ...toRefs(state)
    }
  }
}
</script>
watch
  // watchEffect監(jiān)聽
    watchEffect(() => {
      console.log('watchEffect');
      console.log(numberRef.value);
      console.log(state.age);
    })
watchEffect

5. 在setup中怎么獲取組件實(shí)例

  • setup和其它compostion API中沒有this
  • 如果一定要獲取,要使用getCurrentInstance獲取,并且在掛載后才可獲取數(shù)據(jù)
  • 如果是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來獲取組件實(shí)例
  // 且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升級(jí)了哪些重要的功能

參考官網(wǎng)升遷指南

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向父組件發(fā)出事件
  2. 在子組件中,需要emits聲明向父組件發(fā)出的事件集合
<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>
  <!-- 可以同時(shí)觸發(fā)多個(gè)事件 -->
  <button @click="one($event), two($event)">觸發(fā)多事件</button>
</template>

fragment

vue2只允許template中只有一個(gè)元素,如果多個(gè)元素,必須用一個(gè)元素包裹
vue3則允許template中可以直接有多個(gè)元素,這樣就可以減少dom層級(jí)

移除.sync

vue2中的.sync

vue2中使用.sync是對(duì)以下語句的語法糖,父組件通過v-bind:xxx.sync='xxx'來向子組件說明這個(gè)屬性是雙向綁定的,子組件中通過$emit('update:xxx', newVal)來更新這個(gè)值

<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方式說明這個(gè)屬性是雙向綁定的,子組件中通過$emit('update:xxx', newVal)來更新這個(gè)值

<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>

異步組件的導(dǎo)入方式不一樣

vue2child: () => import('child.vue')
vue3:需要defineAsyncComponent導(dǎo)入,child: defineAsyncComponent(() => import('child.vue'))

teleport

teleport將我們的模板移動(dòng)到DOMVue app之外的其他位置,比如可以使用teleport標(biāo)簽將組件在body

<template>
  <p>這是放在當(dāng)前組件下的內(nèi)容</p>
  <teleport to="body">
    <p>假設(shè)這是個(gè)彈窗,直接放到body下</p> 
  </teleport>
</template>
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容