看来vant的utils目录,我看到了这些函数

172 阅读2分钟

validate.ts

// 判断一个值是否不为undefined并且不为null
export const isDef = <T>(val: T): val is NonNullable<T> =>
  val !== undefined && val !== null;

// 判断一个值是否为函数
export const isFunction = (val: unknown): val is Function =>
  typeof val === 'function';

// 判断一个值是否为对象
export const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object';

// 判断一个值是否为Promise
export const isPromise = <T = any>(val: unknown): val is Promise<T> =>
  isObject(val) && isFunction(val.then) && isFunction(val.catch);

// 判断一个值是否为日期
export const isDate = (val: unknown): val is Date =>
  Object.prototype.toString.call(val) === '[object Date]' &&
  !Number.isNaN((val as Date).getTime());
  
// 验证一个值是否为手机号
export function isMobile(value: string): boolean {
  value = value.replace(/[^-|\d]/g, '');
  return (
    /^((\+86)|(86))?(1)\d{10}$/.test(value) || /^0[0-9-]{10,13}$/.test(value)
  );
}

export type Numeric = number | string;

// 判断传入的参数 val 是否可以被视为数值类型(Numeric)
export const isNumeric = (val: Numeric): val is string =>
  typeof val === 'number' || /^\d+(\.\d+)?$/.test(val);

export const inBrowser = typeof window !== 'undefined';

// 判断当前浏览器环境是否为IOS
export const isIOS = (): boolean =>
  inBrowser
    ? /ios|iphone|ipad|ipod/.test(navigator.userAgent.toLowerCase())
    : false;

basic.ts

export function get(object: any, path: string): any {
  const keys = path.split('.');
  let result = object;

  keys.forEach((key) => {
    result = isObject(result) ? result[key] ?? '' : '';
  });

  return result;
}

// 测试
const exampleObject = {
  user: {
    name: {
      first: 'John',
      last: 'Doe'
    },
    age: 30,
    contact: {
      email: 'john.doe@example.com',
      phone: '123-456-7890'
    }
  },
  company: {
    name: 'Example Corp',
    address: '123 Example Street',
    city: 'Anytown'
  }
};

// 使用 get 函数获取嵌套属性的示例
console.log(get(exampleObject, 'user.name.first')); // 输出: John
console.log(get(exampleObject, 'user.age')); // 输出: 30
console.log(get(exampleObject, 'user.contact.email')); // 输出: john.doe@example.com
console.log(get(exampleObject, 'user.contact.phone')); // 输出: 123-456-7890
console.log(get(exampleObject, 'company.address')); // 输出: 123 Example Street
console.log(get(exampleObject, 'company.nonExistentProperty')); // 输出: ''(因为属性不存在)
// 用于将一个类型 `T` 中的所有属性变为可写的。
// `-readonly`:这个前缀表示要移除属性的 `readonly` 修饰符,即让属性变为可写。
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };


// 从一个对象中挑选出某些字段
export function pick<T, U extends keyof T>(
  obj: T,
  keys: ReadonlyArray<U>,
  ignoreUndefined?: boolean
) {
  return keys.reduce((ret, key) => {
    if (!ignoreUndefined || obj[key] !== undefined) {
      ret[key] = obj[key];
    }
    return ret;
  }, {} as Writeable<Pick<T, U>>);
}

const user =  {
    name: '小可爱',
    info: {
        address: '许昌胖东来',
    }
}

const info = pick(user, ['info']) // { address: '许昌胖东来' }

// 但是如果是多层级的就无法使用了,如下

const exampleObject = {
  user: {
    name: {
      first: 'John',
      last: 'Doe'
    },
    age: 30,
    contact: {
      email: 'john.doe@example.com',
      phone: '123-456-7890'
    }
  },
  company: {
    name: 'Example Corp',
    address: '123 Example Street',
    city: 'Anytown'
  }
};

const age = pick(exampleObj, ['user.age', 'user.contact']) // {user.age: undefined, user.contact: undefined}

// 所以我们可以将get函数和pick函数组合一下如下
function pick2(obj, keys, ignoreUndefined) {
  return keys.reduce((ret, key) => {
    const value = get(obj, key);
    const keyArr = key.split('.');
    const lastKey = keyArr.pop();
    if (!ignoreUndefined || value !== undefined) {
      ret[lastKey] = value;
    }
    return ret;
  }, {});
}

const obj = pick2(exampleObject, ['user.name.first', 'user.age', 'company.name'], true) 
// { first: 'John123123', age: 30, name: 'Example Corp' }

