VantUse 源码学习-path2

396 阅读3分钟

Built-in composition APIs of Vant.

1. useRect

作用

  • 返回元素的大小及其相对于视口的位置

    Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。 转存失败,建议直接上传图片文件

优点

  • 支持输入window return getBoundingClientRect格式返回值
  • 提高兼容性
import { Ref, unref } from 'vue';

const isWindow = (val: unknown): val is Window => val === window;

const makeDOMRect = (width: number, height: number) =>
  ({
    top: 0,
    left: 0,
    right: width,
    bottom: height,
    width,
    height,
  } as DOMRect);

export const useRect = (
  elementOrRef: Element | Window | Ref<Element | Window | undefined>
) => {
  const element = unref(elementOrRef);

  if (isWindow(element)) {
    const width = element.innerWidth;
    const height = element.innerHeight;
    return makeDOMRect(width, height);
  }

  if (element?.getBoundingClientRect) {
    return element.getBoundingClientRect();
  }

  return makeDOMRect(0, 0);
};

2. useCustomFieldValue

作用

  • switch等输入的控件往包裹在外面的 Field 组件传递数据以及触发事件,用于校验以及往外暴露的回调事件
  • 例如以下的 switch 组件利用 useCustomFieldValuehook 监听自身响应式数据switchChecked触发Field 组件上面的回调函数,做到实时更新校验等效果
<van-field name="switch" label="开关">
  <template #input>
    <van-switch v-model="switchChecked" size="20" />
  </template>
</van-field>
import { watch, inject, InjectionKey, Ref } from 'vue';

export type CustomFieldInjectionValue = {
  customValue: Ref<(() => unknown) | undefined>,
  resetValidation: () => void,
  validateWithTrigger: (trigger: 'onBlur' | 'onChange' | 'onSubmit') => void,
};

export const CUSTOM_FIELD_INJECTION_KEY: InjectionKey<CustomFieldInjectionValue> =
  Symbol('van-field');

export function useCustomFieldValue(customValue: () => unknown) {
  const field = inject(CUSTOM_FIELD_INJECTION_KEY, null);
  // 祖先组件Field使用 provide 来提供了一个对象,子组件使用 inject 来获取这个对象
  // provide(CUSTOM_FIELD_INJECTION_KEY, {
  //   customValue,
  //   resetValidation,
  //   validateWithTrigger,
  // });

  // 赋值 触发Field组件事件
  if (field && !field.customValue.value) {
    field.customValue.value = customValue;

    watch(customValue, () => {
      field.resetValidation();
      field.validateWithTrigger('onChange');
    });
  }
}

3. useChildren 寻找后代组件

说明

  • 基于Vue上面的provide方法上面的封装提供绑定等事件
  • 内部linkChildren函数调用才触发provide,为了暴露组件上更多的数据到后代组件
import {
  VNode,
  isVNode,
  provide,
  reactive,
  InjectionKey,
  getCurrentInstance,
  VNodeNormalizedChildren,
  ComponentPublicInstance,
  ComponentInternalInstance,
} from 'vue';

...

export function useChildren<
  // eslint-disable-next-line
  Child extends ComponentPublicInstance = ComponentPublicInstance<{}, any>,
  ProvideValue = never
>(key: InjectionKey<ProvideValue>) {
  // publicChildren 收集getCurrentInstance()
  // internalChildren 收集getCurrentInstance().proxy
  const publicChildren: Child[] = reactive([]);
  const internalChildren: ComponentInternalInstance[] = reactive([]);
  const parent = getCurrentInstance()!; // 获取当前组件实例

  const linkChildren = (value?: ProvideValue) => {
    // 绑定子组件到列表中
    const link = (child: ComponentInternalInstance) => {
      if (child.proxy) {
        internalChildren.push(child);
        publicChildren.push(child.proxy as Child); // push 当前组件实例的方法和属性
        sortChildren(parent, publicChildren, internalChildren); //
      }
    };
    // 解绑子组件
    const unlink = (child: ComponentInternalInstance) => {
      const index = internalChildren.indexOf(child);
      publicChildren.splice(index, 1);
      internalChildren.splice(index, 1);
    };

    // 提供给子组件的数据
    // 子组件通过inject(key, null)来获取
    // 把linkChildren的入参也提供给子组件
    provide(
      key,
      Object.assign(
        {
          link,
          unlink,
          children: publicChildren,
          internalChildren,
        },
        value
      )
    );
  };

  return {
    children: publicChildren,
    linkChildren,
  };
}

4. useParent 寻找祖先组件

说明

  • 基于Vue上面的inject方法上面的封装寻找对应的祖先组件
  • 获取祖先组件的绑定函数自动绑定到祖先组件的数据中
import {
  ref,
  inject,
  computed,
  onUnmounted,
  InjectionKey,
  getCurrentInstance,
  ComponentPublicInstance,
  ComponentInternalInstance,
} from 'vue';

type ParentProvide<T> = T & {
  link(child: ComponentInternalInstance): void;
  unlink(child: ComponentInternalInstance): void;
  children: ComponentPublicInstance[];
  internalChildren: ComponentInternalInstance[];
};

export function useParent<T>(key: InjectionKey<ParentProvide<T>>) {
  // 获取祖先组件provide的对象
  const parent = inject(key, null);

  if (parent) {
    const instance = getCurrentInstance()!;
    const { link, unlink, internalChildren } = parent;

    // 自动绑定解绑
    link(instance);
    onUnmounted(() => unlink(instance));

    // 计算当前组件在父组件中的位置
    const index = computed(() => internalChildren.indexOf(instance));

    return {
      parent,
      index,
    };
  }

  return {
    parent: null,
    index: ref(-1),
  };
}

示例

// Field.tsx 中使用useParent寻找form组件
const { parent: form } = useParent(FORM_KEY);
// 挂载在Field自身上
useExpose <
  FieldExpose >
  {
    blur,
    focus,
    validate,
    formValue,
    resetValidation,
    getValidationStatus,
  };

// Form.tsx使用useChildren寻找所有的Field组件
const { children, linkChildren } = useChildren(FORM_KEY);

// 调用Form组件上面的validateAll方法,实际上就是调用了每个Field自身上validate方法
const validateAll = (names?: string[]) =>
  new Promise<void>((resolve, reject) => {
    const fields = children;
    Promise.all(fields.map((item) => item.validate())).then((errors) => {
      errors = errors.filter(Boolean);
      if (errors.length) {
        reject(errors);
      } else {
        resolve();
      }
    });
  });