最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue3一些不常用卻很實(shí)用API匯總

 更新時(shí)間:2026年02月13日 09:59:26   作者:瓏墨  
Vue3提供了許多常用的API,這些API可以幫助開(kāi)發(fā)者更高效地構(gòu)建和管理Vue應(yīng)用,這篇文章主要介紹了vue3一些不常用卻很實(shí)用API的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

最近在重構(gòu)一個(gè)老移動(dòng)端項(xiàng)目,用Vue3重寫的過(guò)程中,發(fā)現(xiàn)了一些平時(shí)不太注意的API,但用起來(lái)真的很香。今天分享幾個(gè)我覺(jué)得特別實(shí)用的,希望能幫到正在學(xué)習(xí)Vue3的jym。

1. shallowRef 和 shallowReactive - ‘大數(shù)據(jù)’性能提升

先說(shuō)個(gè)場(chǎng)景:你有一個(gè)很大的對(duì)象,里面嵌套了很多層數(shù)據(jù),但你只需要最外層的變化觸發(fā)更新。這時(shí)候用 refreactive 就會(huì)把所有嵌套都變成響應(yīng)式的,性能開(kāi)銷很大。

import { ref, shallowRef, reactive, shallowReactive, triggerRef } from 'vue'

// 普通 ref - 深度響應(yīng)式
const deepData = ref({
  user: {
    profile: {
      name: '張三',
      address: {
        city: '北京',
        // ... 還有很多層
      }
    }
  }
})

// shallowRef - 淺層響應(yīng)式,只監(jiān)聽(tīng)第一層
const shallowData = shallowRef({
  user: {
    profile: {
      name: '李四'
    }
  }
})

// 只有直接替換整個(gè)對(duì)象才會(huì)觸發(fā)更新
shallowData.value = { user: { profile: { name: '王五' } } } // ? 會(huì)觸發(fā)更新

// 修改內(nèi)部屬性不會(huì)觸發(fā)更新
shallowData.value.user.profile.name = '趙六' // ? 不會(huì)觸發(fā)更新

// 但可以用 triggerRef 手動(dòng)觸發(fā)更新
shallowData.value.user.profile.name = '趙六'
triggerRef(shallowData) // ? 手動(dòng)觸發(fā)更新,視圖會(huì)刷新

// shallowReactive - 淺層響應(yīng)式的 reactive 版本
const shallowState = shallowReactive({
  user: {
    name: '張三',
    age: 20
  }
})

shallowState.user = { name: '李四', age: 25 } // ? 會(huì)觸發(fā)更新
shallowState.user.name = '王五' // ? 不會(huì)觸發(fā)更新

我在處理表格數(shù)據(jù)的時(shí)候經(jīng)常用這個(gè),特別是那種幾千行的數(shù)據(jù),用 shallowRef 能明顯感覺(jué)到性能提升。shallowReactiveshallowRef 的區(qū)別在于,前者直接返回響應(yīng)式對(duì)象,后者返回的是包裝后的 ref。

triggerRef 的使用場(chǎng)景:當(dāng)你使用 shallowRef 時(shí),如果確實(shí)需要修改嵌套屬性并觸發(fā)更新,但又不想替換整個(gè)對(duì)象(可能很耗性能),就可以先修改屬性,然后調(diào)用 triggerRef 手動(dòng)觸發(fā)更新。這樣既享受了淺層響應(yīng)式的性能優(yōu)勢(shì),又能在需要時(shí)精確控制更新時(shí)機(jī)。

// 實(shí)際應(yīng)用:批量修改后統(tǒng)一觸發(fā)更新
const tableData = shallowRef([
  { id: 1, name: '張三', status: 'active' },
  { id: 2, name: '李四', status: 'active' },
  // ... 幾千條數(shù)據(jù)
])

// 批量修改多個(gè)嵌套屬性
tableData.value.forEach(item => {
  item.status = 'inactive' // 修改嵌套屬性
  item.updatedAt = Date.now() // 繼續(xù)修改
})

// 所有修改完成后,統(tǒng)一觸發(fā)一次更新(性能更好)
triggerRef(tableData) // ? 只觸發(fā)一次更新,而不是每條數(shù)據(jù)都觸發(fā)

2. markRaw - 讓某些數(shù)據(jù)"免疫"響應(yīng)式