vant-use useRect

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);
};
// 将一个值转为数组
export const toArray = <T>(item: T | T[]): T[] =>
  Array.isArray(item) ? item : [item];

dom.ts

// 获取某个html元素的滚动高度
export function getScrollTop(el: ScrollElement): number {
  const top = 'scrollTop' in el ? el.scrollTop : el.pageYOffset;

  // iOS scroll bounce cause minus scrollTop
  return Math.max(top, 0);
}

// 设置某个html元素的滚动高度
export function setScrollTop(el: ScrollElement, value: number) {
  if ('scrollTop' in el) {
    el.scrollTop = value;
  } else {
    el.scrollTo(el.scrollX, value);
  }
}

// 获取根元素的滚动高度
export function getRootScrollTop(): number {
  return (
    window.pageYOffset ||
    document.documentElement.scrollTop ||
    document.body.scrollTop ||
    0
  );
}

// 设置根元素的滚动高度
export function setRootScrollTop(value: number) {
  setScrollTop(window, value);
  setScrollTop(document.body, value);
}


// 获取元素顶部到页面顶部或滚动条顶部的距离
export function getElementTop(el: ScrollElement, scroller?: ScrollElement) {
  if (el === window) {
    return 0;
  }

  const scrollTop = scroller ? getScrollTop(scroller) : getRootScrollTop();
  return useRect(el).top + scrollTop;
}

// 判断一个html元素的是否隐藏
export function isHidden(
  elementRef: HTMLElement | Ref<HTMLElement | undefined>
) {
  const el = unref(elementRef);
  if (!el) {
    return false;
  }

  const style = window.getComputedStyle(el);
  const hidden = style.display === 'none';

// offsetParent returns null in the following situations:
// 1. The element or its parent element has the display property set to none.
// 2. The element has the position property set to fixed
// offsetParent在以下情况下返回null:
// 1.元素或其父元素的display属性设置为none。
// 2.元素的position属性设置为fixed
  const parentHidden = el.offsetParent === null && style.position !== 'fixed';

  return hidden || parentHidden;
}

<div style="display: none" id="ele" />
const isHiddenEle = isHidden(ele) // true

format.ts

// 添加单位(px)
export function addUnit(value?: Numeric): string | undefined {
  if (isDef(value)) {
    return isNumeric(value) ? `${value}px` : String(value);
  }
  return undefined;
}

const obj = {}
obj.a = addUnit('1')
obj.b = addUnit(1)
obj.c = addUnit(undefined)
obj.d = addUnit(null)
obj.e = addUnit(true)
obj.f = addUnit(false)
obj.g = addUnit(NaN)
obj.h = addUnit(Infinity)
obj.i = addUnit(1.1)
obj.j = addUnit('1.1')
obj.k = addUnit('1.1px')

// {        
//   a: '1px',       
//   b: '1px',       
//   c: undefined,   
//   d: undefined,   
//   e: 'true',      
//   f: 'false',     
//   g: 'NaNpx',     
//   h: 'Infinitypx',
//   i: '1.1px',     
//   j: '1.1px',     
//   k: '1.1px'      
// }

// 获取固定格式的样式可以传入数组如[1,2], 1, '1'
export function getSizeStyle(
  originSize?: Numeric | Numeric[]
): CSSProperties | undefined {
  if (isDef(originSize)) {
    if (Array.isArray(originSize)) {
      return {
        width: addUnit(originSize[0]),
        height: addUnit(originSize[1]),
      };
    }
    const size = addUnit(originSize);
    return {
      width: size,
      height: size,
    };
  }
}

const obj1 = {};
obj1.a = getSizeStyle('1')
obj1.b = getSizeStyle([1, 2])
obj1.c = getSizeStyle(undefined)
obj1.d = getSizeStyle(null)
obj1.e = getSizeStyle(true)
obj1.f = getSizeStyle(false)
console.log('obj1 :>> ', obj1);
// {
//   a: { width: '1px', height: '1px' },
//   b: { width: '1px', height: '2px' },
//   c: undefined,
//   d: undefined,
//   e: { width: 'true', height: 'true' },
//   f: { width: 'false', height: 'false' }
// }


let rootFontSize: number;
// 获取根节点的字体大小
function getRootFontSize() {
  if (!rootFontSize) {
    const doc = document.documentElement;
    const fontSize =
      doc.style.fontSize || window.getComputedStyle(doc).fontSize;

    rootFontSize = parseFloat(fontSize);
  }

  return rootFontSize;
}


