自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

在vue3中如何編寫一個(gè)標(biāo)準(zhǔn)的hooks?

開(kāi)發(fā) 前端
抽取 Hooks 方法可以提高代碼的復(fù)用性、可維護(hù)性和測(cè)試性,當(dāng)遇到上述情況時(shí),考慮抽取 Hooks 是一個(gè)很好的實(shí)踐。

前言

在 Vue 3 中,組合式 API 為開(kāi)發(fā)者提供了更加靈活和高效的方式來(lái)組織和復(fù)用邏輯,其中 Hooks 是一個(gè)重要的概念。Hooks 允許我們將組件中的邏輯提取出來(lái),使其更具可復(fù)用性和可讀性,讓我們的代碼編寫更加靈活。

hooks的定義

其實(shí),事實(shí)上官方并未管這種方式叫做hooks,而似乎更應(yīng)該叫做compositions更加確切些,更加符合vue3的設(shè)計(jì)初衷。由于react的hooks設(shè)計(jì)理念在前,而vue3的組合式使用也像一個(gè)hook鉤子掛載vue框架的生命周期中,對(duì)此習(xí)慣性地稱作hooks。

對(duì)于onMounted、onUnMounted等響應(yīng)式API都必須在setup階段進(jìn)行同步調(diào)用。

圖片圖片

要理解 Vue 3 中的 Hooks,需要明白它的本質(zhì)是一個(gè)函數(shù),這個(gè)函數(shù)可以包含與組件相關(guān)的狀態(tài)和副作用操作。

  • 狀態(tài)是應(yīng)用中存儲(chǔ)的數(shù)據(jù),這些數(shù)據(jù)可以影響組件的外觀和行為。在 Vue 3 中,可以使用 ref 和 reactive 來(lái)創(chuàng)建狀態(tài)。
  • 副作用操作是指在應(yīng)用執(zhí)行過(guò)程中會(huì)產(chǎn)生外部可觀察效果的操作,比如數(shù)據(jù)獲取、訂閱事件、定時(shí)器等。這些操作可能會(huì)影響應(yīng)用的狀態(tài)或與外部系統(tǒng)進(jìn)行交互。

記?。篽ooks就是特殊的函數(shù),可以在vue組件外部使用,可以訪問(wèn)vue的響應(yīng)式系統(tǒng)。

vue3中hooks和react的區(qū)別

vue3的compositions和react的hooks還是有所區(qū)別的,對(duì)此官方還特別寫了兩者的比較,原文如下:

圖片圖片

大抵意思如下,Vue Composition API 與 React Hooks 都具有邏輯組合能力,但存在一些重要差異。

React Hooks 的問(wèn)題:

  • 每次組件更新都會(huì)重復(fù)調(diào)用,存在諸多注意事項(xiàng),可能使經(jīng)驗(yàn)豐富的開(kāi)發(fā)者也感到困惑,并導(dǎo)致性能優(yōu)化問(wèn)題。
  • 對(duì)調(diào)用順序敏感且不能有條件調(diào)用。
  • 變量可能因依賴數(shù)組不正確而“過(guò)時(shí)”,開(kāi)發(fā)者需依賴 ESLint 規(guī)則確保正確依賴,但規(guī)則不夠智能,可能過(guò)度補(bǔ)償正確性,遇到邊界情況會(huì)很麻煩。
  • 昂貴的計(jì)算需使用 useMemo,且要手動(dòng)傳入正確依賴數(shù)組。
  • 傳遞給子組件的事件處理程序默認(rèn)會(huì)導(dǎo)致不必要的子組件更新,需要顯式使用 useCallback 和正確的依賴數(shù)組,否則可能導(dǎo)致性能問(wèn)題。陳舊閉包問(wèn)題結(jié)合并發(fā)特性,使理解鉤子代碼何時(shí)運(yùn)行變得困難,處理跨渲染的可變狀態(tài)也很麻煩。

Vue Composition API 的優(yōu)勢(shì):

  • setup() 或 <script setup> 中的代碼僅執(zhí)行一次,不存在陳舊閉包問(wèn)題,調(diào)用順序不敏感且可以有條件調(diào)用。
  • Vue 的運(yùn)行時(shí)響應(yīng)式系統(tǒng)自動(dòng)收集計(jì)算屬性和監(jiān)聽(tīng)器中使用的響應(yīng)式依賴,無(wú)需手動(dòng)聲明依賴。
  • 無(wú)需手動(dòng)緩存回調(diào)函數(shù)以避免不必要的子組件更新,精細(xì)的響應(yīng)式系統(tǒng)確保子組件僅在需要時(shí)更新,手動(dòng)優(yōu)化子組件更新對(duì) Vue 開(kāi)發(fā)者來(lái)說(shuō)很少是問(wèn)題。