有些數(shù)據(jù)你明確知道不需要響應(yīng)式,比如第三方庫(kù)的實(shí)例、DOM元素、或者一些配置對(duì)象。用 markRaw 標(biāo)記后,Vue就不會(huì)把它變成響應(yīng)式的。

import { reactive, markRaw } from 'vue'

const state = reactive({
  // 這個(gè)第三方庫(kù)實(shí)例不需要響應(yīng)式
  chart: markRaw(new ChartLibrary()),
  
  // 這個(gè)配置對(duì)象也不需要
  config: markRaw({
    apiUrl: 'https://api.example.com',
    timeout: 5000
  }),
  
  // 這個(gè)需要響應(yīng)式
  count: 0
})

// 即使把 chart 放到 reactive 里,它也不會(huì)變成響應(yīng)式
// 這樣既避免了不必要的性能開(kāi)銷,也防止了某些庫(kù)因?yàn)轫憫?yīng)式代理而出錯(cuò)

我之前用 ECharts 的時(shí)候就遇到過(guò)這個(gè)問(wèn)題,圖表實(shí)例被響應(yīng)式代理后各種報(bào)錯(cuò),用 markRaw 一包就解決了。

3. toRaw - 獲取原始對(duì)象

有時(shí)候你需要拿到響應(yīng)式對(duì)象的原始值,比如傳給第三方庫(kù)、或者做深拷貝。toRaw 就是干這個(gè)的。

import { reactive, toRaw } from 'vue'

const state = reactive({
  name: 'Vue3',
  version: '3.0'
})

// 獲取原始對(duì)象,不再是 Proxy
const rawState = toRaw(state)

// 傳給需要普通對(duì)象的函數(shù)
someThirdPartyLibrary.process(rawState)

// 或者做深拷貝
const copy = JSON.parse(JSON.stringify(toRaw(state)))

注意,toRaw 只能拿到一層,如果對(duì)象里還有嵌套的響應(yīng)式對(duì)象,那些還是 Proxy。需要遞歸處理的話,可以配合 markRaw 使用。

4. customRef - 自定義響應(yīng)式引用

這個(gè)API特別適合做防抖、節(jié)流,或者需要自定義 get/set 邏輯的場(chǎng)景。

import { customRef } from 'vue'

// 防抖的 ref
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track() // 追蹤依賴
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger() // 觸發(fā)更新
        }, delay)
      }
    }
  })
}

// 使用
const searchText = useDebouncedRef('')

// 用戶輸入時(shí)不會(huì)立即更新,等停止輸入 200ms 后才更新

我在搜索框里經(jīng)常用這個(gè),避免用戶每輸入一個(gè)字符就發(fā)一次請(qǐng)求。

5. readonly - 創(chuàng)建只讀的響應(yīng)式對(duì)象

有時(shí)候你想讓某些數(shù)據(jù)是響應(yīng)式的(能追蹤變化),但不允許修改。readonly 就派上用場(chǎng)了。

import { reactive, readonly } from 'vue'

const original = reactive({
  count: 0,
  name: 'Vue3'
})

// 創(chuàng)建只讀版本
const readOnlyData = readonly(original)

// 可以讀取
console.log(readOnlyData.count) // 0

// 不能修改(開(kāi)發(fā)環(huán)境會(huì)警告)
readOnlyData.count = 1 // ?? 警告:Set operation on key "count" failed

// 但修改原始對(duì)象,只讀版本也會(huì)更新
original.count = 1
console.log(readOnlyData.count) // 1 ?

這個(gè)在組件間傳遞 props 的時(shí)候很有用,特別是你想確保子組件不會(huì)意外修改父組件的數(shù)據(jù)。

6. getCurrentInstance - 獲取當(dāng)前組件實(shí)例

這個(gè)API在組合式API里獲取組件實(shí)例特別方便,雖然官方文檔說(shuō)主要給庫(kù)開(kāi)發(fā)者用,但實(shí)際開(kāi)發(fā)中偶爾也會(huì)用到。

import { getCurrentInstance } from 'vue'

export default {
  setup() {
    const instance = getCurrentInstance()
    
    // 獲取組件的 props
    console.log(instance.props)
    
    // 獲取組件的 attrs
    console.log(instance.attrs)
    
    // 獲取組件的 emit
    instance.emit('custom-event')
    
    // 訪問(wèn)全局屬性
    const $router = instance.appContext.config.globalProperties.$router
    
    return {}
  }
}

