K
JavaScript 工具函数集
创建于:2025-07-26 15:06:11
|
更新于:2026-01-16 11:12:48

clsx & twMerge

结合 clsx 和 twMerge。

cn.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export const cn = (...inputs: ClassValue[]) => {
  return twMerge(clsx(...inputs));
};

formatter

formatTimeToChatTime

时间转换

formatter.ts
export const formatTimeToChatTime = (time: Date) => {
  const now = dayjs();
  const target = dayjs(time);
 
  // 如果传入时间小于60秒,返回刚刚
  if (now.diff(target, 'second') < 60) {
    return 'common.time.Just now';
  }
 
  // 如果时间是今天,展示几时:几分
  if (now.isSame(target, 'day')) {
    return target.format('HH:mm');
  }
 
  // 如果是昨天,展示昨天
  if (now.subtract(1, 'day').isSame(target, 'day')) {
    return 'common.time.Yesterday';
  }
 
  // 如果是前天,展示前天
  if (now.subtract(2, 'day').isSame(target, 'day')) {
    return 'common.time.The day before yesterday';
  }
 
  // 如果是今年,展示某月某日
  if (now.isSame(target, 'year')) {
    return target.format('MM/DD');
  }
 
  // 如果是更久之前,展示某年某月某日
  return target.format('YYYY/M/D');
};

mergeTableCol

antd-table 列合并

mergeTableCol.ts
export function mergeTableCol<T extends { rowSpan?: number }>(array: T[] = [], key: keyof T): T[] {
  // 先按 key 排序,保证表格相邻
  const sorted = sortBy(array, key);
 
  // 按 key 分组
  const grouped = groupBy(sorted, key);
 
  const result: T[] = [];
 
  Object.values(grouped).forEach((group) => {
    // 只对“超过一项的分组”做合并
    if (group.length > 1) {
      group.forEach((item, index) => {
        if (index === 0) {
          item.rowSpan = group.length; // 第一条设置合并行数
        } else {
          item.rowSpan = 0; // 其余隐藏
        }
        result.push(item);
      });
    } else {
      // 只有一项,不合并,保持原样
      group[0].rowSpan = 1;
      result.push(group[0]);
    }
  });
 
  return result;
}

buildTree

片平数组构建树结构

buildTree.ts
import { groupBy } from 'lodash';
 
interface FlatItem {
  [key: string]: any;
}
 
interface BuildTreeOptions {
  /* 主键 */
  idKey?: string;
  /* 父级主键 */
  parentIdKey?: string;
  /* 子级 */
  childrenKey?: string;
  /* 根节点 */
  rootParentId?: any;
}
 
/* 构建树 */
export const buildTree = <T extends FlatItem>(list: T[], options: BuildTreeOptions = {}): T[] => {
  const { idKey = 'id', parentIdKey = 'parentId', childrenKey = 'children', rootParentId = null } = options;
 
  const grouped = groupBy(list, parentIdKey);
 
  function build(parentId: any): T[] {
    return (grouped[parentId] || []).map((item) => ({
      ...item,
      [childrenKey]: build(item[idKey]),
    }));
  }
 
  return build(rootParentId);
};

function

带返回的函数防抖

常规防抖

debounce.ts
function debounce( callback, delay = 300 ){
    let timer;
    return ( ...args ) => {
      clearTimeout( timer );
      timer = setTimeout( () => {
        callback( ...args );
      }, delay );
    }
}

问题:无法获取到函数的返回值。

原因:callback 在 settimeout 中执行,返回值自然是没办法返回给使用者的。

但实际上,一个函数在一段时间内不一定执行,也不应该拿到期待的返回值。

解决方案

  1. 回调函数中传递
  2. promise resolve

回调函数中传递

这种方式后续的操作都需要在回调中处理。

debounce.ts
import { debounce } from "lodash-es";
 
const test = (callback) => {
  console.log("test");
  
  let returnValue = 100
  callback && callback(returnValue);
  
  return returnValue
};
 
const debouncedTest = debounce(test, 100);
 
console.log(
  "return",
  debouncedTest((value) => {
    console.log(value);
  })
);

promise resolve

将含有返回值的函数当做异步函数对待,通过 promise 承接返回值。

debounceWithReturn.ts
function debounceWithReturn( callback, delay ) {
  let timer;
 
  return( ...args ) => {
    return new Promise( ( resolve, reject ) => {
      clearTimeout(timer);
      timer = setTimeout( () => {
          try {
            let output = callback(...args);
            resolve( output );
          } catch ( err ) {
            reject( err );
          }
      }, delay );
    })
 
  }
}
 
