Published on
· 45 min read

React面试题完全指南

Authors
  • avatar
    Name
    felixDu
    Twitter
Table of Contents

React面试题完全指南

目录

  1. React基础概念
  2. React Hooks
  3. React性能优化
  4. React状态管理
  5. React高级特性
  6. React Router
  7. React生态系统

React基础概念

1. 什么是React?它的主要特点是什么?

面试官提问: "请简要介绍一下React,以及它的核心特点是什么?"

参考答案: React是由Facebook开发的用于构建用户界面的JavaScript库。它的主要特点包括:

  1. 组件化:将UI拆分成独立、可复用的组件
  2. 虚拟DOM:通过虚拟DOM提高性能
  3. 单向数据流:数据从父组件流向子组件
  4. JSX语法:在JavaScript中编写类似HTML的代码
  5. 声明式编程:描述UI应该是什么样子,而不是如何改变它
// 组件示例
function Welcome(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// 使用组件
function App() {
  return (
    <div>
      <Welcome name="Alice" />
      <Welcome name="Bob" />
    </div>
  );
}

面试官追问: "虚拟DOM是如何工作的?"

深入回答: 虚拟DOM是React在内存中维护的一个JavaScript对象树,它是真实DOM的抽象表示。工作流程如下:

  1. 当状态改变时,React创建新的虚拟DOM树
  2. 将新旧虚拟DOM树进行比较(Diff算法)
  3. 计算出最小的变更集
  4. 批量更新真实DOM
// 虚拟DOM的简化表示
const vdom = {
  type: 'div',
  props: {
    className: 'container',
    children: [
      {
        type: 'h1',
        props: {
          children: 'Hello World'
        }
      }
    ]
  }
};

2. 类组件和函数组件的区别

面试官提问: "请比较一下React中的类组件和函数组件,它们有什么区别?"

参考答案:

// 类组件
class ClassComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    console.log('组件已挂载');
  }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>增加</button>
      </div>
    );
  }
}

// 函数组件
function FunctionComponent(props) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('组件已挂载');
  }, []);

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

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

主要区别:

  1. 语法:类组件使用ES6类语法,函数组件使用函数
  2. 状态管理:类组件使用this.state,函数组件使用useState
  3. 生命周期:类组件有生命周期方法,函数组件使用useEffect
  4. 性能:函数组件通常更轻量
  5. 代码简洁性:函数组件更简洁

面试官追问: "在什么情况下你会选择使用类组件?"

深入回答: 虽然React推荐使用函数组件,但以下情况可能需要类组件:

  • 需要使用Error Boundaries(错误边界)
  • 需要特定的生命周期方法如getSnapshotBeforeUpdate
  • 维护旧代码库

3. JSX是什么?它是如何工作的?

面试官提问: "解释一下JSX,以及它在React中的作用。"

参考答案: JSX(JavaScript XML)是React的语法扩展,允许在JavaScript中编写类似HTML的代码。

// JSX代码
const element = <h1 className="greeting">Hello, world!</h1>;

// 编译后的JavaScript
const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, world!'
);

JSX特点:

  1. 可以嵌入JavaScript表达式
  2. 属性使用驼峰命名(如className而非class)
  3. 必须有一个根元素
  4. 可以防止XSS攻击
// JSX示例
function TodoList({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>
          {item.done ? <s>{item.text}</s> : item.text}
        </li>
      ))}
    </ul>
  );
}

4. Props和State的区别

面试官提问: "请解释Props和State的区别,以及它们的使用场景。"

参考答案:

Props(属性):

  • 从父组件传递给子组件的数据
  • 只读,不能修改
  • 用于组件间通信

State(状态):

  • 组件内部管理的数据
  • 可以修改
  • 改变时会触发重新渲染
// Props示例
function Parent() {
  const [parentData, setParentData] = useState('父组件数据');

  return <Child message={parentData} onUpdate={setParentData} />;
}

function Child({ message, onUpdate }) {
  // message是props,只读
  const [localState, setLocalState] = useState('子组件状态');

  return (
    <div>
      <p>来自父组件的Props: {message}</p>
      <p>本地State: {localState}</p>
      <button onClick={() => setLocalState('更新的状态')}>
        更新State
      </button>
      <button onClick={() => onUpdate('子组件更新了父组件')}>
        更新父组件
      </button>
    </div>
  );
}

面试官追问: "如何进行子组件向父组件的通信?"

深入回答: 通过回调函数实现:

function Parent() {
  const [data, setData] = useState('');

  const handleChildData = (childData) => {
    setData(childData);
  };

  return (
    <div>
      <p>从子组件接收的数据: {data}</p>
      <Child onSendData={handleChildData} />
    </div>
  );
}

function Child({ onSendData }) {
  const [input, setInput] = useState('');

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <button onClick={() => onSendData(input)}>
        发送给父组件
      </button>
    </div>
  );
}

React Hooks

5. useState Hook详解

面试官提问: "请详细解释useState Hook的工作原理和使用注意事项。"

参考答案:

useState是React最基础的Hook,用于在函数组件中添加状态。

function Counter() {
  // 基本用法
  const [count, setCount] = useState(0);

  // 惰性初始化
  const [expensiveState, setExpensiveState] = useState(() => {
    return computeExpensiveValue();
  });

  // 函数式更新
  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

  // 批量更新
  const handleMultipleUpdates = () => {
    setCount(c => c + 1);
    setCount(c => c + 1);
    setCount(c => c + 1);
    // 最终count增加3
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={handleMultipleUpdates}>+3</button>
    </div>
  );
}

注意事项:

  1. 不要在条件语句中调用useState
  2. 状态更新是异步的
  3. 对象和数组需要创建新的引用
// 对象状态更新
const [user, setUser] = useState({ name: 'John', age: 30 });

// 错误方式
user.age = 31;
setUser(user); // 不会触发重新渲染