不過(guò)要注意,getCurrentInstance 只能在 setup 或生命周期鉤子里調(diào)用,而且不能緩存到異步回調(diào)里用。

7. defineAsyncComponent - 異步組件加載

這個(gè)其實(shí)不算特別冷門,但很多人可能沒(méi)深入用過(guò)。除了基本的懶加載,它還有很多高級(jí)用法。

import { defineAsyncComponent } from 'vue'

// 基礎(chǔ)用法
const AsyncComponent = defineAsyncComponent(() => import('./HeavyComponent.vue'))

// 帶加載狀態(tài)和錯(cuò)誤處理
const AsyncComponentWithStates = defineAsyncComponent({
  loader: () => import('./HeavyComponent.vue'),
  loadingComponent: LoadingSpinner, // 加載時(shí)顯示的組件
  errorComponent: ErrorComponent,   // 錯(cuò)誤時(shí)顯示的組件
  delay: 200,                       // 延遲顯示 loading,避免閃爍
  timeout: 3000,                    // 超時(shí)時(shí)間
  onError(error, retry, fail, attempts) {
    // 錯(cuò)誤處理邏輯
    if (attempts <= 3) {
      retry() // 重試
    } else {
      fail()  // 放棄
    }
  }
})

我在做路由懶加載和按需加載大組件的時(shí)候,這個(gè)API幫了不少忙。

8. watchEffect 的立即執(zhí)行和清理

watchEffect 大家都知道,但它的立即執(zhí)行特性和清理函數(shù)可能很多人沒(méi)充分利用。

import { watchEffect, ref } from 'vue'

const count = ref(0)

const stop = watchEffect((onInvalidate) => {
  // 這個(gè)函數(shù)會(huì)立即執(zhí)行一次
  console.log('count:', count.value)
  
  // 清理函數(shù):在下次執(zhí)行前或停止監(jiān)聽(tīng)時(shí)調(diào)用
  onInvalidate(() => {
    console.log('清理之前的副作用')
    // 比如取消請(qǐng)求、清除定時(shí)器等
  })
})

// 手動(dòng)停止監(jiān)聽(tīng)
stop()

這個(gè)特性在處理副作用的時(shí)候特別有用,比如監(jiān)聽(tīng)數(shù)據(jù)變化自動(dòng)發(fā)送請(qǐng)求,可以在清理函數(shù)里取消之前的請(qǐng)求,避免競(jìng)態(tài)條件。

import { watchEffect, ref } from 'vue'

const userId = ref(1)
let abortController = null

watchEffect((onInvalidate) => {
  // 取消之前的請(qǐng)求
  if (abortController) {
    abortController.abort()
  }
  
  // 創(chuàng)建新的請(qǐng)求
  abortController = new AbortController()
  
  fetch(`/api/user/${userId.value}`, {
    signal: abortController.signal
  })
    .then(res => res.json())
    .then(data => {
      // 處理數(shù)據(jù)
    })
  
  // 清理函數(shù):在下次執(zhí)行前或組件卸載時(shí)調(diào)用
  onInvalidate(() => {
    if (abortController) {
      abortController.abort()
    }
  })
})

這樣就能避免快速切換用戶ID時(shí),舊的請(qǐng)求覆蓋新請(qǐng)求結(jié)果的問(wèn)題。

9. unref、isRef、isReactive - 類型檢查工具

這些工具函數(shù)在處理可能是響應(yīng)式也可能不是的數(shù)據(jù)時(shí)特別有用。

import { ref, reactive, unref, isRef, isReactive, isReadonly } from 'vue'

// unref - 如果是 ref 就返回 .value,否則返回原值
function useValue(maybeRef) {
  return unref(maybeRef) // 不用判斷是不是 ref,直接 unref 就行
}

const count = ref(10)
const name = 'Vue3'

console.log(useValue(count))  // 10
console.log(useValue(name))   // 'Vue3'

// isRef - 判斷是否是 ref
if (isRef(count)) {
  console.log('這是一個(gè) ref')
}

// isReactive - 判斷是否是 reactive 對(duì)象
const state = reactive({ count: 0 })
if (isReactive(state)) {
  console.log('這是一個(gè) reactive 對(duì)象')
}

