2024前端面试系列---手写react组件

381 阅读4分钟

1. 手写一个简单的计数器组件

要求:实现一个按钮,每次点击时数字加 1。

import React, { useState } from 'react';

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  const handleIncrement = () => {
    // 使用 prevCount 确保获取到最新的 count 值
    setCount(prevCount => prevCount + 1);
  };

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

export default Counter;

2. 实现一个受控输入框组件

要求:用户输入内容后显示到页面上。

import React, { useState } from 'react';

function ControlledInput() {
  // 初始化 state,用于保存输入框的值
  const [inputValue, setInputValue] = useState('');

  // 处理输入框变化的函数
  const handleChange = (event) => {
    // 更新 state 的值
    setInputValue(event.target.value);
  };

  return (
    <div>
      <input
        type="text"
        value={inputValue}  // 绑定 state 到输入框的 value 属性
        onChange={handleChange}  // 输入框内容变化时触发 handleChange
      />
      <p>你输入的内容是: {inputValue}</p>  {/* 显示输入框内容 */}
    </div>
  );
}

export default ControlledInput;

延伸问题

  • 如何添加输入验证功能?
  • 如果想在用户停止输入 500ms 后才更新内容,应该如何实现?
import React, { useState, useEffect } from 'react';

function ControlledInput() {
  // State for input value
  const [inputValue, setInputValue] = useState('');
  // State for error message
  const [error, setError] = useState('');

  // Timer for debouncing
  const [debounceTimer, setDebounceTimer] = useState(null);

  // Input validation function
  const validateInput = (value) => {
    // For demonstration: Only allow alphanumeric characters
    const regex = /^[a-zA-Z0-9]*$/;
    if (!regex.test(value)) {
      return '输入只能包含字母和数字';
    }
    return '';
  };

  // Handle input change with debounce logic
  const handleChange = (event) => {
    const value = event.target.value;

    // Clear the previous timer if any
    if (debounceTimer) {
      clearTimeout(debounceTimer);
    }

    // Set the error message based on validation
    const validationError = validateInput(value);
    setError(validationError);

    // Set a new timer to update the state after 500ms
    const timer = setTimeout(() => {
      setInputValue(value);
    }, 500);
    setDebounceTimer(timer);
  };

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleChange}
        placeholder="请输入字母和数字"
      />
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <p>你输入的内容是: {inputValue}</p>
    </div>
  );
}

export default ControlledInput;


3. 手写一个模态框组件

要求:实现一个模态框,可以通过父组件控制其显示与隐藏。

  1. 模态框组件 (Modal)
import React from 'react';

function Modal({ isVisible, onClose, children }) {
  if (!isVisible) return null;  // 如果不可见,不渲染

  return (
    <div style={overlayStyle}>
      <div style={modalStyle}>
        <button onClick={onClose} style={closeButtonStyle}>关闭</button>
        {children}  {/* 模态框内容可以通过 `children` 插槽传递 */}
      </div>
    </div>
  );
}

// 样式:简单的模态框和遮罩层
const overlayStyle = {
  position: 'fixed',
  top: 0,
  left: 0,
  right: 0,
  bottom: 0,
  backgroundColor: 'rgba(0, 0, 0, 0.5)',
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
};

const modalStyle = {
  backgroundColor: 'white',
  padding: '20px',
  borderRadius: '5px',
  maxWidth: '400px',
  minWidth: '300px',
  boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
};

const closeButtonStyle = {
  position: 'absolute',
  top: '10px',
  right: '10px',
  background: 'none',
  border: 'none',
  fontSize: '16px',
  cursor: 'pointer',
};

export default Modal;

  1. 父组件 (App)
import React, { useState } from 'react';
import Modal from './Modal';

function App() {
  const [isModalVisible, setIsModalVisible] = useState(false);

  // 控制模态框显示
  const showModal = () => {
    setIsModalVisible(true);
  };

  // 关闭模态框
  const hideModal = () => {
    setIsModalVisible(false);
  };

  return (
    <div>
      <button onClick={showModal}>显示模态框</button>
      
      {/* 渲染模态框,传入 isVisible 和 onClose props */}
      <Modal isVisible={isModalVisible} onClose={hideModal}>
        <h2>欢迎来到模态框</h2>
        <p>这是模态框的内容!</p>
      </Modal>
    </div>
  );
}

