Skip to content

bytedance

Debounce

每次触发事件时,重置定时器,推迟函数执行,直到事件停止触发超过 wait 时间。

export default function debounce(func, wait = 0) {
  let timeoutID = null;
  return function (...args) {
    // Keep a reference to `this` so that
    // func.apply() can access it.
    const context = this;
    clearTimeout(timeoutID);

    timeoutID = setTimeout(function () {
      timeoutID = null; // Not strictly necessary but good to do this.
      func.apply(context, args);
    }, wait);
  };
}

Throttle

在 wait 时间内,函数最多执行一次;如果事件持续触发,函数会在每个 wait 时间窗口内执行一次(通常是窗口开始时)。

/**
 * @callback func
 * @param {number} wait
 * @return {Function}
 */
export default function throttle(func, wait = 0) {
  let shouldThrottle = false;

  return function (...args) {
    if (shouldThrottle) {
      return;
    }

    shouldThrottle = true;
    setTimeout(function () {
      shouldThrottle = false;
    }, wait);

    func.apply(this, args);
  };
}

map

/**
 * @template T, U
 * @param { (value: T, index: number, array: Array<T>) => U } callbackFn
 * @param {any} [thisArg]
 * @return {Array<U>}
 */
Array.prototype.myMap = function (callbackFn, thisArg) {
  const len = this.length;
  const array = new Array(len);

  for (let k = 0; k < len; k++) {
    // Ignore index if value is not defined for index (e.g. in sparse arrays).
    if (Object.hasOwn(this, k)) {
      array[k] = callbackFn.call(thisArg, this[k], k, this);
    }
  }

  return array;
};

pV7wByovxFlmtJC

filter

/**
 * @template T
 * @param { (value: T, index: number, array: Array<T>) => boolean } callbackFn
 * @param {any} [thisArg]
 * @return {Array<T>}
 */
Array.prototype.myFilter = function (callbackFn, thisArg) {
  const len = this.length;
  const results = [];

  for (let k = 0; k < len; k++) {
    const kValue = this[k];
    if (
      // Ignore index if value is not defined for index (e.g. in sparse arrays).
      Object.hasOwn(this, k) &&
      callbackFn.call(thisArg, kValue, k, this)
    ) {
      results.push(kValue);
    }
  }

  return results;
};

Reduce

/**
 * @template T, U
 * @param {(previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U} callbackFn
 * @param {U} [initialValue]
 * @return {Array<U>}
 */
Array.prototype.myReduce = function (callbackFn, initialValue) {
  const noInitialValue = initialValue === undefined;
  const len = this.length;

  if (noInitialValue && len === 0) {
    throw new TypeError("Reduce of empty array with no initial value");
  }

  let acc = noInitialValue ? this[0] : initialValue;
  let startingIndex = noInitialValue ? 1 : 0;

  for (let k = startingIndex; k < len; k++) {
    if (Object.hasOwn(this, k)) {
      acc = callbackFn(acc, this[k], k, this);
    }
  }

  return acc;
};

deep clone

/**
 * @template T
 * @param {T} value
 * @return {T}
 */
export default function deepClone(value) {
  if (typeof value !== "object" || value === null) {
    return value;
  }

  if (Array.isArray(value)) {
    return value.map((item) => deepClone(item));
  }

  return Object.fromEntries(
    Object.entries(value).map(([key, value]) => [key, deepClone(value)])
  );
}

bind

/**
 * @param {any} thisArg
 * @param {...*} argArray
 * @return {Function}
 */
Function.prototype.myBind = function (thisArg, ...argArray) {
  const originalMethod = this;
  return function (...args) {
    return originalMethod.apply(thisArg, [...argArray, ...args]);
  };
};

Flatten

type ArrayValue = any | Array<ArrayValue>;

export default function flatten(value: Array<ArrayValue>): Array<any> {
  const res = [];
  const copy = value.slice();

  while (copy.length) {
    const item = copy.shift();
    if (Array.isArray(item)) {
      copy.unshift(...item);
    } else {
      res.push(item);
    }
  }

  return res;
}

Promise.all

/**
 * @param {Array} iterable
 * @return {Promise<Array>}
 */
export default function promiseAll(iterable) {
  return new Promise((resolve, reject) => {
    const results = new Array(iterable.length);
    let unresolved = iterable.length;

    if (unresolved === 0) {
      resolve(results);
      return;
    }

    iterable.forEach(async (item, index) => {
      try {
        const value = await item;
        results[index] = value;
        unresolved -= 1;

        if (unresolved === 0) {
          resolve(results);
        }
      } catch (err) {
        reject(err);
      }
    });
  });
}

Promise.race

/**
 * @param {Array} iterable
 * @return {Promise}
 */
export default function promiseRace(iterable) {
  return new Promise((resolve, reject) => {
    if (iterable.length === 0) {
      return;
    }

    iterable.forEach(async (item) => {
      try {
        const result = await item;
        resolve(result);
      } catch (err) {
        reject(err);
      }
    });
  });
}

UseRef

React 会将 ref 对象的 current 属性设置为该 DOM 节点

UseEffect vs UseLayoutEffect

  • useEffect 是 async 的, 不会 delay browser 的 paint(performance 的角度, 所以用 UseEffect 比较多)
  • UseLayoutEffect 是 sync 的, 会 delay paint(before directly modify dom == paint?), 等执行完才会 paint, 所以有延迟

Next.js 水合问题

浏览器 API 相关的

  • API 仅在 client 端存在

不确定的值

  • Date 或者 random

响应式布局相关的

  • window.innerWidth

custom hook 例子分析

  • 将可复用的逻辑抽象成一个 Hook
  • 返回状态相关的逻辑, 不渲染 ui

React Fiber

  • 并发模式

低优先级任务正在进行:假设 React 正在进行一个耗时的渲染工作,比如根据网络请求返回的大量数据渲染一个长列表。这个任务的优先级很低。React 正在不紧不慢地处理一个个 Fiber 工作单元。

  用户点击事件发生:用户突然点击了一个按钮,触发了一个setState。

  React调度高优先级更新:React识别到这次setState是由用户交互(如onClick)触发的,于是它将这个更新标记为高优先级。

  中断与切换:React的调度器(Scheduler)会立刻暂停正在进行的低优先级渲染任务(长列表的渲染被“冻结”了)。

  执行高优先级任务:然后,React立即开始为这次高优先级的点击事件执行**Reconciliation(比较虚拟DOM)和Commit(渲染到真实DOM)**的过程。因为这个更新通常很小(比如只是切换一个按钮的状态),所以这个过程飞快地完成了。

  用户感知:用户几乎在点击的瞬间就看到了UI的变化,感觉应用非常“跟手”。

  恢复低优先级任务:在高优先级任务完成后,React会回到之前被暂停的地方,继续它未完成的低优先级渲染工作。

```Fiber将大的更新任务拆解成无数个小块。它在每完成一小块工作后,都会检查有没有更紧急的任务(比如用户的点击)。

如果用户有操作,React会立即暂停当前的渲染工作,去响应用户的操作,让UI立刻给出反馈。

然后再在浏览器空闲时,回来继续之前没完成的渲染任务。

在用户看来,应用始终是流畅的、可交互的。即使后台在进行复杂的渲染,也不会影响他们当前的操作。这就是更好的用户体验和更高的感知性能。

## Nextjs

- page, layout, loading, error, not-found, route, template, middleware, default

## Suspense

## performance

- React:用 ‎`React.lazy` + ‎`Suspense` 实现组件懒加载和代码切分。
- Next.js:页面自动切分,组件用 ‎`next/dynamic` 实现懒加载。

##

```