自定義hooks需要遵守的原則

那么,在編寫自定義Hooks時(shí),有哪些常見(jiàn)的錯(cuò)誤或者陷阱需要避免?

以下是一些需要注意的點(diǎn):

  1. 狀態(tài)共享問(wèn)題:不要在自定義Hooks內(nèi)部創(chuàng)建狀態(tài)(使用ref或reactive),除非這些狀態(tài)是暴露給使用者的API的一部分。Hooks應(yīng)該是無(wú)狀態(tài)的,避免在Hooks內(nèi)部保存狀態(tài)。
  2. 副作用處理不當(dāng):副作用(例如API調(diào)用、定時(shí)器等)應(yīng)該在生命周期鉤子(如onMounted、onUnmounted)中處理。不要在自定義Hooks的參數(shù)處理或邏輯中直接執(zhí)行副作用。
  3. 過(guò)度依賴外部狀態(tài):自定義Hooks應(yīng)盡量減少對(duì)外部狀態(tài)的依賴。如果必須依賴,確保通過(guò)參數(shù)傳遞,而不是直接訪問(wèn)組件的狀態(tài)或其他全局狀態(tài)。
  4. 參數(shù)驗(yàn)證不足:自定義Hooks應(yīng)該能夠處理無(wú)效或意外的參數(shù)。添加參數(shù)驗(yàn)證邏輯,確保Hooks的魯棒性。
  5. 使用不穩(wěn)定的API:避免使用可能在未來(lái)版本中更改或刪除的API。始終查閱官方文檔,確保你使用的API是穩(wěn)定的。
  6. 性能問(wèn)題:避免在自定義Hooks中進(jìn)行昂貴的操作,如深度比較或復(fù)雜的計(jì)算,這可能會(huì)影響組件的渲染性能。
  7. 重渲染問(wèn)題:確保自定義Hooks不會(huì)由于響應(yīng)式依賴不當(dāng)而導(dǎo)致組件不必要的重渲染。
  8. 命名不一致:自定義Hooks應(yīng)該遵循一致的命名約定,通常是use前綴,以便于識(shí)別和使用。
  9. 過(guò)度封裝:避免創(chuàng)建過(guò)于通用或復(fù)雜的Hooks,這可能會(huì)導(dǎo)致難以理解和維護(hù)的代碼。Hooks應(yīng)該保持簡(jiǎn)單和直觀。
  10. 錯(cuò)誤處理不足:自定義Hooks應(yīng)該能夠妥善處理錯(cuò)誤情況,例如API請(qǐng)求失敗或無(wú)效輸入。
  11. 生命周期鉤子濫用:不要在自定義Hooks中濫用生命周期鉤子,確保只在必要時(shí)使用。
  12. 不遵循單向數(shù)據(jù)流:Hooks應(yīng)該遵循Vue的單向數(shù)據(jù)流原則,避免創(chuàng)建可能導(dǎo)致數(shù)據(jù)流混亂的邏輯。
  13. 忽視類型檢查:使用TypeScript編寫Hooks時(shí),確保進(jìn)行了適當(dāng)?shù)念愋蜋z查和類型推斷。
  14. 使用不恰當(dāng)?shù)捻憫?yīng)式API:例如,使用ref而不是reactive,或者在應(yīng)該使用readonly的場(chǎng)景中使用了可變對(duì)象。
  15. 全局狀態(tài)管理不當(dāng):如果你的Hooks依賴于全局狀態(tài),確保正確處理,避免造成狀態(tài)管理上的混亂。

我們自定義一個(gè)hooks方法

記住這些軍規(guī)后,我們嘗試自己寫一個(gè)自定義hooks函數(shù)。下面代碼實(shí)現(xiàn)了一個(gè)自定義的鉤子函數(shù),用于處理組件的事件監(jiān)聽(tīng)和卸載邏輯,以達(dá)到組件邏輯的封裝和復(fù)用目的。

import { ref, onMounted, onUnmounted } from 'vue';