// 正确方式
setUser({ ...user, age: 31 });
// 或
setUser(prevUser => ({ ...prevUser, age: 31 }));

6. useEffect Hook详解

面试官提问: "解释useEffect的作用,以及如何正确使用依赖数组。"

参考答案:

useEffect用于处理副作用,如数据获取、订阅、手动DOM操作等。

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // 基本用法 - 每次渲染后执行
  useEffect(() => {
    console.log('组件渲染了');
  });

  // 带依赖数组 - 依赖变化时执行
  useEffect(() => {
    const fetchUser = async () => {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        setUser(data);
      } catch (error) {
        console.error('获取用户失败:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, [userId]); // 只在userId变化时重新获取

  // 清理函数
  useEffect(() => {
    const timer = setTimeout(() => {
      console.log('定时器触发');
    }, 1000);

    // 清理函数
    return () => {
      clearTimeout(timer);
    };
  }, []);

  // 空依赖数组 - 仅在挂载时执行
  useEffect(() => {
    console.log('组件挂载');

    return () => {
      console.log('组件卸载');
    };
  }, []);

  if (loading) return <div>加载中...</div>;

  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  );
}

面试官追问: "useEffect和useLayoutEffect有什么区别?"

深入回答:

function LayoutEffectExample() {
  const [value, setValue] = useState(0);
  const ref = useRef();

  // useEffect - 异步执行,不阻塞浏览器绘制
  useEffect(() => {
    console.log('useEffect执行');
    // 可能会看到闪烁
    if (ref.current) {
      ref.current.style.transform = `translateX(${value}px)`;
    }
  });

  // useLayoutEffect - 同步执行,阻塞浏览器绘制
  useLayoutEffect(() => {
    console.log('useLayoutEffect执行');
    // 不会看到闪烁
    if (ref.current) {
      ref.current.style.transform = `translateX(${value}px)`;
    }
  });

  return <div ref={ref}>移动的元素</div>;
}

7. 自定义Hook

面试官提问: "如何创建自定义Hook?请给出一个实际的例子。"

参考答案:

自定义Hook是复用状态逻辑的函数,必须以"use"开头。

// 自定义Hook - 本地存储
function useLocalStorage(key, initialValue) {
  // 从localStorage读取初始值
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error('读取localStorage失败:', error);
      return initialValue;
    }
  });

  // 更新localStorage的函数
  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error('写入localStorage失败:', error);
    }
  };

  return [storedValue, setValue];
}

// 使用自定义Hook
function Settings() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  const [language, setLanguage] = useLocalStorage('language', 'zh-CN');

  return (
    <div>
      <select value={theme} onChange={(e) => setTheme(e.target.value)}>
        <option value="light">浅色</option>
        <option value="dark">深色</option>
      </select>
      <select value={language} onChange={(e) => setLanguage(e.target.value)}>
        <option value="zh-CN">中文</option>
        <option value="en-US">英文</option>
      </select>
    </div>
  );
}

// 更复杂的例子 - 数据获取Hook
function useFetch(url, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();

    const fetchData = async () => {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch(url, {
          ...options,
          signal: abortController.signal
        });

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const jsonData = await response.json();
        setData(jsonData);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => {
      abortController.abort();
    };
  }, [url]);

  return { data, loading, error, refetch: () => {} };
}

8. useContext Hook

面试官提问: "解释useContext的使用场景,以及如何避免性能问题。"

参考答案:

useContext用于消费React Context,避免props drilling问题。

// 创建Context
const ThemeContext = createContext();
const UserContext = createContext();

// Context Provider
function App() {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState({ name: 'John' });

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <UserContext.Provider value={{ user, setUser }}>
        <Layout />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

// 使用useContext
function DeepChildComponent() {
  const { theme, setTheme } = useContext(ThemeContext);
  const { user } = useContext(UserContext);

  return (
    <div className={`theme-${theme}`}>
      <h1>欢迎, {user.name}!</h1>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        切换主题
      </button>
    </div>
  );
}

// 性能优化 - 拆分Context
const ThemeStateContext = createContext();
const ThemeDispatchContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeStateContext.Provider value={theme}>
      <ThemeDispatchContext.Provider value={setTheme}>
        {children}
      </ThemeDispatchContext.Provider>
    </ThemeStateContext.Provider>
  );
}

// 自定义Hook封装
function useTheme() {
  const theme = useContext(ThemeStateContext);
  const setTheme = useContext(ThemeDispatchContext);

  if (theme === undefined) {
    throw new Error('useTheme必须在ThemeProvider内使用');
  }

  return { theme, setTheme };
}

面试官追问: "Context的性能问题如何解决?"

深入回答:

// 1. 使用memo优化子组件
const ExpensiveChild = memo(({ data }) => {
  console.log('ExpensiveChild渲染');
  return <div>{/* 复杂的UI */}</div>;
});

// 2. 使用useMemo缓存Context值
function OptimizedProvider({ children }) {
  const [state, setState] = useState({ count: 0, name: 'John' });

  const value = useMemo(
    () => ({
      state,
      increment: () => setState(s => ({ ...s, count: s.count + 1 })),
      updateName: (name) => setState(s => ({ ...s, name }))
    }),
    [state]
  );

  return (
    <MyContext.Provider value={value}>
      {children}
    </MyContext.Provider>
  );
}

9. useReducer Hook

面试官提问: "什么时候应该使用useReducer而不是useState?"

参考答案:

useReducer适合管理复杂的状态逻辑,特别是当状态更新依赖于之前的状态时。

