更新于

数组转树,防抖节流,自定义hook

向下滚动可查看阅读进度

1.数组转树

/**
 * 把平铺的数组结构转成树形结构
 */
const arr = [
  //pid是说他的父节点的id
  { id: "01", name: "张大大", pid: "", job: "项目经理" },
  { id: "02", name: "小亮", pid: "01", job: "产品leader" },
  { id: "03", name: "小美", pid: "01", job: "UIleader" },
  { id: "04", name: "老马", pid: "01", job: "技术leader" },
  { id: "05", name: "老王", pid: "01", job: "测试leader" },
  { id: "06", name: "老李", pid: "01", job: "运维leader" },
  { id: "07", name: "小丽", pid: "02", job: "产品经理" },
  { id: "08", name: "大光", pid: "02", job: "产品经理" },
  { id: "09", name: "小高", pid: "03", job: "UI设计师" },
  { id: "10", name: "小刘", pid: "04", job: "前端工程师" },
  { id: "11", name: "小华", pid: "04", job: "后端工程师" },
  { id: "12", name: "小李", pid: "04", job: "后端工程师" },
  { id: "13", name: "小赵", pid: "05", job: "测试工程师" },
  { id: "14", name: "小强", pid: "05", job: "测试工程师" },
  { id: "15", name: "小涛", pid: "06", job: "运维工程师" },
];
//递归法
function toTree(list, parId) {
  let len = list.length;
  function loop(parId) {
    //返回输入的节点下面所有子节点
    let res = [];
    for (let i = 0; i < len; i++) {
      let item = list[i];
      if (item.pid === parId) {
        //.children表示父子关系,让树形成
        item.children = loop(item.id); //找子节点的子节点
        res.push(item);
      }
    }
    return res;
  }
  return loop(parId);
}

let result = toTree(arr, "");
console.log(result);
// hash法
function toTreeHash(list) {
  const res = [];
  const map = {};
  // of遍历值(也就是下方的value对象),in遍历key
  // 1. 先遍历一遍数组,把每个元素的 id 映射为节点对象
  for (const item of list) {
    map[item.id] = { ...item, children: [] };
  }
  // 此时大概是这样:map={
  //  "01":{
  //     id:"01",
  //     name:"张大大",
  //     children:[]
  //  },

  //  "02":{
  //     id:"02",
  //     name:"小亮",
  //     children:[]
  //  }
}
// 2. 再遍历数组,将节点放入其父节点的 children 中
for (const item of list) {
  const node = map[item.id];
  if (item.pid) {
    if (!map[item.pid]) continue; // 防御性处理,避免写了pid,但是找不到这个pid对应的节点而报错
    map[item.pid].children.push(node);
  } else {
    res.push(node); // pid为空字符串(false),就是根节点
  }
}

return res;

const tree = toTreeHash(arr);
console.log(tree);

// 优势说明
// 时间复杂度:O(n),因为我们只遍历两次 arr;

// 利用了哈希表 map 进行父子节点的快速定位与组装;

// 比递归版本更适用于大数据量场景;

// 不依赖元素顺序,结构更健壮。

2.防抖节流:

function debounce(fn, delay) {
  let timer = null;
  const debounceFunc = function (...arg) {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arg);
      //apply语法:fn.apply(thisArg, argsArray)
      //...arg是数组,而这里正需要传入数组。
      //fn.call与fn.apply一样,只不过第二个参数不用数组了
    }, delay);
  };
  return debounceFunc;
}

// v2
function debounce(fn, delay, immediate = false) {
  //immediate=false意思是如果不传入true,那么默认他就是false
  let timer = null;
  let hasInvoked = false; //has invoked means 这一轮已经调用过了
  const debounceFunc = function (...args) {
    //args意思是arguments,参数,但是不要写arguments,因为他是一个内置的东西
    if (timer) clearTimeout(timer);
    if (immediate && !hasInvoked) {
      fn.call(this, ...args);
      hasInvoked = true;
    } else {
      timer = setTimeout(() => {
        fn.call(this, ...args);
        hasInvoked = false;
      }, delay);
    }
  };
  return debounceFunc;
}

// 使用时间戳的节流函数会在第一次触发事件时立即执行(因为此时的now是一个特别大的数,绝对大于delay),
// 以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行
function throttle(fn, delay) {
  let last = 0;
  const throttleFunc = function (...args) {
    const now = new Date().getTime();
    if (now - last >= delay) {
      fn.apply(this, args);
      last = now;
    }
  };
  return throttleFunc;
}

export default MyComponent;

示例:使用React调用防抖节流要用useCall和useRef 因为: React每次render都会创造一套新的变量及其闭包 // React 中直接调用 debounce 会导致每次 render 都重新创建防抖函数 // useCallback:避免每次 render 都重新执行 debounce(),从而避免重新创建新的 timer 闭包 // useRef:解决debounce只能拿到最开始render的旧的变量闭包的问题,让旧函数也能读取最新值。

function Component() {
  const [value, setValue] = useState("");

  const valueRef = useRef(value);

  valueRef.current = value; //useRef不会触发重新渲染,且能跨渲染记住值

  const handleInput = useCallback(
    debounce(() => {
      console.log(valueRef.current);
    }, 500),
    [],
  );

  return (
    <input
      value={value}
      onChange={(e) => {
        setValue(e.target.value); //每次输入组件就会重新渲染render
        handleInput();
      }}
    />
  );
}

自定义hook:要求必须是useXXX写法,如下

function useDebounceCallback(fn, delay) {
  const fnRef = useRef(fn);
  fnRef.current = fn;
  const debounceFunc = useCallback(
    debounce((...args) => {
      fnRef.current(...args);
    }, delay),
    [delay],
  );
  return debounceFunc;
}
export default useDebounceCallback;