function useEventListener(eventType, listener, options = false) {
  const targetRef = ref(null);

  onMounted(() => {
    const target = targetRef.value;
    if (target) {
      target.addEventListener(eventType, listener, options);
    }
  });

  onUnmounted(() => {
    const target = targetRef.value;
    if (target) {
      target.removeEventListener(eventType, listener, options);
    }
  });

  return targetRef;
}

對(duì)于簡(jiǎn)單的數(shù)字累加自定義hooks方法,我們可以這樣寫:

import { ref } from 'vue';

function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const increment = () => {
    count.value++;
  };

  return { count, increment };
}

編寫單元測(cè)試來(lái)驗(yàn)證你的自定義Hooks是否按預(yù)期工作:

import { mount } from '@vue/test-utils';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('should increment count', () => {
    const { count, increment } = useCounter();
    increment();
    expect(count.value).toBe(1);
  });
});

使用hooks:

<template>
  <div>{{ count }}</div>
</template>

<script setup>
import { useCounter } from './useCounter';

const { count } = useCounter(10);
</script>

hooks工具庫(kù)vueuse和vue-hooks-plus

對(duì)于常用的hooks方法可以單獨(dú)抽取進(jìn)行發(fā)包成hooks工具。在業(yè)務(wù)開(kāi)發(fā)中常用的vue hooks方法庫(kù)有:vueuse和vue-hooks-plus。那么,咱們看看這兩個(gè)庫(kù)對(duì)于useCounter的封裝是什么樣的。

vueuse:

// eslint-disable-next-line no-restricted-imports
import { ref, unref } from 'vue-demi'

import type { MaybeRef } from 'vue-demi'

export interface UseCounterOptions {
  min?: number
  max?: number
}

/**
 * Basic counter with utility functions.
 *
 * @see https://vueuse.org/useCounter
 * @param [initialValue]
 * @param options
 */
export function useCounter(initialValue: MaybeRef<number> = 0, options: UseCounterOptions = {}) {
  let _initialValue = unref(initialValue)
  const count = ref(initialValue)

  const {
    max = Number.POSITIVE_INFINITY,
    min = Number.NEGATIVE_INFINITY,
  } = options

  const inc = (delta = 1) => count.value = Math.min(max, count.value + delta)
  const dec = (delta = 1) => count.value = Math.max(min, count.value - delta)
  const get = () => count.value
  const set = (val: number) => (count.value = Math.max(min, Math.min(max, val)))
  const reset = (val = _initialValue) => {
    _initialValue = val
    return set(val)
  }

  return { count, inc, dec, get, set, reset }
}

vue-hooks-plus:

import { Ref, readonly, ref } from 'vue'
import { isNumber } from '../utils' // export const isNumber = (value: unknown): value is number => typeof value === 'number'

export interface UseCounterOptions {
  /**
   *  Min count
   */
  min?: number

  /**
   *  Max count
   */
  max?: number
}

export interface UseCounterActions {
  /**
   * Increment, default delta is 1
   * @param delta number
   * @returns void
   */
  inc: (delta?: number) => void

  /**
   * Decrement, default delta is 1
   * @param delta number
   * @returns void
   */
  dec: (delta?: number) => void

  /**
   * Set current value
   * @param value number | ((c: number) => number)
   * @returns void
   */
  set: (value: number | ((c: number) => number)) => void

  /**
   * Reset current value to initial value
   * @returns void
   */
  reset: () => void
}

export type ValueParam = number | ((c: number) => number)

function getTargetValue(val: number, options: UseCounterOptions = {}) {
  const { min, max } = options
  let target = val
  if (isNumber(max)) {
    target = Math.min(max, target)
  }
  if (isNumber(min)) {
    target = Math.max(min, target)
  }
  return target
}

function useCounter(
  initialValue = 0,
  options: UseCounterOptions = {},
): [Ref<number>, UseCounterActions] {
  const { min, max } = options

  const current = ref(
    getTargetValue(initialValue, {
      min,
      max,
    }),
  )

  const setValue = (value: ValueParam) => {
    const target = isNumber(value) ? value : value(current.value)
    current.value = getTargetValue(target, {
      max,
      min,
    })
    return current.value
  }

  const inc = (delta = 1) => {
    setValue(c => c + delta)
  }

  const dec = (delta = 1) => {
    setValue(c => c - delta)
  }

  const set = (value: ValueParam) => {
    setValue(value)
  }

  const reset = () => {
    setValue(initialValue)
  }

  return [
    readonly(current),
    {
      inc,
      dec,
      set,
      reset,
    },
  ]
}

