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 时,你得到的是一个非反应性的版本,这是原始的 prop 值。因此,为了保持其反应性,你可能需要使用一些技巧:

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,
}));

参考:
https://logaretm.com/blog/juggling-refs-around/

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。