// isReadonly - 判斷是否是 readonly 對(duì)象
const readonlyState = readonly(state)
if (isReadonly(readonlyState)) {
  console.log('這是一個(gè) readonly 對(duì)象')
}

在寫工具函數(shù)或者組合式函數(shù)的時(shí)候,這些檢查函數(shù)能讓你寫出更健壯的代碼。

10. effectScope 和 onScopeDispose - 批量管理副作用

effectScope 是 Vue3.2 新增的,可以批量管理一組響應(yīng)式效果(computed、watch、watchEffect等),統(tǒng)一停止或清理。

import { effectScope, ref, watchEffect, computed, onScopeDispose } from 'vue'

function useMouse() {
  const x = ref(0)
  const y = ref(0)
  
  // 創(chuàng)建一個(gè)作用域
  const scope = effectScope()
  
  scope.run(() => {
    // 在這個(gè)作用域內(nèi)的所有響應(yīng)式效果都會(huì)被統(tǒng)一管理
    watchEffect(() => {
      const handler = (e) => {
        x.value = e.clientX
        y.value = e.clientY
      }
      window.addEventListener('mousemove', handler)
      
      // 清理函數(shù)
      onScopeDispose(() => {
        window.removeEventListener('mousemove', handler)
      })
    })
    
    // 也可以有多個(gè) watchEffect、computed 等
    const distance = computed(() => {
      return Math.sqrt(x.value ** 2 + y.value ** 2)
    })
  })
  
  // 返回停止函數(shù)
  return {
    x,
    y,
    stop: () => scope.stop() // 一次性停止所有效果
  }
}

這個(gè)在寫組合式函數(shù)的時(shí)候特別有用,可以確保所有副作用都能被正確清理,避免內(nèi)存泄漏。

11. provide/inject 的響應(yīng)式傳遞

provide/inject 大家都知道,但很多人不知道它可以傳遞響應(yīng)式數(shù)據(jù),而且子組件可以直接修改。

// 父組件
import { provide, ref, reactive } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = reactive({ name: '張三', age: 20 })
    
    // 提供響應(yīng)式數(shù)據(jù)
    provide('count', count)
    provide('user', user)
    
    // 也可以提供修改函數(shù)
    provide('increment', () => {
      count.value++
    })
    
    return { count, user }
  }
}

// 子組件(可以是任意深度的子組件)
import { inject, ref } from 'vue'

export default {
  setup() {
    // 注入響應(yīng)式數(shù)據(jù),可以設(shè)置默認(rèn)值
    const count = inject('count', ref(0)) // 如果父組件沒(méi)有提供,使用默認(rèn)值
    const user = inject('user', () => ({ name: '默認(rèn)用戶', age: 0 })) // 也可以使用工廠函數(shù)
    const increment = inject('increment', () => {
      console.warn('increment 方法未提供')
    })
    
    // 可以直接修改(如果父組件提供的是響應(yīng)式數(shù)據(jù))
    const handleClick = () => {
      count.value++ // ? 父組件的 count 也會(huì)更新
      user.name = '李四' // ? 父組件的 user 也會(huì)更新
      // 或者調(diào)用父組件提供的方法
      increment()
    }
    
    return { count, user, handleClick }
  }
}

這個(gè)在跨層級(jí)組件通信的時(shí)候特別方便,比 props 一層層傳要優(yōu)雅得多。

12. v-memo - 性能優(yōu)化的指令

v-memo 是 Vue3.2 新增的指令,可以緩存模板的一部分,只有在依賴項(xiàng)變化時(shí)才重新渲染。

<template>
  <!-- 只有 item.id 或 selected 變化時(shí)才重新渲染這個(gè) div -->
  <div v-for="item in list" :key="item.id" v-memo="[item.id, selected]">
    <p>{{ item.name }}</p>
    <p>{{ item.description }}</p>
    <!-- 復(fù)雜的內(nèi)容 -->
    <ExpensiveComponent :data="item.data" />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const list = ref([
  { id: 1, name: 'Item 1', description: '...', data: {...} },
  { id: 2, name: 'Item 2', description: '...', data: {...} },
  // ...
])

const selected = ref(null)
</script>

