Desmond

Desmond

An introvert who loves web programming, graphic design and guitar
github
bilibili
twitter

Vue中的反應性提示

可選的反應性#

import { Ref, ref } from 'vue';
type MaybeRef<T> = Ref<T> | T;
// 示例用法
// ✅ 有效
const raw: MaybeRef<number> = 1;
// ✅ 有效
const reffed: MaybeRef<number> = ref(1);
isRef(raw); // false
isRef(reffed); // true
unref(raw); //  1
unref(reffed); //  1

示例:

import { unref, ref, isRef, onMounted, watch } from 'vue';
import { MaybeRef } from '@/types';
async function fetchProduct(id: number) {
  // 從 API 返回產品
  return fetch(`/api/products/${id}`).then((res) => res.json());
}
export function useProduct(id: MaybeRef<number>) {
  const product = ref(null);
  onMounted(async () => {
    product.value = await fetchProduct(unref(id));
  });
  if (isRef(id)) {
    watch(id, async (newId) => {
      product.value = await fetchProduct(newId);
    });
  }
  return product;
}
import { useProduct } from '@/composables/products';
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
// 僅在掛載時獲取一次
const product = useProduct(route.params.productId);
// 每當參數變更時保持同步
const product = useProduct(
  computed(() => route.params.productId)
);

避免重新打包 refs#

import { reactive, watch } from 'vue';
const obj = reactive({
  id: 1,
});
// ✅ 有效!
watch(obj, () => {
  // ...
});
// ❌ 無效
watch(obj.id, () => {
  // ...
});

最後的 watch 不太有效,因為當你訪問 props.id 時,你獲得的是它的非反應性版本,這是原始的屬性值。因此,為了保持反應性,你可能需要採用一些技術:

import { reactive, watch, toRef, toRefs, computed } from 'vue';
const obj = reactive({
  id: 1,
});
// 將所有條目轉換為 refs
const objRef = toRefs(obj);
watch(objRef.id, () => {
  //...
});
// 你也可以解構它
const { id: idRef } = toRefs(obj);
watch(idRef, () => {
  //...
});
// 將單個條目轉換為其反應性版本
const idRef = toRef(obj, 'id');
watch(idRef, () => {
  //...
});
// 只需在計算屬性中提取值
const idComp = computed(() => obj.id);
watch(idComp, () => {
  //...
});

所有上述語句從反應性對象值或任何對象 ref 中產生一個反應性的 id 值。然而,這會產生一個問題。用戶必須在將其傳遞給函數之前適當地構造反應性數據,這迫使他們使用可用的任何打包機制。

根本問題是這個過程迫使消費者解包他們的值,並在希望保持反應性時重新打包它們。這種做法往往導致冗長,我喜歡稱之為「重新打包 refs」。

解決方案#

import { Ref } from 'vue';
// 原始值或 ref
export type MaybeRef<T> = Ref<T> | T;
// 不能是原始值
export type LazyOrRef<T> = Ref<T> | (() => T);
// 可以是 ref、getter 或原始值
export type MaybeLazyRef<T> = MaybeRef<T> | (() => T);
import { unref } from 'vue';
export function unravel<T>(value: MaybeLazyRef<T>): T {
  if (typeof value === 'function') {
    return value();
  }
  return unref(value);
}
export function isWatchable<T>(
  value: MaybeLazyRef<T>
): value is LazyOrRef<T> {
  return isRef(value) || typeof value === 'function';
}

示例:

import { ref, onMounted, watch } from 'vue';
import { unravel, isWatchable } from '@/utils';
import { MaybeLazyRef } from '@/types';
async function fetchProduct(id: number) {
  // 從 API 返回產品
  return fetch(`/api/products/${id}`).then((res) => res.json());
}
export function useProduct(id: MaybeLazyRef<number>) {
  const product = ref(null);
  onMounted(async () => {
    product.value = await fetchProduct(unravel(id));
  });
  if (isWatchable(id)) {
    // 有效,因為 getter 函數或 ref 都是可觀察的
    watch(id, async (newId) => {
      product.value = await fetchProduct(newId);
    });
  }
  return product;
}
import { useRoute } from 'vue-router';
import { useProduct } from '@/composables/products';
const route = useRoute();
// 僅在掛載時獲取一次
const product = useProduct(route.params.productId);
// 每當參數變更時保持同步
const product = useProduct(() => route.params.productId);

需要反應性#

脫離我們之前的示例,假設你正在嘗試構建一個僅接受反應性位置參數的 usePositionFollower 函數,這些參數以 x,y 坐標表示。獲取非反應性位置將毫無意義,因為沒有任何東西可以跟隨。

因此,告知用戶僅提供反應性值,或更具體地說,反應性表達式將是有益的。這排除了使用 MaybeLazyRef,但請記住,我們之前為此創建了 LazyOrRef,它可以與 isWatchable 函數一起運作。

import { h, defineComponent } from 'vue';
import { LazyOrRef } from '@/types';
import { unravel } from '@/utils';
export function usePositionFollower(
  position: LazyOrRef<{ x: number; y: number }>
) {
  const style = computed(() => {
    const { x, y } = unravel(position);
    return {
      position: 'fixed',
      top: 0,
      left: 0,
      transform: `translate3d(${x}px, ${y}px, 0)`,
    };
  });
  const Follower = defineComponent(
    (props, { slots }) =>
      () =>
        h('div', { ...props, style: style.value }, slots)
  );
  return Follower;
}
const { x, y } = useMouse();
const Follower = usePositionFollower(() => ({
  x: x.value,
  y: y.value,
}));
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。