// 复杂状态管理示例
const initialState = {
  count: 0,
  history: [],
  error: null
};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {
        ...state,
        count: state.count + 1,
        history: [...state.history, `+1 at ${new Date().toLocaleTimeString()}`]
      };
    case 'decrement':
      if (state.count <= 0) {
        return {
          ...state,
          error: '计数不能小于0'
        };
      }
      return {
        ...state,
        count: state.count - 1,
        history: [...state.history, `-1 at ${new Date().toLocaleTimeString()}`],
        error: null
      };
    case 'reset':
      return initialState;
    case 'clearError':
      return { ...state, error: null };
    default:
      throw new Error(`未知的action类型: ${action.type}`);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>计数: {state.count}</p>
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}

      <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>

      <h3>历史记录:</h3>
      <ul>
        {state.history.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

// 结合Context使用
const StateContext = createContext();
const DispatchContext = createContext();

function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialAppState);

  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}

React性能优化

10. React.memo和useMemo的区别

面试官提问: "请解释React.memo和useMemo的区别和使用场景。"

参考答案:

React.memo - 用于优化组件重渲染

// 未优化的组件
function ExpensiveComponent({ data, id }) {
  console.log('ExpensiveComponent渲染');
  return <div>{/* 复杂的渲染逻辑 */}</div>;
}

// 使用React.memo优化
const MemoizedComponent = React.memo(ExpensiveComponent);

// 自定义比较函数
const MemoizedComponentWithCompare = React.memo(
  ExpensiveComponent,
  (prevProps, nextProps) => {
    // 返回true表示相等,不需要重新渲染
    return prevProps.id === nextProps.id;
  }
);

// 父组件
function Parent() {
  const [count, setCount] = useState(0);
  const data = { value: 'static data' };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <MemoizedComponent data={data} id="123" />
    </div>
  );
}

useMemo - 用于缓存计算结果

function DataProcessor({ items, filter }) {
  // 缓存计算结果
  const filteredItems = useMemo(() => {
    console.log('执行过滤计算');
    return items.filter(item => item.category === filter);
  }, [items, filter]);

  // 缓存组件
  const expensiveComponent = useMemo(() => {
    return <ExpensiveChild data={filteredItems} />;
  }, [filteredItems]);

  // 缓存对象,避免子组件不必要的重渲染
  const config = useMemo(() => ({
    theme: 'dark',
    language: 'zh-CN'
  }), []); // 空依赖,永远不变

  return (
    <div>
      <ChildComponent config={config} />
      {expensiveComponent}
    </div>
  );
}

面试官追问: "什么时候不应该使用这些优化?"

深入回答:

  1. 过早优化是万恶之源
  2. 简单组件不需要memo
  3. 缓存本身也有成本
// 不需要优化的例子
function SimpleComponent({ text }) {
  return <span>{text}</span>; // 太简单,memo反而增加开销
}

// 需要优化的例子
function ComplexList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <MemoizedListItem key={item.id} item={item} />
      ))}
    </ul>
  );
}

11. useCallback的使用

面试官提问: "解释useCallback的作用,以及它如何帮助性能优化。"

参考答案:

useCallback用于缓存函数引用,避免子组件不必要的重渲染。

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  // 未优化 - 每次渲染都创建新函数
  const handleAddBad = () => {
    setTodos([...todos, { id: Date.now(), text: inputValue }]);
    setInputValue('');
  };

  // 使用useCallback优化
  const handleAdd = useCallback(() => {
    setTodos(prevTodos => [...prevTodos, { id: Date.now(), text: inputValue }]);
    setInputValue('');
  }, [inputValue]); // 只在inputValue变化时更新函数

  // 传递给子组件的回调
  const handleDelete = useCallback((id) => {
    setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
  }, []); // 空依赖,函数永不变化

  // 结合useMemo使用
  const handleToggle = useCallback((id) => {
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    );
  }, []);

  const stats = useMemo(() => ({
    total: todos.length,
    completed: todos.filter(t => t.done).length
  }), [todos]);

  return (
    <div>
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <button onClick={handleAdd}>添加</button>

      <TodoStats stats={stats} />

      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onDelete={handleDelete}
          onToggle={handleToggle}
        />
      ))}
    </div>
  );
}

// 子组件使用React.memo优化
const TodoItem = React.memo(({ todo, onDelete, onToggle }) => {
  console.log('TodoItem渲染:', todo.id);

  return (
    <li>
      <input
        type="checkbox"
        checked={todo.done}
        onChange={() => onToggle(todo.id)}
      />
      <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={() => onDelete(todo.id)}>删除</button>
    </li>
  );
});

12. 虚拟列表/无限滚动

面试官提问: "如何实现一个高性能的长列表?"

参考答案:

对于大量数据的列表,使用虚拟滚动技术只渲染可见区域的元素。

// 简化的虚拟列表实现
function VirtualList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);
  const scrollElementRef = useRef();

  // 计算可见区域
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    items.length - 1,
    Math.floor((scrollTop + containerHeight) / itemHeight)
  );

  // 只渲染可见项
  const visibleItems = items.slice(startIndex, endIndex + 1);

  // 计算偏移量
  const offsetY = startIndex * itemHeight;

  // 总高度,用于显示滚动条
  const totalHeight = items.length * itemHeight;

  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
  };

  return (
    <div
      ref={scrollElementRef}
      style={{
        height: containerHeight,
        overflow: 'auto',
        position: 'relative'
      }}
      onScroll={handleScroll}
    >
      {/* 占位元素,撑开滚动条 */}
      <div style={{ height: totalHeight }} />

      {/* 可见项容器 */}
      <div
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          right: 0,
          transform: `translateY(${offsetY}px)`
        }}
      >
        {visibleItems.map((item, index) => (
          <div
            key={startIndex + index}
            style={{ height: itemHeight }}
          >
            {item.content}
          </div>
        ))}
      </div>
    </div>
  );
}

// 使用react-window库的示例
import { FixedSizeList } from 'react-window';

function OptimizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

13. 代码分割和懒加载

面试官提问: "如何在React中实现代码分割?"

参考答案:

// 1. 使用React.lazy进行组件懒加载
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  const [showHeavy, setShowHeavy] = useState(false);

  return (
    <div>
      <button onClick={() => setShowHeavy(true)}>
        加载重型组件
      </button>

      {showHeavy && (
        <Suspense fallback={<div>加载中...</div>}>
          <HeavyComponent />
        </Suspense>
      )}
    </div>
  );
}

// 2. 路由级别的代码分割
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function AppRouter() {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// 3. 条件加载
function ConditionalLoad() {
  const [Component, setComponent] = useState(null);

  const loadComponent = async (componentName) => {
    try {
      const module = await import(`./components/${componentName}`);
      setComponent(() => module.default);
    } catch (error) {
      console.error('组件加载失败:', error);
    }
  };

  return (
    <div>
      <button onClick={() => loadComponent('Chart')}>加载图表</button>
      <button onClick={() => loadComponent('Table')}>加载表格</button>

      {Component && <Component />}
    </div>
  );
}

// 4. 预加载组件
const PreloadableComponent = lazy(() => import('./PreloadableComponent'));

// 预加载函数
export const preloadComponent = () => {
  import('./PreloadableComponent');
};

// 在合适的时机预加载
function Navigation() {
  return (
    <nav>
      <Link
        to="/heavy-page"
        onMouseEnter={preloadComponent}
      >
        重型页面(悬停预加载)
      </Link>
    </nav>
  );
}

React状态管理

14. 状态提升和组件通信

面试官提问: "在React中,如何实现兄弟组件之间的通信?"

参考答案:

// 1. 状态提升到共同父组件
function Parent() {
  const [sharedData, setSharedData] = useState('');

  return (
    <div>
      <SiblingA onDataChange={setSharedData} />
      <SiblingB data={sharedData} />
    </div>
  );
}

function SiblingA({ onDataChange }) {
  return (
    <input
      onChange={(e) => onDataChange(e.target.value)}
      placeholder="在A组件输入"
    />
  );
}

function SiblingB({ data }) {
  return <div>B组件接收到: {data}</div>;
}

// 2. 使用Context进行跨层级通信
const MessageContext = createContext();

function MessageProvider({ children }) {
  const [messages, setMessages] = useState([]);

  const sendMessage = (message) => {
    setMessages(prev => [...prev, message]);
  };

  return (
    <MessageContext.Provider value={{ messages, sendMessage }}>
      {children}
    </MessageContext.Provider>
  );
}

// 3. 使用自定义事件总线
class EventBus {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data));
    }
  }

  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
  }
}

const eventBus = new EventBus();

// 使用自定义Hook封装
function useEventBus(event, handler) {
  useEffect(() => {
    eventBus.on(event, handler);
    return () => eventBus.off(event, handler);
  }, [event, handler]);

  return eventBus.emit.bind(eventBus, event);
}

// 组件中使用
function ComponentA() {
  const emit = useEventBus('dataUpdate', () => {});

  return (
    <button onClick={() => emit({ data: 'from A' })}>
      发送数据
    </button>
  );
}

function ComponentB() {
  const [data, setData] = useState('');

  useEventBus('dataUpdate', (payload) => {
    setData(payload.data);
  });

  return <div>接收到: {data}</div>;
}

15. Redux vs Context API

面试官提问: "什么时候应该使用Redux,什么时候Context API就足够了?"

参考答案:

Context API适用场景:

  • 中小型应用
  • 状态变化不频繁
  • 状态结构简单
  • 不需要时间旅行调试
// Context API示例
const AppContext = createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [notifications, setNotifications] = useState([]);

  const value = {
    user,
    theme,
    notifications,
    login: async (credentials) => {
      const user = await api.login(credentials);
      setUser(user);
    },
    logout: () => setUser(null),
    toggleTheme: () => setTheme(t => t === 'light' ? 'dark' : 'light'),
    addNotification: (notification) => {
      setNotifications(prev => [...prev, notification]);
    }
  };

  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}

Redux适用场景:

  • 大型应用
  • 复杂的状态逻辑
  • 需要可预测的状态更新
  • 需要强大的开发工具
// Redux示例
// store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
import notificationReducer from './notificationSlice';

export const store = configureStore({
  reducer: {
    user: userReducer,
    notifications: notificationReducer,
  },
});

// userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const loginUser = createAsyncThunk(
  'user/login',
  async (credentials) => {
    const response = await api.login(credentials);
    return response.data;
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    loading: false,
    error: null,
  },
  reducers: {
    logout: (state) => {
      state.data = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(loginUser.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(loginUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(loginUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

export const { logout } = userSlice.actions;
export default userSlice.reducer;

// 组件中使用
import { useSelector, useDispatch } from 'react-redux';

function LoginComponent() {
  const dispatch = useDispatch();
  const { data: user, loading, error } = useSelector(state => state.user);

  const handleLogin = (credentials) => {
    dispatch(loginUser(credentials));
  };

  return (
    // UI代码
  );
}

16. 状态管理最佳实践

面试官提问: "分享一些React状态管理的最佳实践。"

参考答案:

// 1. 状态正规化
// 不好的做法
const [posts, setPosts] = useState([
  {
    id: 1,
    title: 'Post 1',
    author: { id: 1, name: 'John', avatar: '...' },
    comments: [
      { id: 1, text: 'Nice!', author: { id: 2, name: 'Jane' } }
    ]
  }
]);

// 好的做法 - 正规化数据
const [state, setState] = useState({
  posts: {
    byId: {
      1: { id: 1, title: 'Post 1', authorId: 1, commentIds: [1] }
    },
    allIds: [1]
  },
  users: {
    byId: {
      1: { id: 1, name: 'John', avatar: '...' },
      2: { id: 2, name: 'Jane' }
    },
    allIds: [1, 2]
  },
  comments: {
    byId: {
      1: { id: 1, text: 'Nice!', authorId: 2 }
    },
    allIds: [1]
  }
});

// 2. 派生状态 vs 存储状态
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState('all');

  // 派生状态 - 不要存储在state中
  const filteredTodos = useMemo(() => {
    switch (filter) {
      case 'active':
        return todos.filter(todo => !todo.completed);
      case 'completed':
        return todos.filter(todo => todo.completed);
      default:
        return todos;
    }
  }, [todos, filter]);

  // 不好的做法
  // const [filteredTodos, setFilteredTodos] = useState([]);
  // useEffect(() => {
  //   setFilteredTodos(/* 过滤逻辑 */);
  // }, [todos, filter]);
}

// 3. 状态分组
// 相关状态组合在一起
const [formData, setFormData] = useState({
  name: '',
  email: '',
  phone: ''
});

// 而不是
// const [name, setName] = useState('');
// const [email, setEmail] = useState('');
// const [phone, setPhone] = useState('');

// 4. 使用reducer管理复杂状态
function useComplexState() {
  const [state, dispatch] = useReducer(
    (state, action) => {
      switch (action.type) {
        case 'FETCH_START':
          return { ...state, loading: true, error: null };
        case 'FETCH_SUCCESS':
          return {
            ...state,
            loading: false,
            data: action.payload,
            lastFetch: Date.now()
          };
        case 'FETCH_ERROR':
          return { ...state, loading: false, error: action.error };
        default:
          return state;
      }
    },
    { data: null, loading: false, error: null, lastFetch: null }
  );

  return [state, dispatch];
}

React高级特性

17. 高阶组件(HOC)

面试官提问: "解释什么是高阶组件,以及如何实现一个HOC?"

参考答案:

高阶组件是接受组件作为参数并返回新组件的函数。

// 基础HOC示例 - 添加日志功能
function withLogging(WrappedComponent) {
  return function LoggingComponent(props) {
    useEffect(() => {
      console.log(`${WrappedComponent.name} mounted`);
      return () => console.log(`${WrappedComponent.name} unmounted`);
    }, []);

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

// 使用HOC
const Button = ({ onClick, children }) => (
  <button onClick={onClick}>{children}</button>
);

const LoggedButton = withLogging(Button);

// 更复杂的HOC - 权限控制
function withAuth(WrappedComponent, requiredRole) {
  return function AuthComponent(props) {
    const { user } = useContext(AuthContext);
    const navigate = useNavigate();

    useEffect(() => {
      if (!user || (requiredRole && user.role !== requiredRole)) {
        navigate('/login');
      }
    }, [user, navigate]);

    if (!user) {
      return <div>加载中...</div>;
    }

    if (requiredRole && user.role !== requiredRole) {
      return <div>无权访问</div>;
    }

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

// 使用权限HOC
const AdminDashboard = withAuth(Dashboard, 'admin');

// HOC组合
function compose(...hocs) {
  return (Component) =>
    hocs.reduceRight((acc, hoc) => hoc(acc), Component);
}

const EnhancedComponent = compose(
  withAuth,
  withLogging,
  withErrorBoundary
)(BaseComponent);

// 传递ref的HOC
function withForwardRef(Component) {
  const ForwardRefComponent = React.forwardRef((props, ref) => {
    return <Component {...props} forwardedRef={ref} />;
  });

  ForwardRefComponent.displayName = `withForwardRef(${Component.displayName || Component.name})`;

  return ForwardRefComponent;
}

18. Render Props模式

面试官提问: "什么是Render Props模式?请给出一个实例。"

参考答案:

Render Props是一种在组件之间共享逻辑的技术。

// 鼠标位置追踪器
class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  };

  render() {
    return (
      <div onMouseMove={this.handleMouseMove} style={{ height: '100vh' }}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

// 使用render prop
function App() {
  return (
    <MouseTracker
      render={({ x, y }) => (
        <div>
          <h1>鼠标位置: ({x}, {y})</h1>
          <Cat x={x} y={y} />
        </div>
      )}
    />
  );
}

// 使用children作为render prop
function MouseTrackerWithChildren({ children }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (event) => {
    setPosition({
      x: event.clientX,
      y: event.clientY
    });
  };

  return (
    <div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
      {children(position)}
    </div>
  );
}

// 使用
<MouseTrackerWithChildren>
  {({ x, y }) => <div>坐标: {x}, {y}</div>}
</MouseTrackerWithChildren>

// 更实用的例子 - 数据获取
function DataFetcher({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

  return render({ data, loading, error });
}

// 使用DataFetcher
function UserProfile({ userId }) {
  return (
    <DataFetcher
      url={`/api/users/${userId}`}
      render={({ data: user, loading, error }) => {
        if (loading) return <Spinner />;
        if (error) return <ErrorMessage error={error} />;

        return (
          <div>
            <h1>{user.name}</h1>
            <p>{user.email}</p>
          </div>
        );
      }}
    />
  );
}

19. Error Boundaries错误边界

面试官提问: "如何在React中优雅地处理错误?"

参考答案:

Error Boundaries是React组件,可以捕获子组件树中的JavaScript错误。

// 错误边界组件
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
      errorCount: 0
    };
  }

  static getDerivedStateFromError(error) {
    // 更新state使下一次渲染显示fallback UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 记录错误到错误报告服务
    console.error('错误边界捕获到错误:', error, errorInfo);

    // 记录到状态中
    this.setState(prevState => ({
      error,
      errorInfo,
      errorCount: prevState.errorCount + 1
    }));

    // 上报错误
    if (this.props.onError) {
      this.props.onError(error, errorInfo);
    }

    // 发送到错误追踪服务
    // logErrorToService(error, errorInfo);
  }

  resetError = () => {
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null
    });
  };

  render() {
    if (this.state.hasError) {
      // 自定义fallback UI
      if (this.props.fallback) {
        return this.props.fallback(
          this.state.error,
          this.state.errorInfo,
          this.resetError
        );
      }

      // 默认fallback UI
      return (
        <div className="error-boundary-default">
          <h2>哎呀,出错了!</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo && this.state.errorInfo.componentStack}
          </details>
          <button onClick={this.resetError}>重试</button>
        </div>
      );
    }

    return this.props.children;
  }
}

// 使用错误边界
function App() {
  return (
    <ErrorBoundary
      fallback={(error, errorInfo, reset) => (
        <div className="custom-error-ui">
          <h1>应用出现错误</h1>
          <p>{error.message}</p>
          <button onClick={reset}>重新加载</button>
        </div>
      )}
      onError={(error, errorInfo) => {
        // 发送错误报告
        sendErrorReport({ error, errorInfo, timestamp: Date.now() });
      }}
    >
      <Header />
      <MainContent />
      <Footer />
    </ErrorBoundary>
  );
}

// 使用Hook实现错误处理
function useErrorHandler() {
  const [error, setError] = useState(null);

  const resetError = () => setError(null);

  const captureError = useCallback((error) => {
    setError(error);
    // 记录错误
    console.error('捕获到错误:', error);
  }, []);

  // 用于异步错误
  useEffect(() => {
    if (error) {
      throw error;
    }
  }, [error]);

  return { error, resetError, captureError };
}

// 在组件中使用
function RiskyComponent() {
  const { captureError } = useErrorHandler();

  const fetchData = async () => {
    try {
      const data = await api.getData();
      // 处理数据
    } catch (error) {
      captureError(error);
    }
  };

  return <button onClick={fetchData}>获取数据</button>;
}

20. Portal传送门

面试官提问: "什么是React Portal?它解决了什么问题?"

参考答案:

Portal提供了一种将子节点渲染到存在于父组件DOM层次之外的DOM节点的方式。

// 基础Portal示例
import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <button className="modal-close" onClick={onClose}>×</button>
        {children}
      </div>
    </div>,
    document.getElementById('modal-root') // 目标DOM节点
  );
}

// 使用Modal
function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <button onClick={() => setShowModal(true)}>打开模态框</button>

      <Modal isOpen={showModal} onClose={() => setShowModal(false)}>
        <h2>模态框标题</h2>
        <p>这是模态框内容</p>
      </Modal>
    </div>
  );
}