export default useCounter

兩段代碼都在代碼實(shí)現(xiàn)上都遵守了上面的hook軍規(guī),實(shí)現(xiàn)了相似的功能,即創(chuàng)建一個(gè)可復(fù)用的計(jì)數(shù)器模塊,具有增加、減少、設(shè)置特定值和重置等操作,并且都可以配置最小和最大計(jì)數(shù)范圍。

差異點(diǎn)

  1. 代碼細(xì)節(jié):
  • 第一段代碼使用了unref函數(shù)來(lái)獲取初始值的實(shí)際數(shù)值,第二段代碼沒(méi)有使用這個(gè)函數(shù),而是直接在初始化響應(yīng)式變量時(shí)進(jìn)行處理。
  • 第二段代碼引入了一個(gè)輔助函數(shù)isNumber和getTargetValue來(lái)確保設(shè)置的值在合法范圍內(nèi),第一段代碼在設(shè)置值的時(shí)候直接進(jìn)行范圍判斷,沒(méi)有單獨(dú)的輔助函數(shù)。
  1. 返回值處理:
  • 第二段代碼返回的響應(yīng)式變量是只讀的,這可以提高代碼的安全性,防止在組件中意外修改計(jì)數(shù)器的值;第一段代碼沒(méi)有對(duì)返回的響應(yīng)式變量進(jìn)行只讀處理。

那么什么場(chǎng)景下需要抽取hooks呢?

在以下幾種情況下,通常需要抽取 Hooks 方法:

1.邏輯復(fù)用當(dāng)多個(gè)組件中存在相同或相似的邏輯時(shí),抽取為 Hooks 可以提高代碼的復(fù)用性。例如,在多個(gè)不同的頁(yè)面組件中都需要進(jìn)行數(shù)據(jù)獲取和狀態(tài)管理,如從服務(wù)器獲取用戶信息并顯示加載狀態(tài)、錯(cuò)誤狀態(tài)等。可以將這些邏輯抽取為一個(gè)useFetchUser的 Hooks 方法,這樣不同的組件都可以調(diào)用這個(gè)方法來(lái)獲取用戶信息,避免了重復(fù)編寫相同的代碼。

2.復(fù)雜邏輯的封裝如果某個(gè)組件中有比較復(fù)雜的業(yè)務(wù)邏輯,將其抽取為 Hooks 可以使組件的代碼更加清晰和易于維護(hù)。比如,一個(gè)表單組件中包含了表單驗(yàn)證、數(shù)據(jù)提交、錯(cuò)誤處理等復(fù)雜邏輯??梢詫⑦@些邏輯分別抽取為useFormValidation、useSubmitForm、useFormErrorHandling等 Hooks 方法,然后在表單組件中組合使用這些 Hooks,使得表單組件的主要邏輯更加專注于用戶界面的呈現(xiàn),而復(fù)雜的業(yè)務(wù)邏輯被封裝在 Hooks 中。

3.與特定功能相關(guān)的邏輯當(dāng)有一些特定的功能需要在多個(gè)組件中使用時(shí),可以抽取為 Hooks。例如,實(shí)現(xiàn)一個(gè)主題切換功能,需要管理當(dāng)前主題狀態(tài)、切換主題的方法以及保存主題設(shè)置到本地存儲(chǔ)等邏輯??梢詫⑦@些邏輯抽取為useTheme Hooks 方法,方便在不同的組件中切換主題和獲取當(dāng)前主題狀態(tài)。

4.提高測(cè)試性如果某些邏輯在組件中難以進(jìn)行單元測(cè)試,可以將其抽取為 Hooks 以提高測(cè)試性。比如,一個(gè)組件中的定時(shí)器邏輯可能與組件的生命周期緊密耦合,難以單獨(dú)測(cè)試。將定時(shí)器相關(guān)的邏輯抽取為useTimer Hooks 方法后,可以更容易地對(duì)定時(shí)器的行為進(jìn)行單元測(cè)試,而不依賴于組件的其他部分。

總之,抽取 Hooks 方法可以提高代碼的復(fù)用性、可維護(hù)性和測(cè)試性,當(dāng)遇到上述情況時(shí),考慮抽取 Hooks 是一個(gè)很好的實(shí)踐。

案例:vue-vben-admin中的usePermission