// 转换rem
function convertRem(value: string) {
  value = value.replace(/rem/g, '');
  return +value * getRootFontSize();
}
convertRem('1rem') // 16px;
convertRem('2rem') // 32px;

// 转换vw, 转入vw,转换为px
function convertVw(value: string) {
  value = value.replace(/vw/g, '');
  return (+value * innnerWidth) / 100;
}

function convertVh(value: string) {
  value = value.replace(/vh/g, '');
  return (+value * innerHeight) / 100;
}



export function unitToPx(value: Numeric): number {
  if (typeof value === 'number') {
    return value;
  }

  if (inBrowser) {
    if (value.includes('rem')) {
      return convertRem(value);
    }
    if (value.includes('vw')) {
      return convertVw(value);
    }
    if (value.includes('vh')) {
      return convertVh(value);
    }
  }

  return parseFloat(value);
}

const obj2 = {};
obj2.a = unitToPx('1')
obj2.b = unitToPx('1rem')
obj2.c = unitToPx('1vw')
obj2.d = unitToPx('1vh')
obj2.e = unitToPx(1)
console.log('obj2 :>> ', obj2);
// {
//   "a": 1,
//   "b": 16,
//   "c": 10.89, // 根据当前窗口大小计算
//   "d": 4.23, // 根据当前窗口大小计算
//   "e": 1
// }



const camelizeRE = /-(\w)/g;

// 将短横线分隔的小写字母格式转换为小驼峰命名
export const camelize = (str: string): string =>
  str.replace(camelizeRE, (_, c) => c.toUpperCase());
  
camelize('custom-comp')  // customComp

// 将一个字符串转换成短横线分隔的小写字母格式
export const kebabCase = (str: string) =>
  str
    .replace(/([A-Z])/g, '-$1')
    .toLowerCase()
    .replace(/^-/, '');

kebabCase('camelCase'); // "camel-case" 
kebabCase('PascalCase'); // "pascal-case"
kebabCase('already-kebab-case'); // "already-kebab-case"
kebabCase('Some Mixed String'); // "some-mixed-string"

// 将传入的num进行补零操作
export function padZero(num: Numeric, targetLength = 2): string {
  let str = num + '';

  while (str.length < targetLength) {
    str = '0' + str;
  }

  return str;
}

padZero(1, 5) // 00001
padZero('1', 4) // 0001
padZero('111', 3) // 111


// 用于将一个数值 `num` 限制在指定的最小值 `min` 和最大值 `max` 之间。
export const clamp = (num: number, min: number, max: number): number =>
  Math.min(Math.max(num, min), max);

clamp(5,10,20) // 10
clamp(24,20, 30) // 24
clamp(20, 10, 19) // 19

// 两数相加避免浮点数
export function addNumber(num1: number, num2: number) {
  const cardinal = 10 ** 10;
  return Math.round((num1 + num2) * cardinal) / cardinal;
}


// 函数拦截器
export function callInterceptor(
  interceptor: Interceptor | undefined,
  {
    args = [],
    done,
    canceled,
  }: {
    args?: unknown[];
    done: () => void;
    canceled?: () => void;
  }
) {
  if (interceptor) {
    // eslint-disable-next-line prefer-spread
    const returnVal = interceptor.apply(null, args);

    if (isPromise(returnVal)) {
      returnVal
        .then((value) => {
          if (value) {
            done();
          } else if (canceled) {
            canceled();
          }
        })
        .catch(noop);
    } else if (returnVal) {
      done();
    } else if (canceled) {
      canceled();
    }
  } else {
    done();
  }
}

// 使用前调用函数
function beforeClose() {}
// 使用后
callInterceptor(beforeClose, {
    args: [state],
    done: () => {
        console.log('在beforeClose触发后触发')
    }
})

with-install.ts

type EventShim = {
  new (...args: any[]): {
    $props: {
      onClick?: (...args: any[]) => void;
    };
  };
};

export type WithInstall<T> = T & {
  install(app: App): void;
} & EventShim;

export function withInstall<T extends Component>(options: T) {
  (options as Record<string, unknown>).install = (app: App) => {
    const { name } = options;
    if (name) {
      app.component(name, options);
      app.component(camelize(`-${name}`), options);
    }
  };

  return options as WithInstall<T>;
}

// 使用如下
import { withInstall } from '../utils';
import _Area from './Area.tsx';

export const Area = withInstall(_Area);
export default Area;
export { areaProps } from './Area';
export type { AreaProps } from './Area';
export type { AreaList, AreaInstance } from './types';

declare module 'vue' {
  export interface GlobalComponents {
    VanArea: typeof Area;
  }
}