目录

阿里面试

目录

阿里面试

提供了一个数组结构的 data,要求实现一个 query 方法,返回一个新的数组,query 方法内部有 过滤、排序、分组 等操作,并且支持链式调用,调用最终的 execute 方法返回结果:

class Query {
  constructor(data) {
    this.data = data;
    this.filters = [];
    this.sortByField = null;
    this.groupByField = null;
  }

  where(filterFn) {
    this.filters.push(filterFn);
    return this;
  }

  sortBy(field) {
    this.sortByField = field;
    return this;
  }

  groupBy(field) {
    this.groupByField = field;
    return this;
  }

  execute() {
    let result = [...this.data];

    // Apply filters
    for (const filterFn of this.filters) {
      result = result.filter(filterFn);
    }

    // Sort by field
    if (this.sortByField) {
      result = result.sort((a, b) => a[this.sortByField] - b[this.sortByField]);
    }

    // Group by field
    if (this.groupByField) {
      const groups = {};
      for (const item of result) {
        const key = item[this.groupByField];
        if (!groups[key]) {
          groups[key] = [];
        }
        groups[key].push(item);
      }
      result = Object.values(groups);
    }

    return result;
  }
}

// 使用示例
const list = [
  { id: 1, name: 'John', age: 20 },
  { id: 2, name: 'Jane', age: 25 },
  { id: 3, name: 'John', age: 30 },
  { id: 4, name: 'Bob', age: 22 },
  { id: 5, name: 'Jane', age: 28 }
];

const result = new Query(list)
  .where(item => item.age > 18)
  .sortBy('id')
  .groupBy('name')
  .execute();

console.log(result);

react 实现一个倒计时抢券组件,页面加载时从 10s 开始倒计时,倒计时结束之后点击按钮请求接口进行抢券,同时更新文案等等功能。

import React, { useState, useEffect } from 'react';

function CouponComponent() {
  const [countdown, setCountdown] = useState(10);
  const [canClaim, setCanClaim] = useState(false);
  const [claimText, setClaimText] = useState('倒计时结束,点击抢券');
  const [isClaiming, setIsClaiming] = useState(false);

  useEffect(() => {
    if (countdown > 0) {
      const timer = setInterval(() => {
        setCountdown(prevCountdown => prevCountdown - 1);
      }, 1000);

      return () => {
        clearInterval(timer);
      };
    } else {
      setCanClaim(true);
      setClaimText('倒计时结束,点击抢券');
    }
  }, [countdown]);

  const handleClaim = () => {
    if (canClaim && !isClaiming) {
      setIsClaiming(true);
      setClaimText('正在抢券...');
      // 发送抢券请求
      // 假设请求成功,设置成功文案并执行其他逻辑
      setTimeout(() => {
        setClaimText('抢券成功');
        // 执行其他逻辑...
      }, 2000);
    }
  };

  return (
    <div>
      <div>倒计时: {countdown}s</div>
      <button onClick={handleClaim} disabled={!canClaim}>
        {claimText}
      </button>
    </div>
  );
}

export default CouponComponent;

React 代码层的优化

class 组件和 function 组件两种情况,核心是通过减少渲染次数达到优化目的,具体的优化手段有 PureComponent、shouldComponentUpdate、React.memo、React.useMemo、React.useCallback、React.useRef 等等。

useMemo 和 useCallback 有什么区别

useMemouseCallback都是React中的Hook,用于优化函数组件的性能。它们的主要区别在于它们的用途和返回值类型。

  1. useMemo

    • useMemo用于在依赖项发生变化时进行性能优化,以避免不必要的计算开销。
    • useMemo接受一个函数和依赖项数组作为参数,并返回计算结果的记忆化版本。
    • useMemo会在组件渲染过程中执行传递的函数,并将结果缓存起来。只有当依赖项发生变化时,才会重新执行函数并更新缓存的结果。
    • 当需要根据某些依赖项计算一个值,并且这个计算是昂贵的时候,可以使用useMemo来避免在每次渲染时重复计算。
  2. useCallback

    • useCallback用于在依赖项发生变化时进行性能优化,以避免不必要的函数创建。
    • useCallback接受一个函数和依赖项数组作为参数,并返回一个记忆化版本的回调函数。
    • useCallback会在组件渲染过程中创建并返回一个回调函数。只有当依赖项发生变化时,才会重新创建回调函数。
    • 当需要将一个回调函数传递给子组件,但该回调函数依赖于某些值时,可以使用useCallback来确保子组件在依赖项不变的情况下不会重新渲染。