// 更复杂的例子 - 工具提示
function Tooltip({ children, content, position = 'top' }) {
  const [isVisible, setIsVisible] = useState(false);
  const [coords, setCoords] = useState({ x: 0, y: 0 });
  const triggerRef = useRef();

  const show = () => {
    const rect = triggerRef.current.getBoundingClientRect();
    const coords = calculatePosition(rect, position);
    setCoords(coords);
    setIsVisible(true);
  };

  const hide = () => setIsVisible(false);

  const calculatePosition = (rect, position) => {
    const offset = 10;
    switch (position) {
      case 'top':
        return {
          x: rect.left + rect.width / 2,
          y: rect.top - offset
        };
      case 'bottom':
        return {
          x: rect.left + rect.width / 2,
          y: rect.bottom + offset
        };
      // 其他位置...
      default:
        return { x: 0, y: 0 };
    }
  };

  return (
    <>
      <span
        ref={triggerRef}
        onMouseEnter={show}
        onMouseLeave={hide}
      >
        {children}
      </span>

      {isVisible && createPortal(
        <div
          className={`tooltip tooltip-${position}`}
          style={{
            position: 'fixed',
            left: coords.x,
            top: coords.y,
            transform: 'translate(-50%, -100%)'
          }}
        >
          {content}
        </div>,
        document.body
      )}
    </>
  );
}

// 自定义Hook封装Portal逻辑
function usePortal(id) {
  const [portalElement, setPortalElement] = useState(null);

  useEffect(() => {
    let element = document.getElementById(id);
    let created = false;

    if (!element) {
      created = true;
      element = document.createElement('div');
      element.setAttribute('id', id);
      document.body.appendChild(element);
    }

    setPortalElement(element);

    return () => {
      if (created && element.parentNode) {
        element.parentNode.removeChild(element);
      }
    };
  }, [id]);

  return portalElement;
}

// 使用自定义Hook
function Notification({ message, type }) {
  const portalElement = usePortal('notification-root');

  if (!portalElement) return null;

  return createPortal(
    <div className={`notification notification-${type}`}>
      {message}
    </div>,
    portalElement
  );
}

21. Ref和forwardRef

面试官提问: "解释React中Ref的使用场景和forwardRef的作用。"

参考答案:

// 1. 基础Ref使用
function TextInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  const selectText = () => {
    inputRef.current.select();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>聚焦</button>
      <button onClick={selectText}>选中文本</button>
    </div>
  );
}

// 2. 存储可变值
function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);

    return () => {
      clearInterval(intervalRef.current);
    };
  }, []);

  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };

  return (
    <div>
      <p>计时: {count}</p>
      <button onClick={stopTimer}>停止</button>
    </div>
  );
}

// 3. forwardRef传递ref
const FancyInput = React.forwardRef((props, ref) => {
  const localRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      localRef.current.focus();
    },
    clear: () => {
      localRef.current.value = '';
    },
    getValue: () => {
      return localRef.current.value;
    }
  }));

  return (
    <div className="fancy-input-wrapper">
      <input
        ref={localRef}
        className="fancy-input"
        {...props}
      />
    </div>
  );
});

// 使用forwardRef组件
function Parent() {
  const inputRef = useRef();

  const handleSubmit = () => {
    const value = inputRef.current.getValue();
    console.log('输入值:', value);
    inputRef.current.clear();
  };

  return (
    <div>
      <FancyInput ref={inputRef} placeholder="请输入..." />
      <button onClick={handleSubmit}>提交</button>
    </div>
  );
}

// 4. 回调Ref
function MeasuredComponent() {
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  const measureRef = useCallback(node => {
    if (node !== null) {
      const { width, height } = node.getBoundingClientRect();
      setDimensions({ width, height });
    }
  }, []);

  return (
    <div>
      <div ref={measureRef} style={{ background: 'lightblue', padding: 20 }}>
        测量这个元素
      </div>
      <p>宽度: {dimensions.width}px, 高度: {dimensions.height}px</p>
    </div>
  );
}

