MST

星途 面试题库

面试题:React自定义事件与原生事件的交互及性能调优策略

在React项目中,既要使用自定义事件,又要使用原生DOM事件。描述这两种事件在同一组件中的交互场景,例如如何避免冲突,如何进行事件委托等。同时,针对大规模事件触发场景,阐述详细的性能调优策略,并给出相关代码示例。
13.0万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

1. 自定义事件与原生DOM事件交互场景

避免冲突

  • 命名规范:在React中,自定义事件命名可以采用特定前缀,如my-,与原生DOM事件名称区分。例如,原生事件有click,自定义事件可命名为my-custom-event
  • 事件处理函数隔离:在组件中,为原生DOM事件和自定义事件分别设置不同的处理函数。例如:
import React, { useEffect } from 'react';

const MyComponent = () => {
  const handleNativeClick = () => {
    console.log('原生click事件触发');
  };

  const handleCustomEvent = () => {
    console.log('自定义事件触发');
  };

  useEffect(() => {
    const customEvent = new CustomEvent('my-custom-event');
    document.addEventListener('my-custom-event', handleCustomEvent);
    return () => {
      document.removeEventListener('my-custom-event', handleCustomEvent);
    };
  }, []);

  return <button onClick={handleNativeClick}>点击</button>;
};

export default MyComponent;

事件委托

  • 原生DOM事件委托:在React中,对于原生DOM事件的委托,可利用React自身的事件系统。例如,有多个子元素都需要绑定click事件,可以在父元素上统一绑定click事件,通过event.target判断具体触发的子元素。
import React from 'react';

const ParentComponent = () => {
  const handleClick = (event) => {
    if (event.target.tagName === 'LI') {
      console.log(`点击了列表项: ${event.target.textContent}`);
    }
  };

  return (
    <ul onClick={handleClick}>
      <li>列表项1</li>
      <li>列表项2</li>
      <li>列表项3</li>
    </ul>
  );
};

export default ParentComponent;
  • 自定义事件委托:对于自定义事件委托,可以通过在祖先元素上监听自定义事件来实现。例如:
import React, { useEffect } from 'react';

const ParentComponent = () => {
  const handleCustomEvent = () => {
    console.log('父组件捕获到自定义事件');
  };

  useEffect(() => {
    document.addEventListener('my-custom-event', handleCustomEvent);
    return () => {
      document.removeEventListener('my-custom-event', handleCustomEvent);
    };
  }, []);

  return (
    <div>
      <ChildComponent />
    </div>
  );
};

const ChildComponent = () => {
  const triggerCustomEvent = () => {
    const customEvent = new CustomEvent('my-custom-event');
    document.dispatchEvent(customEvent);
  };

  return <button onClick={triggerCustomEvent}>触发自定义事件</button>;
};

export default ParentComponent;

2. 大规模事件触发场景性能调优策略

防抖(Debounce)

  • 原理:在事件触发后,等待一定时间(如300ms),如果这段时间内事件再次触发,则重新计时,只有在等待时间结束且没有新的事件触发时,才执行真正的处理函数。
  • 代码示例
import React, { useState } from 'react';

const debounce = (func, delay) => {
  let timer;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
};

const MyComponent = () => {
  const [inputValue, setInputValue] = useState('');
  const handleInputChange = debounce((event) => {
    console.log('防抖后处理输入变化:', event.target.value);
    setInputValue(event.target.value);
  }, 300);

  return <input onChange={handleInputChange} />;
};

export default MyComponent;

节流(Throttle)

  • 原理:在事件频繁触发的过程中,每隔一定时间(如200ms)执行一次处理函数,无论事件触发多么频繁,都以固定时间间隔执行。
  • 代码示例
import React, { useState } from 'react';

const throttle = (func, limit) => {
  let inThrottle;
  return function() {
    const context = this;
    const args = arguments;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
};

const MyComponent = () => {
  const [scrollY, setScrollY] = useState(0);
  const handleScroll = throttle(() => {
    setScrollY(window.pageYOffset);
    console.log('节流后处理滚动事件:', window.pageYOffset);
  }, 200);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return (
    <div>
      <p>滚动位置: {scrollY}</p>
    </div>
  );
};

export default MyComponent;

批量更新

  • 原理:在React中,默认情况下,React会在事件处理函数结束后批量更新状态,以提高性能。但在某些异步操作中,可能需要手动进行批量更新。例如,在setTimeout或Promise回调中多次调用setState,React默认会每次都进行更新,这会导致性能下降。通过unstable_batchedUpdates(React 18之前)或flushSync(React 18及之后)可以将多次setState操作合并为一次更新。
  • React 18之前代码示例
import React, { useState } from 'react';
import ReactDOM from'react-dom';
import { unstable_batchedUpdates } from'react-dom';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    unstable_batchedUpdates(() => {
      setCount(count + 1);
      setCount(count + 1);
    });
  };

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={handleClick}>增加计数</button>
    </div>
  );
};

ReactDOM.render(<MyComponent />, document.getElementById('root'));
  • React 18及之后代码示例
import React, { useState } from'react';
import ReactDOM from'react-dom/client';
import { flushSync } from'react-dom';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    flushSync(() => {
      setCount(count + 1);
      setCount(count + 1);
    });
  };

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={handleClick}>增加计数</button>
    </div>
  );
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<MyComponent />);