export default App;

延伸问题

  • 如何优化性能,避免频繁渲染?
  • 如何让模态框支持动画效果?

4. 手写一个 Tab 组件

要求:实现一个简单的标签页切换功能。

function Tabs({ tabs }) {
  const [activeTab, setActiveTab] = React.useState(0);

  return (
    <div>
      <div style={{ display: "flex", borderBottom: "1px solid #ccc" }}>
        {tabs.map((tab, index) => (
          <button
            key={index}
            style={{ marginRight: "10px", padding: "10px", borderBottom: activeTab === index ? "2px solid blue" : "none" }}
            onClick={() => setActiveTab(index)}
          >
            {tab.label}
          </button>
        ))}
      </div>
      <div style={{ padding: "20px" }}>{tabs[activeTab].content}</div>
    </div>
  );
}

使用示例

const tabData = [
  { label: "Tab 1", content: "这是 Tab 1 的内容" },
  { label: "Tab 2", content: "这是 Tab 2 的内容" },
  { label: "Tab 3", content: "这是 Tab 3 的内容" },
];

<Tabs tabs={tabData} />;

延伸问题

  • 如何支持动态添加和删除标签页?
  • 如何在切换标签页时添加动画?

5. 实现一个自定义 Hook

要求:实现一个自定义 Hook,用于倒计时功能。

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

function useCountdown(initialTime = 60) {
  // 状态管理剩余时间
  const [timeLeft, setTimeLeft] = useState(initialTime);
  const [isRunning, setIsRunning] = useState(false); // 是否在倒计时中
  const timerRef = useRef(null); // 用于存储定时器引用

  // 启动倒计时
  const start = () => {
    if (timeLeft > 0 && !isRunning) {
      setIsRunning(true);
    }
  };

  // 暂停倒计时
  const pause = () => {
    setIsRunning(false);
  };

  // 重置倒计时
  const reset = (newTime) => {
    pause();
    setTimeLeft(newTime !== undefined ? newTime : initialTime);
  };

  // 倒计时逻辑
  useEffect(() => {
    if (isRunning) {
      timerRef.current = setInterval(() => {
        setTimeLeft((prevTime) => {
          if (prevTime <= 1) {
            clearInterval(timerRef.current);
            setIsRunning(false); // 停止倒计时
            return 0;
          }
          return prevTime - 1;
        });
      }, 1000);
    }

    // 清理定时器
    return () => clearInterval(timerRef.current);
  }, [isRunning]);

  return {
    timeLeft,
    isRunning,
    start,
    pause,
    reset,
  };
}

export default useCountdown;

延伸问题

  • 如何让倒计时支持暂停和恢复?
  • 如何让倒计时结束后触发回调?

6. 实现一个高阶组件 (HOC)

要求:实现一个高阶组件,给组件添加计时功能。

function withTimer(WrappedComponent) {
  return function TimerComponent(props) {
    const [time, setTime] = React.useState(0);

    React.useEffect(() => {
      const interval = setInterval(() => setTime((prev) => prev + 1), 1000);
      return () => clearInterval(interval);
    }, []);

    return <WrappedComponent {...props} time={time} />;
  };
}

function DisplayTimer({ time }) {
  return <p>已计时: {time} 秒</p>;
}

const EnhancedTimer = withTimer(DisplayTimer);

延伸问题

  • 如何将计时器状态提升到父组件?
  • 与自定义 Hook 的区别?

7. 实现一个异步数据加载组件

要求:加载远程数据并显示,同时显示加载状态。

function DataLoader({ url }) {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    setLoading(true);
    fetch(url)
      .then((res) => res.json())
      .then((data) => setData(data))
      .catch(console.error)
      .finally(() => setLoading(false));
  }, [url]);

  if (loading) return <p>加载中...</p>;
  if (!data) return <p>未加载到数据</p>;

  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

延伸问题

  • 如何处理请求失败?
  • 如何添加刷新功能?