// 5. 多个ref的处理
function MultiRefComponent() {
  const refs = useRef({});
  const setRef = (id) => (el) => {
    refs.current[id] = el;
  };

  const focusById = (id) => {
    refs.current[id]?.focus();
  };

  return (
    <div>
      {['input1', 'input2', 'input3'].map(id => (
        <div key={id}>
          <input ref={setRef(id)} placeholder={id} />
          <button onClick={() => focusById(id)}>聚焦{id}</button>
        </div>
      ))}
    </div>
  );
}

React Router

22. React Router基础

面试官提问: "介绍一下React Router的核心概念和使用方法。"

参考答案:

// 1. 基础路由设置
import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  NavLink,
  Navigate,
  Outlet
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <NavLink to="/" className={({ isActive }) => isActive ? 'active' : ''}>
          首页
        </NavLink>
        <NavLink to="/about">关于</NavLink>
        <NavLink to="/users">用户</NavLink>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users" element={<Users />}>
          <Route index element={<UserList />} />
          <Route path=":userId" element={<UserDetail />} />
          <Route path="new" element={<NewUser />} />
        </Route>
        <Route path="/old-path" element={<Navigate to="/new-path" replace />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// 2. 嵌套路由
function Users() {
  return (
    <div>
      <h1>用户管理</h1>
      <nav>
        <Link to="/users">用户列表</Link>
        <Link to="/users/new">新建用户</Link>
      </nav>
      <Outlet /> {/* 子路由渲染位置 */}
    </div>
  );
}

// 3. 动态路由和参数
import { useParams, useSearchParams, useLocation } from 'react-router-dom';

function UserDetail() {
  const { userId } = useParams();
  const [searchParams, setSearchParams] = useSearchParams();
  const location = useLocation();

  const tab = searchParams.get('tab') || 'profile';

  return (
    <div>
      <h2>用户ID: {userId}</h2>
      <p>当前路径: {location.pathname}</p>

      <div>
        <button onClick={() => setSearchParams({ tab: 'profile' })}>
          资料
        </button>
        <button onClick={() => setSearchParams({ tab: 'posts' })}>
          帖子
        </button>
      </div>

      {tab === 'profile' && <UserProfile userId={userId} />}
      {tab === 'posts' && <UserPosts userId={userId} />}
    </div>
  );
}

// 4. 编程式导航
import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();
  const location = useLocation();

  const handleLogin = async (credentials) => {
    try {
      await api.login(credentials);
      // 登录成功后跳转
      const from = location.state?.from?.pathname || '/dashboard';
      navigate(from, { replace: true });
    } catch (error) {
      console.error('登录失败:', error);
    }
  };

  return (
    <form onSubmit={handleLogin}>
      {/* 表单内容 */}
    </form>
  );
}