// demo
const func =  val => {
  console.log(val)
  return val
};
 
const func1 = debounce(func, 1000);
 
const a = () => {
  let t;
  t =  func1(1);
  t =  func1(2);
  t =  func1(3);
  console.log('akira', t.then(res => console.log('then', res)));
};
 
a();

request

transParams

将 params 转换为 query 参数,用于拼接在 url 上。

transParams.ts
/**
 * 将参数转换为 query 字符串
 * @param params - 需要转换的参数对象
 * @returns 转换后的 query 字符串
 */
export function transParams(params: Record<string, any>): string {
  let result = "";
 
  for (const propName of Object.keys(params)) {
    const value = params[propName];
    const part = encodeURIComponent(propName) + "=";
 
    if (value !== null && value !== "" && typeof value !== "undefined") {
      if (typeof value === "object") {
        for (const key of Object.keys(value)) {
          if (value[key] !== null && value[key] !== "" && typeof value[key] !== "undefined") {
            const paramKey = `${propName}[${key}]`;
            const subPart = encodeURIComponent(paramKey) + "=";
            result += subPart + encodeURIComponent(value[key]) + "&";
          }
        }
      } else {
        result += part + encodeURIComponent(value) + "&";
      }
    }
  }
 
  return result;
}

download

download.ts
/**
 * 下载文件
 * @param url - 文件下载地址
 * @param filename - 下载后的文件名
 */
export function download(url: string, filename: string) {
  const link = document.createElement("a");
  link.href = url;
  link.download = filename;
  link.style.display = "none";
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}
 
/**
 * 下载 Blob 数据
 * @param blob - Blob 数据
 * @param filename - 下载后的文件名
 */
export function downloadBlob(blob: Blob, filename: string) {
  const url = window.URL.createObjectURL(blob);
  download(url, filename);
  window.URL.revokeObjectURL(url);
}
 
/**
 * 下载文本内容
 * @param content - 文本内容
 * @param filename - 下载后的文件名
 */
export function downloadText(content: string, filename: string) {
  const blob = new Blob([content], { type: "text/plain" });
  downloadBlob(blob, filename);
}

tree

buildTree

将拍平的数组转换为树状结构

buildTree.ts
export function buildTree(data,key="children"){
  const result = []
  const map = {}
  data.forEach((value,index)=>{
    map[value.id] = value
  })
  data.forEach((value,index)=>{
    if(value.pid){
      const parent = map[value.pid];
      if(!parent.hasOwnProperty(key)){
        parent[key] = []
      }
      parent[key].push(value)
    }else{
      result.push(value)
    }
  })
  return result;
}

filterTree

根据条件过滤树,子项包含符合条件的子项,则返回其以及其父级。

filterTree.ts
export function filterTree(treeData, filter, childrenKey = "children") {
  const loop = (data) => {
    return data
      .map((item) => {
        const children = item[childrenKey];
        const hasMatched = filter(item);
        const hasMatchingChildren = children && children.some((child) => loop([child])?.length > 0);
 
        if (hasMatched || hasMatchingChildren) {
          return {
            ...item,
            [childrenKey]: children ? loop(children) : undefined,
          };
        }
 
        return null;
      })
      .filter((item) => item !== null);
  };
  return loop(treeData);
}

isEmptyObject

判断对象是否为空,包括嵌套对象。

isEmptyObject.ts
/** 判断对象是否为空 */
export const isEmptyObject = (obj: any): boolean => {
  // 处理 null 和 undefined
  if (obj === null || obj === undefined) {
    return true;
  }
 
  // 处理非对象类型(字符串、数字、布尔值等)
  if (typeof obj !== 'object' || Array.isArray(obj)) {
    return false;
  }
 
  // 获取对象的所有键
  const keys = Object.keys(obj);
 
  // 空对象视为空
  if (keys.length === 0) {
    return true;
  }
 
  // 递归检查每个属性
  return keys.every(key => {
    const value = obj[key];
 
    // null 或 undefined 视为空
    if (value === null || value === undefined) {
      return true;
    }
 
    // 如果是对象,递归检查
    if (typeof value === 'object' && !Array.isArray(value)) {
      return isEmptyObject(value);
    }
 
    // 其他类型(字符串、数字、布尔值、数组等)视为非空
    return false;
  });
};
我也是有底线的 🫠