需要注意的是,尽管useMemouseCallback可以用于性能优化,但在大多数情况下,它们并不是必需的。只有在遇到性能问题并确定存在性能瓶颈时,才应该考虑使用这些优化手段。在大多数情况下,React的自身优化机制已经足够处理性能问题。

useEffect 和 useLayoutEffect 有什么区别

useEffectuseLayoutEffect是React中的Hook,用于处理副作用操作。它们的主要区别在于它们执行的时机。

  1. useEffect

    • useEffect在组件渲染之后异步执行副作用操作。
    • useEffect的副作用操作不会阻塞组件的渲染和布局过程。
    • useEffect中的副作用操作可能在浏览器绘制之后才会执行,因此对于布局和视觉上的变化,可能会有延迟。
  2. useLayoutEffect

    • useLayoutEffect在组件渲染之后同步执行副作用操作。
    • useLayoutEffect的副作用操作会在组件渲染和布局之间同步执行。
    • useLayoutEffect中的副作用操作会在浏览器执行绘制之前完成,以确保在组件布局和视觉上的变化之前执行。

在大多数情况下,应优先考虑使用useEffect,因为它不会阻塞渲染过程,并且在大多数情况下可以满足副作用操作的需求。只有在需要在布局和视觉上变化之前立即执行副作用操作时,才需要使用useLayoutEffect

需要注意的是,在使用useLayoutEffect时,由于它在渲染和布局之间同步执行,可能会导致性能问题,因此需要谨慎使用,确保副作用操作的性能开销较小。在大多数情况下,使用useEffect即可满足需求,并且能够更好地优化性能。

useEffect 对应在 class 中都生命周期怎么写

在类组件中,useEffect的功能可以通过生命周期方法来实现。下面是useEffect对应的一些常见生命周期方法以及它们的作用:

  1. componentDidMount

    • componentDidMount在组件挂载后立即调用,可以用于执行副作用操作,例如订阅事件、请求数据等。
    • componentDidMount中执行的操作相当于在useEffect的依赖项为空数组时执行。
  2. componentDidUpdate

    • componentDidUpdate在组件更新后调用,在首次渲染后以及每次更新时都会调用。
    • 可以使用componentDidUpdate来执行副作用操作,但需要在操作之前进行条件检查,以避免无限循环。
  3. componentWillUnmount

    • componentWillUnmount在组件卸载之前调用,可以用于清理副作用操作,例如取消订阅、清除定时器等。
    • componentWillUnmount中执行的操作相当于在useEffect的清除函数中执行。

下面是一个示例,演示了如何使用类组件的生命周期方法来替代useEffect的功能:

class MyComponent extends React.Component {
  componentDidMount() {
    // componentDidMount 对应 useEffect(() => { ... }, [])
    console.log('Component did mount');
    
    // 执行副作用操作,例如订阅事件、请求数据等
    // ...
  }

  componentDidUpdate(prevProps, prevState) {
    // componentDidUpdate 对应 useEffect(() => { ... }, [依赖项])
    console.log('Component did update');

    // 执行副作用操作,例如根据依赖项的变化请求数据等
    // ...
  }

  componentWillUnmount() {
    // componentWillUnmount 对应 useEffect 的清除函数
    console.log('Component will unmount');

    // 清理副作用操作,例如取消订阅、清除定时器等
    // ...
  }

  render() {
    return <div>My Component</div>;
  }
}

在上面的示例中,componentDidMount方法相当于在组件挂载后执行的副作用操作,componentDidUpdate方法相当于在依赖项更新后执行的副作用操作,componentWillUnmount方法相当于清理函数。

需要注意的是,在函数组件中使用useEffect时,每次渲染都会创建新的副作用操作和清理函数,而在类组件中,生命周期方法只会在特定的时机调用一次。

如果在 if 里面写 useEffect 会有什么表现

在条件语句里面写 useEffect 控制台会出现报错,因为 hook 的规则就是不能在条件语句或者循环语句里面写,这点在 react 官方文档里面也有提到。

React 的 Fiber 架构是什么

作为架构来说,在旧的架构中,Reconciler(协调器)采用递归的方式执行,无法中断,节点数据保存在递归的调用栈中,被称为 Stack Reconciler,stack 就是调用栈;在新的架构中,Reconciler(协调器)是基于 fiber 实现的,节点数据保存在 fiber 中,所以被称为 fiber Reconciler。

作为静态数据结构来说,每个 fiber 对应一个组件,保存了这个组件的类型对应的 dom 节点信息,这个时候,fiber 节点就是我们所说的虚拟 DOM。

作为动态工作单元来说,fiber 节点保存了该节点需要更新的状态,以及需要执行的副作用。