// 5. 路由守卫
function ProtectedRoute({ children }) {
  const { user } = useAuth();
  const location = useLocation();

  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

// 使用路由守卫
<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>

// 6. 路由配置数组
const routes = [
  {
    path: '/',
    element: <Layout />,
    children: [
      { index: true, element: <Home /> },
      {
        path: 'courses',
        element: <Courses />,
        children: [
          { index: true, element: <CourseList /> },
          { path: ':courseId', element: <Course /> }
        ]
      },
      {
        path: 'dashboard',
        element: <ProtectedRoute><Dashboard /></ProtectedRoute>,
        children: [
          { path: 'stats', element: <Stats /> },
          { path: 'settings', element: <Settings /> }
        ]
      }
    ]
  }
];

// 使用useRoutes Hook
import { useRoutes } from 'react-router-dom';

function App() {
  const element = useRoutes(routes);
  return element;
}

面试官追问: "如何实现路由级别的代码分割?"

深入回答:

// 路由懒加载
import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Profile = lazy(() => import('./pages/Profile'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="/profile" element={<Profile />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// 带错误边界的路由懒加载
function LazyBoundary({ children }) {
  return (
    <ErrorBoundary fallback={<ErrorPage />}>
      <Suspense fallback={<LoadingSpinner />}>
        {children}
      </Suspense>
    </ErrorBoundary>
  );
}

// 预加载路由组件
const preloadDashboard = () => import('./pages/Dashboard');

// 在合适的时机预加载
<Link to="/dashboard" onMouseEnter={preloadDashboard}>
  Dashboard
</Link>

React生态系统

23. 服务端渲染(SSR)

面试官提问: "解释React SSR的原理和优势。"

参考答案:

服务端渲染是在服务器上将React组件渲染成HTML字符串,然后发送给客户端。

SSR的优势:

  1. 更好的SEO
  2. 更快的首屏加载
  3. 更好的用户体验

基础SSR实现:

// server.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server';
import App from './App';

const app = express();

app.get('*', (req, res) => {
  const context = {};

  const html = renderToString(
    <StaticRouter location={req.url} context={context}>
      <App />
    </StaticRouter>
  );

  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>React SSR</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script src="/client.js"></script>
      </body>
    </html>
  `);
});

// client.js
import { hydrateRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

hydrateRoot(
  document.getElementById('root'),
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

// Next.js示例
// pages/index.js
export default function Home({ posts }) {
  return (
    <div>
      <h1>博客列表</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

// 服务端数据获取
export async function getServerSideProps() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  return {
    props: {
      posts
    }
  };
}

// 静态生成
export async function getStaticProps() {
  const posts = await fetchPosts();

  return {
    props: {
      posts
    },
    revalidate: 3600 // 每小时重新生成
  };
}

24. 测试React组件

面试官提问: "如何测试React组件?分享一些测试最佳实践。"

参考答案:

// 1. 使用React Testing Library进行单元测试
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { setupServer } from 'msw/node';

// 组件
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (input.trim()) {
      setTodos([...todos, { id: Date.now(), text: input }]);
      setInput('');
    }
  };

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="添加待办事项"
      />
      <button onClick={addTodo}>添加</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

// 测试
describe('TodoList', () => {
  test('添加新的待办事项', async () => {
    const user = userEvent.setup();
    render(<TodoList />);

    const input = screen.getByPlaceholderText('添加待办事项');
    const button = screen.getByText('添加');

    // 输入文本
    await user.type(input, '学习React测试');

    // 点击添加按钮
    await user.click(button);

    // 验证待办事项已添加
    expect(screen.getByText('学习React测试')).toBeInTheDocument();

    // 验证输入框已清空
    expect(input).toHaveValue('');
  });

  test('不添加空的待办事项', async () => {
    const user = userEvent.setup();
    render(<TodoList />);

    const button = screen.getByText('添加');
    await user.click(button);

    const listItems = screen.queryAllByRole('listitem');
    expect(listItems).toHaveLength(0);
  });
});

// 2. 测试异步组件
const server = setupServer(
  rest.get('/api/users/:id', (req, res, ctx) => {
    const { id } = req.params;
    return res(
      ctx.json({
        id,
        name: 'John Doe',
        email: 'john@example.com'
      })
    );
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]);

  if (loading) return <div>加载中...</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

test('加载并显示用户信息', async () => {
  render(<UserProfile userId="123" />);

  // 初始显示加载状态
  expect(screen.getByText('加载中...')).toBeInTheDocument();

  // 等待用户信息加载
  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
  });

  expect(screen.getByText('john@example.com')).toBeInTheDocument();
});

// 3. 测试自定义Hook
import { renderHook, act } from '@testing-library/react';

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
}

test('useCounter Hook', () => {
  const { result } = renderHook(() => useCounter(10));

  expect(result.current.count).toBe(10);

  act(() => {
    result.current.increment();
  });
  expect(result.current.count).toBe(11);

  act(() => {
    result.current.decrement();
  });
  expect(result.current.count).toBe(10);

  act(() => {
    result.current.reset();
  });
  expect(result.current.count).toBe(10);
});

// 4. 集成测试
test('完整的用户流程', async () => {
  const user = userEvent.setup();
  const { container } = render(<App />);

  // 登录
  const loginButton = screen.getByText('登录');
  await user.click(loginButton);

  const usernameInput = screen.getByLabelText('用户名');
  const passwordInput = screen.getByLabelText('密码');

  await user.type(usernameInput, 'testuser');
  await user.type(passwordInput, 'password123');

  const submitButton = screen.getByText('提交');
  await user.click(submitButton);

  // 验证登录成功
  await waitFor(() => {
    expect(screen.getByText('欢迎, testuser')).toBeInTheDocument();
  });

  // 创建快照
  expect(container).toMatchSnapshot();
});

25. React未来趋势

面试官提问: "你如何看待React的未来发展趋势?"

参考答案:

1. React Server Components

// 服务器组件示例
// Note.server.js
import db from './db';

async function Note({ id }) {
  const note = await db.notes.get(id);

  return (
    <div>
      <h1>{note.title}</h1>
      <p>{note.content}</p>
    </div>
  );
}

// 客户端组件
// LikeButton.client.js
'use client';

function LikeButton({ noteId }) {
  const [likes, setLikes] = useState(0);

  return (
    <button onClick={() => setLikes(likes + 1)}>
      点赞 ({likes})
    </button>
  );
}

2. Concurrent Features

// 并发特性
import { useTransition, useDeferredValue } from 'react';

function SearchResults({ query }) {
  const [isPending, startTransition] = useTransition();
  const [results, setResults] = useState([]);

  const handleSearch = (newQuery) => {
    // 紧急更新 - 立即显示输入
    setQuery(newQuery);

    // 非紧急更新 - 可以延迟
    startTransition(() => {
      setResults(performSearch(newQuery));
    });
  };

  const deferredQuery = useDeferredValue(query);

  return (
    <div>
      <input onChange={(e) => handleSearch(e.target.value)} />
      {isPending && <div>搜索中...</div>}
      <ResultsList results={results} />
    </div>
  );
}

3. 新的Hook和API

// useId - 生成唯一ID
function FormField() {
  const id = useId();

  return (
    <>
      <label htmlFor={id}>名称:</label>
      <input id={id} type="text" />
    </>
  );
}

// useSyncExternalStore - 订阅外部数据源
function useOnlineStatus() {
  return useSyncExternalStore(
    subscribe,
    getSnapshot,
    getServerSnapshot
  );
}

面试官追问: "你认为React未来会如何发展?"

深入回答:

  1. 更好的开发体验

    • 更智能的错误提示
    • 更好的TypeScript支持
    • 更强大的开发工具
  2. 性能持续优化

    • 自动批处理
    • 更智能的代码分割
    • 更小的包体积
  3. 全栈框架整合

    • Next.js、Remix等框架的深度整合
    • 更好的SSR/SSG支持
    • 边缘计算支持
  4. 新的编程模式

    • 服务器组件成为主流
    • 更多的并发特性
    • 更好的状态管理方案

总结

这份React面试指南涵盖了从基础到高级的各个方面,包括:

  1. 基础概念 - React核心原理、组件、JSX、Props/State
  2. Hooks - 所有重要的Hook及其使用场景
  3. 性能优化 - 各种优化技术和最佳实践
  4. 状态管理 - 从简单到复杂的状态管理方案
  5. 高级特性 - HOC、Render Props、Error Boundaries等
  6. 路由 - React Router的完整使用
  7. 生态系统 - SSR、测试、未来趋势

掌握这些知识点,不仅能帮助你通过React面试,更重要的是能让你成为一个优秀的React开发者。记住,理解原理比死记硬背更重要,能够根据实际场景选择合适的解决方案才是真正的能力体现。