Skip to content

特殊方法

ajax

该方法用于发送ajax请求,支持Promise,详细配置请看方法实现。

ts
import { ajax } from '...'

ajax({
  url: '...',
  method: 'GET',
  params: { id: 1 }
  headers: { ... },
  timeout: 5000,
  responseType: 'json'
}).then(res => {
  console.log(res)
})
方法源码
ts
/**
 * 使用XHR发送请求,获取数据
 */
export const ajax = <T = unknown>(param: {
  url: string;
  timeout?: number;
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
  data?: any;
  params?: { [key: string]: string };
  headers?: { [key: string]: string };
  responseType?: XMLHttpRequestResponseType;
  beforeRequest?: (xhr: XMLHttpRequest) => void;
}): Promise<{ data: T; xhr: XMLHttpRequest }> => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve({ data: xhr.response, xhr });
          return;
        }

        reject(new Error(`Request failed with status ${xhr.status}`));
      }
    };

    if (param.beforeRequest) {
      param.beforeRequest(xhr);
    }

    const {
      url,
      timeout = 5000,
      method = 'GET',
      headers = { 'Content-Type': 'application/json' },
      data,
      params,
      responseType = 'json'
    } = param;

    if (params) {
      const paramsStr = Object.keys(params)
        .map((key) => `${key}=${params[key]}`)
        .join('&');

      xhr.open(method, `${url}?${paramsStr}`, true);
    } else {
      xhr.open(method, url, true);
    }

    xhr.responseType = responseType;
    xhr.timeout = timeout;
    Object.keys(headers).forEach((key) => {
      xhr.setRequestHeader(key, headers[key]);
    });

    if (data && method !== 'GET') {
      if (
        headers['Content-Type'] === 'application/json' &&
        typeof data === 'object'
      ) {
        xhr.send(JSON.stringify(data));
        return;
      }

      xhr.send(data);
      return;
    }

    xhr.send(null);
  });
};

fileSlice

该方法用于文件切片,接收一个文件对象、一个开始位置与一个片大小,返回切片好的Blob数组。

ts
import { fileSlice } from '...'

const file = new File([''], 'test.txt')
const blobs = fileSlice(file, 0, 1024 * 1024)
方法源码
ts
/**
 * @description 文件切片
 * @param {File} file 文件对象
 * @param {number} [start = 0] 从文件的哪里开始,默认0
 * @param {number} [piece = 1024 * 512] 每一块大小,默认512k
 * @returns {Blob[]} 返回一个文件切片数组
 */
export const fileSlice = (
  file: File,
  start: number = 0,
  piece: number = 1024 * 512
): Blob[] => {
  const total = file.size;
  let end = start + piece;
  if (end > total) {
    end = total;
  }

  const chunks: Blob[] = [];
  while (end <= total) {
    const blob = file.slice(start, end);
    chunks.push(blob);

    start = end;
    end = start + piece;

    if (end > total) {
      end = total;
    }
  }

  return chunks;
};

filterEmoji

该方法用于过滤字符串中的emoji表情,接收一个字符串参数,返回过滤后的字符串。

ts
import { filterEmoji } from '...'

filterEmoji('😂😂😂') // ''
方法源码
ts
export const filterEmoji = (value: string) => {
  const regexp =
    /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g;

  return value.replace(regexp, '');
};

changeCarousel

该方法用于实现轮播图效果,接收一个配置对象,包含以下属性:

  • el:轮播图容器;
  • index:下一个显示的元素索引;
  • direction:轮播方向;
  • duration:动画持续时间;
  • timingFunc:动画时间函数;
  • beforeChange:切换前的回调函数;
  • afterChange:切换后的回调函数。
ts
import { changeCarousel } from '...'