這個(gè)在處理長(zhǎng)列表的時(shí)候特別有用,可以大幅提升性能。不過(guò)要注意,v-memo 必須和 v-for 一起使用。

13. Suspense - 異步組件的加載狀態(tài)

Suspense 可以優(yōu)雅地處理異步組件的加載狀態(tài),不需要在每個(gè)組件里單獨(dú)處理。

<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <div class="loading">加載中...</div>
    </template>
  </Suspense>
</template>

<script setup>
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent(() => 
  import('./HeavyComponent.vue')
)
</script>

如果異步組件內(nèi)部有異步操作(比如 setup 里用了 await),Suspense 也會(huì)等待:

<!-- AsyncComponent.vue -->
<script setup>
// 這個(gè)組件內(nèi)部有異步操作
const data = await fetchData() // Suspense 會(huì)等待這個(gè)完成
</script>

這個(gè)在處理異步數(shù)據(jù)加載的時(shí)候特別優(yōu)雅,比手動(dòng)管理 loading 狀態(tài)要方便。

14. 模板引用的高級(jí)用法

模板引用(ref)除了獲取 DOM 元素,還有很多高級(jí)用法。

<template>
  <!-- 在 v-for 中獲取多個(gè)元素 -->
  <div v-for="item in list" :ref="el => setItemRef(el, item.id)">
    {{ item.name }}
  </div>
  
  <!-- 獲取子組件實(shí)例 -->
  <ChildComponent ref="childRef" />
</template>

<script setup>
import { ref, onMounted } from 'vue'

const list = ref([
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' },
])

// 存儲(chǔ)多個(gè)元素引用
const itemRefs = ref({})

const setItemRef = (el, id) => {
  if (el) {
    itemRefs.value[id] = el
  }
}

// 子組件引用
const childRef = ref(null)

onMounted(() => {
  // 訪問(wèn)子組件的方法或?qū)傩?
  console.log(childRef.value.someMethod())
  console.log(childRef.value.someProperty)
  
  // 訪問(wèn)所有列表項(xiàng)的 DOM
  console.log(itemRefs.value)
})
</script>

還可以配合 defineExpose 暴露子組件的特定內(nèi)容:

<!-- ChildComponent.vue -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const privateData = ref('private') // 這個(gè)不會(huì)被暴露

const increment = () => {
  count.value++
}

// 只暴露 count 和 increment
defineExpose({
  count,
  increment
})
</script>

15. computed 的 getter/setter 形式

computed 除了只讀形式,還可以有 getter 和 setter,實(shí)現(xiàn)雙向綁定。

import { ref, computed } from 'vue'

const firstName = ref('張')
const lastName = ref('三')

// 可讀寫的 computed
const fullName = computed({
  get() {
    return `${firstName.value}${lastName.value}`
  },
  set(newValue) {
    // 當(dāng)設(shè)置 fullName 時(shí),自動(dòng)拆分到 firstName 和 lastName
    const parts = newValue.split('')
    if (parts.length >= 2) {
      firstName.value = parts[0]
      lastName.value = parts.slice(1).join('')
    }
  }
})

// 使用
console.log(fullName.value) // '張三'
fullName.value = '李四' // 自動(dòng)更新 firstName 和 lastName
console.log(firstName.value) // '李'
console.log(lastName.value) // '四'

這個(gè)在處理表單數(shù)據(jù)轉(zhuǎn)換的時(shí)候特別有用。

16. watch 的深度監(jiān)聽(tīng)和立即執(zhí)行

watch 的選項(xiàng)參數(shù)有很多實(shí)用的配置。

import { watch, ref, reactive } from 'vue'

const count = ref(0)
const state = reactive({
  user: {
    name: '張三',
    age: 20
  }
})

// 深度監(jiān)聽(tīng)對(duì)象
watch(
  () => state.user,
  (newVal, oldVal) => {
    console.log('user 變化了', newVal, oldVal)
  },
  { deep: true } // 深度監(jiān)聽(tīng)嵌套屬性
)

// 或者直接監(jiān)聽(tīng)整個(gè)對(duì)象
watch(
  state,
  (newVal, oldVal) => {
    // 注意:newVal 和 oldVal 是同一個(gè)對(duì)象(因?yàn)?reactive)
    console.log('state 變化了')
  },
  { deep: true }
)