我們看看關(guān)于在業(yè)務(wù)開(kāi)發(fā)中如何進(jìn)行hooks抽取封裝的案例,vue-vben-admin(https://github.com/vbenjs/vue-vben-admin)是個(gè)優(yōu)秀的中后臺(tái)管理項(xiàng)目,在項(xiàng)目中設(shè)計(jì)很復(fù)雜也很全面,很多地方都充分體現(xiàn)了vue3的設(shè)計(jì)思想,也能窺見(jiàn)作者對(duì)于vue3源碼的深入。

import type { RouteRecordRaw } from 'vue-router';

import { useAppStore } from '/@/store/modules/app';
import { usePermissionStore } from '/@/store/modules/permission';
import { useUserStore } from '/@/store/modules/user';

import { useTabs } from './useTabs';

import { router, resetRouter } from '/@/router';
// import { RootRoute } from '/@/router/routes';

import projectSetting from '/@/settings/projectSetting';
import { PermissionModeEnum } from '/@/enums/appEnum';
import { RoleEnum } from '/@/enums/roleEnum';

import { intersection } from 'lodash-es';
import { isArray } from '/@/utils/is';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';

// User permissions related operations
export function usePermission() {
  const userStore = useUserStore();
  const appStore = useAppStore();
  const permissionStore = usePermissionStore();
  const { closeAll } = useTabs(router);

  /**
   * Change permission mode
   */
  async function togglePermissionMode() {
    appStore.setProjectConfig({
      permissionMode:
        appStore.projectConfig?.permissionMode === PermissionModeEnum.BACK
          ? PermissionModeEnum.ROUTE_MAPPING
          : PermissionModeEnum.BACK,
    });
    location.reload();
  }

  /**
   * Reset and regain authority resource information
   * 重置和重新獲得權(quán)限資源信息
   * @param id
   */
  async function resume() {
    const tabStore = useMultipleTabStore();
    tabStore.clearCacheTabs();
    resetRouter();
    const routes = await permissionStore.buildRoutesAction();
    routes.forEach((route) => {
      router.addRoute(route as unknown as RouteRecordRaw);
    });
    permissionStore.setLastBuildMenuTime();
    closeAll();
  }

  /**
   * Determine whether there is permission
   */
  function hasPermission(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean {
    // Visible by default
    if (!value) {
      return def;
    }

    const permMode = projectSetting.permissionMode;

    if ([PermissionModeEnum.ROUTE_MAPPING, PermissionModeEnum.ROLE].includes(permMode)) {
      if (!isArray(value)) {
        return userStore.getRoleList?.includes(value as RoleEnum);
      }
      return (intersection(value, userStore.getRoleList) as RoleEnum[]).length > 0;
    }

    if (PermissionModeEnum.BACK === permMode) {
      const allCodeList = permissionStore.getPermCodeList as string[];
      if (!isArray(value)) {
        return allCodeList.includes(value);
      }
      return (intersection(value, allCodeList) as string[]).length > 0;
    }
    return true;
  }

  /**
   * Change roles
   * @param roles
   */
  async function changeRole(roles: RoleEnum | RoleEnum[]): Promise<void> {
    if (projectSetting.permissionMode !== PermissionModeEnum.ROUTE_MAPPING) {
      throw new Error(
        'Please switch PermissionModeEnum to ROUTE_MAPPING mode in the configuration to operate!',
      );
    }

    if (!isArray(roles)) {
      roles = [roles];
    }
    userStore.setRoleList(roles);
    await resume();
  }

  /**
   * refresh menu data
   */
  async function refreshMenu() {
    resume();
  }

  return { changeRole, hasPermission, togglePermissionMode, refreshMenu };
}

這段代碼實(shí)現(xiàn)了一個(gè)與權(quán)限管理相關(guān)的模塊,主要用于在 Vue 應(yīng)用中處理用戶權(quán)限、切換權(quán)限模式、重新獲取權(quán)限資源信息以及刷新菜單等操作。

主要結(jié)構(gòu)和組成部分

  1. 引入依賴:
  • 引入了RouteRecordRaw類型,用于表示路由記錄。
  • 從特定路徑引入了應(yīng)用的store模塊,包括useAppStore、usePermissionStore和useUserStore,用于管理應(yīng)用狀態(tài)。
  • 引入了自定義的useTabs函數(shù),用于處理標(biāo)簽頁(yè)相關(guān)操作。
  • 引入了router和resetRouter,用于操作路由。
  • 引入了一些項(xiàng)目設(shè)置和工具函數(shù),如projectSetting、PermissionModeEnum、RoleEnum、intersection和isArray。
  1. 定義**usePermission**函數(shù):
  • 該函數(shù)內(nèi)部獲取了用戶存儲(chǔ)、應(yīng)用存儲(chǔ)和權(quán)限存儲(chǔ)的實(shí)例,并調(diào)用了useTabs函數(shù)獲取標(biāo)簽頁(yè)操作方法。
  • togglePermissionMode方法:用于切換權(quán)限模式,通過(guò)更新應(yīng)用存儲(chǔ)中的項(xiàng)目配置,然后重新加載頁(yè)面。
  • resume方法:用于重置和重新獲取權(quán)限資源信息。它先清除多標(biāo)簽頁(yè)存儲(chǔ)中的緩存標(biāo)簽,重置路由,重新構(gòu)建路由并添加到路由實(shí)例中,設(shè)置最后構(gòu)建菜單的時(shí)間,并關(guān)閉所有標(biāo)簽頁(yè)。
  • hasPermission方法:用于判斷用戶是否具有特定的權(quán)限。根據(jù)不同的權(quán)限模式,檢查用戶的角色列表或權(quán)限代碼列表是否包含給定的值。
  • changeRole方法:用于切換用戶角色。如果當(dāng)前權(quán)限模式不是ROUTE_MAPPING,則拋出錯(cuò)誤。如果角色不是數(shù)組,則轉(zhuǎn)換為數(shù)組,然后更新用戶存儲(chǔ)中的角色列表,并調(diào)用resume方法重新獲取權(quán)限資源信息。
  • refreshMenu方法:用于刷新菜單數(shù)據(jù),實(shí)際上是調(diào)用了resume方法。
  1. 返回值:
  • usePermission函數(shù)最后返回一個(gè)包含changeRole、hasPermission、togglePermissionMode和refreshMenu方法的對(duì)象。

總結(jié)

本文主要介紹了 Vue 3 中的組合式 API 及 Hooks 相關(guān)內(nèi)容。首先說(shuō)明了 Vue 3 組合式 API 中 Hooks 的概念、作用及與 React Hooks 的區(qū)別,指出 Vue Composition API 的優(yōu)勢(shì)。接著詳細(xì)闡述了編寫自定義 Hooks 時(shí)應(yīng)避免的錯(cuò)誤和陷阱,如狀態(tài)共享、副作用處理、過(guò)度依賴外部狀態(tài)等問(wèn)題,并給出了自定義 Hooks 函數(shù)的示例及單元測(cè)試方法。然后對(duì)比了兩個(gè)庫(kù)(vueuse 和 vue-hooks-plus)對(duì) useCounter 的封裝差異。還探討了抽取 Hooks 的場(chǎng)景,如邏輯復(fù)用、復(fù)雜邏輯封裝等,并以 vue-vben-admin 項(xiàng)目中的權(quán)限管理模塊為例進(jìn)行分析。

參考素材:

  • https://router.vuejs.org/
  • https://inhiblabcore.github.io/docs/hooks/
  • https://vueuse.org/
  • https://juejin.cn/post/7083401842733875208
責(zé)任編輯:武曉燕 來(lái)源: 宇宙一碼平川
相關(guān)推薦

2024-01-25 09:09:00

fsp幀數(shù)游戲

2024-11-06 10:16:22

2022-08-21 09:41:42

ReactVue3前端

2022-09-20 11:00:14

Vue3滾動(dòng)組件

2024-09-26 14:16:07

2021-07-07 08:00:50

Vue3 router-linAppLink

2022-06-27 09:00:55

SwiftGit Hooks

2024-07-30 08:59:22

2022-07-18 10:43:12

項(xiàng)目TienChinJava

2022-11-01 11:55:27

ReactVue3

2022-07-20 11:13:05

前端JSONVue3

2024-12-09 00:00:03

Vue3項(xiàng)目表單

2024-04-08 07:28:27

PiniaVue3狀態(tài)管理庫(kù)

2023-11-28 09:03:59

Vue.jsJavaScript

2021-12-02 05:50:35

Vue3 插件Vue應(yīng)用

2021-07-29 12:05:18

Vue3Api前端

2024-04-16 07:46:15

Vue3STOMP協(xié)議WebSocket

2021-12-08 09:09:33

Vue 3 Computed Vue2

2020-03-25 18:23:07

Vue2Vue3組件

2024-02-28 08:35:26

內(nèi)置組件Vue3頁(yè)面
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)