changeCarousel({
  el: document.querySelector('.carousel'),
  index: 1,
  direction: 'horizontal',
  duration: 500,
  timingFunc: 'ease',
  beforeChange: () => console.log('before change'),
  afterChange: () => console.log('after change')
})
方法源码
ts
/**
 * 切换轮播图
 * @param {object} options
 * @param {HTMLElement} options.el 轮播图容器元素
 * @param {number} options.index 要切换到的索引,从0开始
 * @param {'horizontal' | 'vertical'} options.direction
 * @param {number} options.duration 动画时长,单位ms(默认500ms)
 * @param {string} options.timingFunc 动画函数(默认ease)
 * @param {(currentIndex: number) => void} options.beforeChange 切换前的回调
 * @param {(currentIndex: number) => void} options.afterChange 切换后的回调
 */
export const changeCarousel = ({
  index,
  el,
  duration = 500,
  direction = 'horizontal',
  timingFunc = 'ease',
  beforeChange,
  afterChange
}: {
  index: number;
  el: HTMLElement;
  duration?: number;
  direction?: 'horizontal' | 'vertical';
  timingFunc?: string;
  beforeChange?: (currentIndex: number) => void;
  afterChange?: (currentIndex: number) => void;
}) => {
  if (!el || el.style.display === 'none' || !el.children.length) {
    return;
  }

  const emitAfterChange = (currentIndex: number, duration: number) => {
    if (!afterChange) {
      return;
    }

    if (duration <= 0) {
      afterChange(currentIndex);
      return;
    }

    setTimeout(() => {
      afterChange(currentIndex);
    }, duration);
  };

  const filterStyle = (style: string, timingFunction = '') => {
    if (!style) {
      return '';
    }

    const regexp = new RegExp(
      `(transition: transform [0-9]+ms ${timingFunction} 0s; ?)?(transform: translate3d\\(-?[0-9]+px, -?[0-9]+px, 0px\\); ?)?`,
      'g'
    );

    return style.replace(regexp, '');
  };

  const translate3d = {
    horizontal: (x: number) => `translate3d(${x}px, 0px, 0px)`,
    vertical: (y: number) => `translate3d(0px, ${y}px, 0px)`
  } as const;
  const getTranslate3d = translate3d[direction];

  const itemTotal = el.childElementCount;
  if (index < 0) {
    index = itemTotal - 1;
  } else if (index >= itemTotal) {
    index = 0;
  }

  if (beforeChange) {
    beforeChange(index);
  }

  const style = el.getAttribute('style') ?? '';
  const nativeStyle = filterStyle(style, timingFunc);
  const itemSize =
    direction === 'horizontal' ? el.clientWidth : el.clientHeight;

  // 从最后一个切换到第一个,需要做特殊处理,以实现无缝滚动的效果
  if (
    index === 0 &&
    style.indexOf(getTranslate3d(-(itemTotal - 1) * itemSize)) > -1
  ) {
    const childStyle = filterStyle(
      el.children[0].getAttribute('style') ?? '',
      timingFunc
    );

    // 将第一个子元素移动到最后一个元素的后面
    el.children[0].setAttribute(
      'style',
      `${childStyle};transform: ${getTranslate3d(itemSize * itemTotal)};`
    );

    el.setAttribute(
      'style',
      `${nativeStyle}transition: transform ${duration}ms ${timingFunc} 0s; transform: ${getTranslate3d(
        -(itemSize * itemTotal)
      )};`
    );

    // 等动画结束后,将第一个子元素复位
    if (duration <= 0) {
      el.setAttribute(
        'style',
        `${nativeStyle}transform: translate3d(0px, 0px, 0px);`
      );
      el.children[0].setAttribute('style', childStyle);
    } else {
      setTimeout(() => {
        el.setAttribute(
          'style',
          `${nativeStyle}transform: translate3d(0px, 0px, 0px);`
        );
        el.children[0].setAttribute('style', childStyle);
      }, duration);
    }
    emitAfterChange(index, duration);
    return;
  }

  // 从第一个切换到最后一个
  if (
    index === itemTotal - 1 &&
    (style.indexOf(getTranslate3d(0)) > -1 || style.indexOf('transform') === -1)
  ) {
    const childStyle = filterStyle(
      el.children[index].getAttribute('style') ?? '',
      timingFunc
    );

    // 将最后一个子元素移动到第一个元素的前面
    el.children[index].setAttribute(
      'style',
      `${childStyle};transform: ${getTranslate3d(-itemSize * itemTotal)};`
    );

    el.setAttribute(
      'style',
      `${nativeStyle}transition: transform ${duration}ms ${timingFunc} 0s; transform: ${getTranslate3d(
        itemSize
      )};`
    );

    // 等动画结束后,将最后一个子元素复位
    if (duration <= 0) {
      el.children[index].setAttribute('style', childStyle);
      el.setAttribute(
        'style',
        `${nativeStyle}transform: ${getTranslate3d(-index * itemSize)};`
      );
    } else {
      setTimeout(() => {
        el.children[index].setAttribute('style', childStyle);
        el.setAttribute(
          'style',
          `${nativeStyle}transform: ${getTranslate3d(-index * itemSize)};`
        );
      }, duration);
    }

    emitAfterChange(index, duration);
    return;
  }

  el.setAttribute(
    'style',
    `${nativeStyle}transition: transform ${duration}ms ${timingFunc} 0s; transform: ${getTranslate3d(
      -index * itemSize
    )};`
  );
  emitAfterChange(index, duration);
};