// 立即執(zhí)行一次
watch(
  count,
  (newVal, oldVal) => {
    console.log('count:', newVal)
  },
  { immediate: true } // 立即執(zhí)行一次回調(diào)
)

// 監(jiān)聽(tīng)多個(gè)源
watch(
  [count, () => state.user.name],
  ([newCount, newName], [oldCount, oldName]) => {
    console.log('count 或 name 變化了')
  }
)

// 手動(dòng)實(shí)現(xiàn)只監(jiān)聽(tīng)一次的效果
let stopWatcher = null
stopWatcher = watch(
  count,
  (newVal) => {
    console.log('只執(zhí)行一次')
    // 執(zhí)行后立即停止監(jiān)聽(tīng)
    if (stopWatcher) {
      stopWatcher()
      stopWatcher = null
    }
  }
)

17. nextTick 的多種用法

nextTick 大家都知道,但它的使用場(chǎng)景可能很多人沒(méi)完全掌握。

import { nextTick, ref } from 'vue'

const count = ref(0)
const message = ref('')

// 等待 DOM 更新后執(zhí)行
async function handleClick() {
  count.value++
  message.value = 'Count 已更新'
  
  // 此時(shí) DOM 可能還沒(méi)更新
  console.log(document.querySelector('.count').textContent) // 可能是舊值
  
  // 等待 DOM 更新
  await nextTick()
  console.log(document.querySelector('.count').textContent) // 新值
  
  // 或者用回調(diào)形式
  nextTick(() => {
    console.log('DOM 已更新')
  })
}

// 在組件掛載后操作 DOM
import { onMounted } from 'vue'

onMounted(async () => {
  // 即使組件已掛載,某些 DOM 操作可能還需要等待
  await nextTick()
  // 現(xiàn)在可以安全地操作 DOM 了
  document.querySelector('.some-element').focus()
})

18. 響應(yīng)式 API 的轉(zhuǎn)換工具

Vue3 提供了一些轉(zhuǎn)換工具,可以在不同響應(yīng)式類型之間轉(zhuǎn)換。

import { ref, reactive, toRef, toRefs, isRef } from 'vue'

const state = reactive({
  name: 'Vue3',
  version: '3.0',
  count: 0
})

// toRef - 將 reactive 對(duì)象的某個(gè)屬性轉(zhuǎn)為 ref
const nameRef = toRef(state, 'name')
nameRef.value = 'Vue 3' // 會(huì)同步更新 state.name

// toRefs - 將 reactive 對(duì)象的所有屬性轉(zhuǎn)為 ref
const { name, version, count } = toRefs(state)
// 現(xiàn)在 name、version、count 都是 ref,可以在模板中直接使用

// 在組合式函數(shù)中返回響應(yīng)式數(shù)據(jù)時(shí)特別有用
function useCounter() {
  const state = reactive({
    count: 0,
    step: 1
  })
  
  const increment = () => {
    state.count += state.step
  }
  
  // 返回 toRefs,讓使用者可以解構(gòu)而不失去響應(yīng)式
  return {
    ...toRefs(state),
    increment
  }
}

// 使用時(shí)可以解構(gòu)
const { count, step, increment } = useCounter()
// count 和 step 仍然是響應(yīng)式的

19. 自定義指令的高級(jí)用法

自定義指令除了基本的 mountedupdated,還有很多生命周期鉤子。

// 自定義指令直接定義對(duì)象即可,不需要特殊導(dǎo)入
const vMyDirective = {
  // 元素插入 DOM 前調(diào)用
  created(el, binding, vnode) {
    console.log('created')
  },
  
  // 元素插入 DOM 后調(diào)用
  mounted(el, binding, vnode, prevVnode) {
    console.log('mounted', binding.value)
    // binding.value - 指令的值
    // binding.oldValue - 之前的值(僅在 update 和 componentUpdated 中可用)
    // binding.arg - 指令參數(shù),如 v-my-directive:foo 中的 'foo'
    // binding.modifiers - 修飾符對(duì)象,如 v-my-directive.foo.bar 中的 { foo: true, bar: true }
  },
  
  // 在包含組件的 VNode 更新前調(diào)用
  beforeUpdate(el, binding, vnode, prevVnode) {
    console.log('beforeUpdate')
  },
  
  // 在包含組件的 VNode 及其子 VNode 更新后調(diào)用
  updated(el, binding, vnode, prevVnode) {
    console.log('updated')
  },
  
  // 在卸載前調(diào)用
  beforeUnmount(el, binding, vnode) {
    console.log('beforeUnmount')
  },
  
  // 卸載后調(diào)用
  unmounted(el, binding, vnode) {
    console.log('unmounted')
  }
}

