温故而知新,带你重温React 组件通信,搞定面试

187 阅读6分钟

在 React 应用开发中,组件是构建用户界面的基本单元。随着应用复杂度的提升,组件之间如何高效、安全地传递数据和事件,成为开发者必须掌握的核心技能。本文将系统、详细地讲解 React 中所有主流的组件通信方式,涵盖父子通信、兄弟通信、跨层级通信、非直接关系组件通信,并深入剖析 useContextZustandRedux 等状态管理工具的使用场景与最佳实践。


一、组件通信的基本概念

React 遵循单向数据流(从上到下)的设计原则。这意味着数据通常由父组件通过 props 传递给子组件。但现实开发中,我们经常需要反向传递事件、兄弟组件间共享状态,甚至跨多层组件共享数据。这就催生了多种通信机制。


二、1. 父子通信:Props 与 Callback(最基础)

这是 React 最原始、最推荐的通信方式。

父传子:通过 props 传递数据

父组件将数据作为属性传递给子组件。

function Parent() {
  const [name, setName] = useState("Alice");
  
  return <Child displayName={name} />;
}

function Child({ displayName }) {
  return <div>你好,{displayName}!</div>;
}

子传父:通过回调函数(Callback)

子组件通过调用父组件传入的函数来“通知”状态变化。

function Parent() {
  const handleChildClick = (data) => {
    console.log("子组件传来的数据:", data);
  };

  return <Child onNotify={handleChildClick} />;
}

function Child({ onNotify }) {
  return (
    <button onClick={() => onNotify("Hello from Child!")}>
      点我通知父组件
    </button>
  );
}

优缺点分析

  • 优点
    • 简单直观,符合 React 设计理念。
    • 数据流向清晰,易于调试。
  • 缺点
    • 当组件层级很深时,中间组件可能只是“传话筒”,造成 props drilling(属性钻取)问题,代码冗余且难维护。

二、2. 兄弟通信:通过共同父组件中转

兄弟组件之间没有直接联系,必须通过它们的共同父组件作为“中介”来实现通信。

function Parent() {
  const [sharedData, setSharedData] = useState("");

  const updateData = (newData) => {
    setSharedData(newData);
  };

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

function SiblingA({ onDataChange }) {
  return (
    <input 
      onChange={(e) => onDataChange(e.target.value)} 
      placeholder="输入内容..."
    />
  );
}

function SiblingB({ data }) {
  return <div>接收到的数据:{data}</div>;
}

说明

  • SiblingA 修改输入框内容 → 触发 onDataChange 回调 → Parent 更新状态 → Parent 将新数据通过 props 传给 SiblingB
  • 这是 React 推荐的“提升状态”模式(Lifting State Up)。

二、3. 跨层级通信:Context API(useContext

当组件层级较深,props drilling 显得繁琐时,React 提供了 Context API 来实现跨层级数据传递。

1. 核心概念

  • Context:一个容器,用于存储可以被组件树中任何组件访问的值。
  • Provider:一个组件,用于向其子组件树提供 context 值。
  • Consumer / useContext:用于读取 context 值。

2. 使用步骤

Step 1: 创建 Context

import { createContext, useContext } from 'react';

// 创建一个 ThemeContext,提供默认值
const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {}
});

Step 2: 使用 Provider 包裹组件树

function App() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    // 通过 Provider 提供 value
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <Layout />
    </ThemeContext.Provider>
  );
}

Step 3: 在任意后代组件中使用 useContext

function Header() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <header className={theme}>
      <h1>我的网站</h1>
      <button onClick={toggleTheme}>
        切换到 {theme === 'light' ? '暗色' : '亮色'} 主题
      </button>
    </header>
  );
}

function Sidebar() {
  const { theme } = useContext(ThemeContext);
  return <aside className={theme}>侧边栏</aside>;
}

3. useContext 的优缺点

优点缺点
避免深层 props drill 传递问题频繁更新可能导致不必要的重渲染
React 内置,无需额外依赖不适合管理复杂、高频更新的状态
API 简洁调试不如独立状态管理工具清晰

4. 性能优化建议

  • 拆分 Context:不要把所有状态放在一个 Context 中。例如,将 themeuser 分开。
  • 使用 React.memo:对不依赖 context 变化的组件进行记忆化。
  • 避免传递函数引用:如果函数依赖于 context 外部状态,确保其稳定(可用 useCallback)。

二、4. 非直接关系组件通信:状态管理库

当应用变得复杂,状态逻辑分散、共享频繁、需要持久化或异步处理时,就需要引入专门的状态管理库。