getMonthMaxDay

该方法用于获取某月的最大天数,接收一个年份与一个月份参数,返回该月最大天数。

ts
import { getMonthMaxDay } from '...'

getMonthMaxDay(2024, 6) // 30
方法源码
ts
/**
 * 获取一个月的最大天数
 * @param year
 * @param month 月份(1-12)
 * @returns
 */
export const getMonthMaxDay = (year: number, month: number) => {
  /**
   * Date对象的构造函数接收月份索引(0-11)。
   * 将月份设置为下个月且天数设置为0,Date对象将自动设置为上个月最后一天。
   */
  const maxDay = new Date(year, month, 0).getDate();
  return maxDay;
};

isLeapYear

该方法用于判断某年是否是闰年,接收一个年份参数,返回一个布尔值。

ts
import { isLeapYear } from '...'
isLeapYear(2024) // true
方法源码
ts
export const isLeapYear = (year: number) => {
  if (year % 400 === 0) {
    return true;
  }

  if (year % 4 === 0 && year % 100 !== 0) {
    return true;
  }

  return false;
};

getPseudoRandomNumber

该方法用于生成伪随机数,接收一个最小值与一个最大值参数,返回一个在该范围内的随机数。

ts
import { getPseudoRandomNumber } from '...'
getPseudoRandomNumber(1, 10) // 5
方法源码
ts
/**
 * 获取伪随机数
 * @param {number} [min=0] 最小值,默认0
 * @param {number} [max=65532] 最大值,默认65532
 * @returns
 */
export const getPseudoRandomNumber = (
  min: number = 0,
  max: number = 65535
): number => {
  if (min > max) {
    const temp = min;
    min = max;
    max = temp;
  }

  const arr = new Uint16Array(1);
  const maxNum = 65535;
  let randomNum = 0;
  if (
    typeof window !== 'undefined' &&
    window.crypto &&
    window.crypto.getRandomValues
  ) {
    randomNum = window.crypto.getRandomValues(arr)[0] / maxNum;
  } else {
    randomNum = Math.random();
  }

  return Math.ceil(randomNum * (max - min) + min);
};

fillArray

该方法用于填充数组,接收一个长度与一个填充函数参数,返回填充好的数组。

ts
import { fillArray, getPseudoRandomNumber } from '...'
fillArray(5, () => 1) // [1, 1, 1, 1, 1]
fillArray(5, () => getPseudoRandomNumber(1, 20)) // [5, 10, 15, 20, 10]
方法源码
ts
export const fillArray = <T = number>(
  length: number,
  getValue: () => T
): T[] => {
  if (typeof Array.from !== 'function') {
    const arr = [];
    for (let i = 0; i < length; i++) {
      arr.push(getValue());
    }
    return arr;
  }

  const arr = Array.from({ length }, getValue);
  return arr;
};

Released under the MIT License.