// 使用
// <div v-my-directive:arg.modifier="value"></div>

實(shí)際應(yīng)用:實(shí)現(xiàn)一個(gè)點(diǎn)擊外部關(guān)閉的指令

const vClickOutside = {
  mounted(el, binding) {
    el._clickOutside = (event) => {
      if (!(el === event.target || el.contains(event.target))) {
        binding.value() // 調(diào)用傳入的函數(shù)
      }
    }
    document.addEventListener('click', el._clickOutside)
  },
  unmounted(el) {
    document.removeEventListener('click', el._clickOutside)
  }
}

20. 組合式 API 的類型推斷技巧

在 TypeScript 中使用組合式 API 時(shí),有一些類型推斷的技巧。

import { ref, computed, Ref, ComputedRef } from 'vue'

// 明確指定 ref 的類型
const count: Ref<number> = ref(0)
// 或者讓 TypeScript 自動(dòng)推斷
const count = ref<number>(0)

// computed 的類型
const doubleCount: ComputedRef<number> = computed(() => count.value * 2)

// 在組合式函數(shù)中返回類型
function useCounter(initialValue: number = 0) {
  const count = ref(initialValue)
  
  const increment = () => {
    count.value++
  }
  
  return {
    count: count as Ref<number>, // 或者用 readonly
    increment
  }
}

// 使用泛型
function useAsyncData<T>(url: string) {
  const data = ref<T | null>(null)
  const loading = ref(false)
  const error = ref<Error | null>(null)
  
  const fetchData = async () => {
    loading.value = true
    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }
  
  return {
    data: readonly(data),
    loading: readonly(loading),
    error: readonly(error),
    fetchData
  }
}

21. 性能優(yōu)化的組合拳

把這些 API 組合起來(lái),可以實(shí)現(xiàn)更強(qiáng)大的性能優(yōu)化。

import { shallowRef, markRaw, computed, watchEffect } from 'vue'

// 場(chǎng)景:處理大量數(shù)據(jù)列表
function useLargeList(initialData) {
  // 用 shallowRef 避免深度響應(yīng)式
  const items = shallowRef(initialData.map(item => ({
    ...item,
    // 某些不需要響應(yīng)式的數(shù)據(jù)用 markRaw
    metadata: markRaw(item.metadata)
  })))
  
  // 用 computed 緩存計(jì)算結(jié)果
  const filteredItems = computed(() => {
    return items.value.filter(item => item.visible)
  })
  
  // 用 watchEffect 處理副作用,自動(dòng)清理
  const stop = watchEffect((onInvalidate) => {
    // 處理過(guò)濾后的數(shù)據(jù)
    processItems(filteredItems.value)
    
    onInvalidate(() => {
      // 清理工作
      cleanup()
    })
  })
  
  return {
    items,
    filteredItems,
    stop
  }
}

22. 實(shí)際項(xiàng)目中的最佳實(shí)踐

結(jié)合我自己的項(xiàng)目經(jīng)驗(yàn),分享幾個(gè)實(shí)用的模式:

模式1:響應(yīng)式數(shù)據(jù)的初始化

import { ref, reactive, onMounted, readonly } from 'vue'