1. Zustand:现代轻量级状态管理

Zustand 以其极简 API、高性能、无 Provider、支持中间件等特点,成为许多开发者的首选。

安装

npm install zustand

创建 Store

// store/useUserStore.js
import { create } from 'zustand';

const useUserStore = create((set) => ({
  user: null,
  isLoggedIn: false,

  // 同步 action
  login: (userData) => set({ user: userData, isLoggedIn: true }),
  logout: () => set({ user: null, isLoggedIn: false }),

  // 异步 action(支持 async/await)
  fetchUser: async (id) => {
    const response = await fetch(`/api/users/${id}`);
    const userData = await response.json();
    set({ user: userData, isLoggedIn: true });
  },
}));

export default useUserStore;

在组件中使用

function UserProfile() {
  const { user, isLoggedIn, login, logout } = useUserStore();

  if (!isLoggedIn) return <LoginButton />;

  return (
    <div>
      <h2>欢迎,{user.name}</h2>
      <button onClick={logout}>退出登录</button>
    </div>
  );
}

function LoginButton() {
  const { login } = useUserStore();
  return (
    <button onClick={() => login({ id: 1, name: "Alice" })}>
      登录
    </button>
  );
}

Zustand 核心优势

  • 无需 Provider:直接导入 useUserStore 即可使用。
  • 自动分片更新:只更新使用了状态的组件,性能极佳。
  • 支持中间件:如 persist(持久化)、devtools(调试)。
// 添加持久化(自动保存到 localStorage)
import { persist } from 'zustand/middleware';

const useUserStore = create(
  persist(
    (set) => ({ /* 状态定义 */ }),
    { name: 'user-storage' }
  )
);

2. Redux Toolkit(RTK):官方推荐的复杂状态管理

适用于大型、复杂、需要严格状态管理的项目。

安装

npm install @reduxjs/toolkit react-redux

创建 Slice

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

const userSlice = createSlice({
  name: 'user',
  initialState: { user: null, isLoggedIn: false },
  reducers: {
    login: (state, action) => {
      state.user = action.payload;
      state.isLoggedIn = true;
    },
    logout: (state) => {
      state.user = null;
      state.isLoggedIn = false;
    }
  }
});

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

配置 Store

// store/store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';

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

在组件中使用

import { useSelector, useDispatch } from 'react-redux';
import { login, logout } from '../store/userSlice';

function UserProfile() {
  const user = useSelector(state => state.user.user);
  const dispatch = useDispatch();

  return (
    <div>
      {user ? (
        <>
          <h2>欢迎,{user.name}</h2>
          <button onClick={() => dispatch(logout())}>退出</button>
        </>
      ) : (
        <button onClick={() => dispatch(login({ name: "Alice" }))}>
          登录
        </button>
      )}
    </div>
  );
}

Redux 优势

  • 可预测性:单一状态树,状态变化可追踪。
  • 强大的调试工具(Redux DevTools)。
  • 适合大型团队协作

缺点

  • 模板代码多:需要定义 action、reducer、dispatch。
  • 学习曲线较陡

三、其他通信方式

1. 事件总线(Event Bus)—— 不推荐

使用 mittevents 等库实现发布/订阅模式。

import mitt from 'mitt';
const emitter = mitt();

// 发布
emitter.emit('userLoggedIn', userData);

// 订阅
emitter.on('userLoggedIn', (data) => { /* 处理 */ });

问题:难以追踪、易造成内存泄漏、不利于维护。在 React 中应避免使用

2. Ref 通信(非常规)

通过 ref 获取子组件实例并调用其方法(主要用于 DOM 操作或命令式调用)。

const childRef = useRef();
childRef.current.focusInput(); // 调用子组件方法

注意:这不是主流数据通信方式,应谨慎使用。


四、选择建议:如何决策?

场景推荐方案
简单父子通信props + callback
主题、语言等全局配置useContext
中小型应用,需要共享状态Zustand
大型复杂应用,团队协作Redux Toolkit
需要持久化、日志等Zustand(+中间件)或 Redux

五、总结

React 的组件通信机制丰富多样:

  • 基础通信propscallback 是基石,应优先掌握。
  • 跨层级共享useContext 解决了 props drilling 问题,适合低频更新的全局状态。
  • 复杂状态管理Zustand 以简洁高效著称,是现代 React 项目的理想选择;Redux Toolkit 则适合需要严格状态管理和调试的大型项目。

核心原则

  1. 状态尽可能局部化,只在必要时提升。
  2. 工具越简单越好,避免过度工程化。
  3. 数据流清晰,便于调试和维护。