function useUserData(userId) {
  const user = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const fetchUser = async () => {
    loading.value = true
    error.value = null
    try {
      const response = await fetch(`/api/users/${userId}`)
      user.value = await response.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }
  
  // 組件掛載時(shí)自動(dòng)獲取
  onMounted(fetchUser)
  
  return {
    user: readonly(user),
    loading: readonly(loading),
    error: readonly(error),
    refetch: fetchUser
  }
}

模式2:表單驗(yàn)證

import { ref, computed, reactive, readonly } from 'vue'

function useFormValidation() {
  const form = reactive({
    email: '',
    password: '',
    confirmPassword: ''
  })
  
  const errors = reactive({})
  
  const validateEmail = (email) => {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
  }
  
  // 用 computed 實(shí)時(shí)驗(yàn)證
  const isValid = computed(() => {
    errors.email = form.email && !validateEmail(form.email) 
      ? '郵箱格式不正確' 
      : ''
    
    errors.password = form.password.length < 6 
      ? '密碼至少6位' 
      : ''
    
    errors.confirmPassword = form.password !== form.confirmPassword 
      ? '兩次密碼不一致' 
      : ''
    
    return !Object.values(errors).some(err => err)
  })
  
  return {
    form,
    errors: readonly(errors),
    isValid
  }
}

模式3:防抖和節(jié)流的組合使用

import { customRef, watch } from 'vue'

// 防抖 ref - 基于 customRef 實(shí)現(xiàn)
function useDebounceRef(value, delay = 300) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

// 節(jié)流函數(shù) - 配合 watch 使用(這是純 JS 函數(shù),不是 Vue3 API,但經(jīng)常和 Vue3 一起用)
function useThrottleFn(fn, delay = 300) {
  let lastTime = 0
  return function(...args) {
    const now = Date.now()
    if (now - lastTime >= delay) {
      fn.apply(this, args)
      lastTime = now
    }
  }
}

// 使用
const searchText = useDebounceRef('')

watch(searchText, (newVal) => {
  // 搜索邏輯,只在用戶停止輸入 300ms 后執(zhí)行
  performSearch(newVal)
})

const handleScroll = useThrottleFn(() => {
  // 滾動(dòng)處理,每 300ms 最多執(zhí)行一次
  updateScrollPosition()
}, 100)

版本要求說(shuō)明

部分 API 需要特定版本的 Vue3:

  • Vue 3.2+effectScope、onScopeDispose、v-memo
  • Vue 3.0+:其他大部分 API

建議使用 Vue 3.2 或更高版本,以獲得完整的 API 支持。

常見(jiàn)陷阱和注意事項(xiàng)

  1. shallowRef/shallowReactive 的陷阱:修改嵌套屬性不會(huì)觸發(fā)更新,如果需要更新,要替換整個(gè)對(duì)象。

  2. toRaw 的局限性:只能獲取一層的原始對(duì)象,嵌套的響應(yīng)式對(duì)象仍然是 Proxy。

  3. getCurrentInstance 的使用限制:只能在 setup 或生命周期鉤子中調(diào)用,不能緩存到異步回調(diào)中使用。

  4. inject 的默認(rèn)值:如果父組件沒(méi)有提供值,inject 會(huì)返回 undefined,建議總是提供默認(rèn)值。

  5. watchEffect 的依賴追蹤:會(huì)自動(dòng)追蹤所有在函數(shù)內(nèi)訪問(wèn)的響應(yīng)式數(shù)據(jù),注意不要訪問(wèn)不需要追蹤的數(shù)據(jù)。

  6. computed 的副作用:不要在 computed 中執(zhí)行副作用操作(如修改 DOM、發(fā)送請(qǐng)求等),應(yīng)該使用 watchwatchEffect

總結(jié)下

有些API你可能不常用,但在特定場(chǎng)景下真的能解決實(shí)際問(wèn)題。性能優(yōu)化、邊界情況處理、自定義邏輯,這些場(chǎng)景下它們都能派上用場(chǎng)。

不過(guò)也要注意,不要過(guò)度使用。比如 shallowRef 雖然性能好,但如果你確實(shí)需要深度響應(yīng)式,就不要為了性能而犧牲功能。工具是死的,場(chǎng)景是活的,根據(jù)實(shí)際需求選擇最合適的方案才是王道。。。

到此這篇關(guān)于vue3一些不常用卻很實(shí)用API的文章就介紹到這了,更多相關(guān)vue3實(shí)用API內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

谷城县| 五家渠市| 永仁县| 孟连| 宁海县| 株洲市| 商南县| 东丽区| 和林格尔县| 梨树县| 丰镇市| 德令哈市| 社旗县| 务川| 梓潼县| 余姚市| 崇义县| 墨脱县| 封丘县| 漠河县| 井冈山市| 荆州市| 汨罗市| 炎陵县| 宜兰市| 宁波市| 鸡东县| 盖州市| 昆明市| 仪陇县| 林甸县| 龙山县| 甘洛县| 高尔夫| 万安县| 攀枝花市| 绵阳市| 连平县| 淄博市| 南开区| 鄢陵县|