React组件通信的6种艺术:从单向传值到全局共享

76 阅读5分钟

组件间的通信,就像人与人的对话——单向传递是尊重,状态提升是信任,而全局共享则是无需言语的默契。

本文是《从0死磕全栈》系列第6篇,系统梳理 React + TypeScript 开发中六大核心组件通信方式,涵盖父子传值、兄弟通信、跨层级共享、全局状态管理及 Refs 直接操控,助你根据场景精准选择,告别“props 一层层传到秃头”的困境。


一、第一种:父组件向子组件传值(Props)

✅ 适用场景

父组件需要向子组件传递静态或动态数据(如标题、配置、初始值)。

✅ 实现方式

通过 props 单向传递,并使用 TypeScript 接口定义类型,确保安全性和可维护性。

// 父组件
function Parent() {
  const message: string = "Hello from Parent!";
  return <Child message={message} />;
}

// 子组件 Props 类型定义
interface ChildProps {
  message: string;
}

// 子组件
function Child({ message }: ChildProps) {
  return <h1>{message}</h1>; // 输出: Hello from Parent!
}

🔍 关键要点

  • 单向数据流:数据只能从父流向子,符合 React 设计哲学。
  • TypeScript 友好:使用 interface 定义 props 类型,编译期校验,避免运行时错误。
  • 可扩展性强:新增属性只需在接口中添加,不影响逻辑。

二、第二种:子组件向父组件传值(回调函数)

✅ 适用场景

子组件触发事件(如按钮点击、表单提交)需通知父组件并传递数据。

✅ 实现方式

父组件传递一个回调函数作为 prop,子组件调用该函数并传参。

// 父组件
function Parent() {
  const handleChildData = (data: string): void => {
    console.log("Data from Child:", data); // 输出: Data from Child: Hello from Child!
  };

  return <Child onSendData={handleChildData} />;
}

// 子组件 Props 类型
interface ChildProps {
  onSendData: (data: string) => void;
}

// 子组件
function Child({ onSendData }: ChildProps) {
  const sendData = (): void => {
    onSendData("Hello from Child!");
  };

  return <button onClick={sendData}>Send Data</button>;
}

💡 比喻理解

父组件提供了一个“收件箱”(回调函数),子组件把信息“投递”进去,父组件收到后自动处理。


三、第三种:兄弟组件通信(状态提升)

✅ 适用场景

两个同级组件(兄弟)需要共享和同步数据(如 A 显示内容,B 修改内容)。

✅ 实现方式

将共享状态提升到共同父组件,再通过 props 分别传递给子组件。

// 父组件
function Parent() {
  const [sharedData, setSharedData] = useState<string>("Initial Data");

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

// 兄弟组件 A(显示)
interface SiblingAProps {
  data: string;
}
function SiblingA({ data }: SiblingAProps) {
  return <p>Data from SiblingB: {data}</p>;
}

// 兄弟组件 B(更新)
interface SiblingBProps {
  onUpdateData: (data: string) => void;
}
function SiblingB({ onUpdateData }: SiblingBProps) {
  return (
    <button onClick={() => onUpdateData("Updated Data")}>
      Update Data
    </button>
  );
}

⚠️ 注意事项

  • 仅适用于少量共享状态,若兄弟组件过多或状态复杂,应改用 Context 或 Zustand。
  • 避免“状态过度提升”,导致父组件臃肿。

四、第四种:跨层级组件通信(Context API)

✅ 适用场景

深层嵌套组件(如主题、用户登录态、语言设置)需要共享数据,避免逐层 props 传递。

✅ 实现方式

使用 React.createContext 创建上下文,通过 Provider 提供,useContext 消费。

// 1. 定义 Context 类型
interface ThemeContextType {
  theme: string;
  setTheme: (theme: string) => void;
}

// 2. 创建 Context(带默认值)
const ThemeContext = React.createContext<ThemeContextType>({
  theme: "light",
  setTheme: () => {},
});

// 3. 父组件(Provider)
function App() {
  const [theme, setTheme] = useState<string>("light");

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <ChildComponent />
    </ThemeContext.Provider>
  );
}

// 4. 子组件(Consumer)
function ChildComponent() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <div>
      <p>Current Theme: {theme}</p>
      <button onClick={() => setTheme("dark")}>Toggle Theme</button>
    </div>
  );
}

✅ 优势

  • 解决“props drilling”(属性钻取)问题
  • 支持任意深度组件访问
  • 与 React Suspense、Server Components 兼容性好

🚫 缺点

  • 所有订阅组件都会因值变化重新渲染(可用 useMemo / useCallback 优化)
  • 不适合高频变更的动态数据(如鼠标位置)

五、第五种:全局状态管理(Redux / Zustand)

✅ 适用场景

大型应用中多个无关组件共享复杂、持久化、异步的状态(如用户信息、购物车、主题、权限)。

✅ 推荐方案:Zustand(轻量首选)

参考前作《Zustand 实战:极简状态管理实现 TodoList

// store/userStore.ts
import { create } from 'zustand';

interface UserState {
  user: { id: number; name: string } | null;
  login: (token: string) => void;
  logout: () => void;
}

const useUserStore = create<UserState>((set) => ({
  user: null,
  login: (token) => set({ user: { id: 1, name: '张三' } }),
  logout: () => set({ user: null }),
}));

export default useUserStore;

在任意组件中直接使用:

import useUserStore from '../store/userStore';

function Header() {
  const { user, logout } = useUserStore();

  return (
    <div>
      {user ? <span>欢迎, {user.name}</span> : <button onClick={logout}>登录</button>}
    </div>
  );
}

✅ 对比 Redux Toolkit

特性ZustandRedux Toolkit
包大小~1KB~50KB+
配置无 Provider,开箱即用需 Store + Provider + Slice
代码量极简较多样板
学习曲线极低中高
推荐场景小中型项目大型复杂企业应用

💡 结论:90% 的中小型项目,选 Zustand 足矣!


六、第六种:Refs 直接访问子组件

✅ 适用场景

父组件需要直接调用子组件的方法或访问 DOM 元素(如聚焦输入框、播放视频、触发动画)。

✅ 实现方式

使用 useRef + forwardRef + useImperativeHandle

// 1. 定义 Ref 类型
interface ChildRef {
  increment: () => void;
}

// 2. 子组件(暴露方法)
const Child = forwardRef<ChildRef>((props, ref) => {
  const [count, setCount] = useState<number>(0);

  useImperativeHandle(ref, () => ({
    increment() {
      setCount(count + 1);
    },
  }));

  return <p>Child Count: {count}</p>;
});

// 3. 父组件
function Parent() {
  const childRef = useRef<ChildRef>(null);

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={() => childRef.current?.increment()}>
        Increment from Parent
      </button>
    </div>
  );
}

⚠️ 使用原则

  • 慎用! 这是“命令式”编程,违背 React 声明式理念。
  • 仅用于无法通过 props/状态解决的底层操作(如 focus、scroll、动画控制)。
  • 切勿用于数据传递!

七、总结:如何选择?

场景推荐方案
简单数据传递(父→子)Props
子组件通知父组件回调函数(Props)
兄弟组件共享数据状态提升(父组件管理)
深层嵌套共享(主题、用户)Context API
多组件共享复杂状态(登录、购物车)Zustand(推荐) / Redux Toolkit
需要直接操作子组件 DOM/方法Refs(谨慎使用)

📌 黄金法则

通信的终极目标不是传递数据,而是构建理解——让每个组件在正确的时间,以正确的方式,做正确的事。


🎯 结语:别让通信成为你的绊脚石

React 的强大不在于它能“做什么”,而在于它教会你“何时不该做”。

  • 不要用 Context 做计数器;
  • 不要用 Refs 传递用户名;
  • 不要用 Zustand 管理输入框的值。

合适的工具,胜过强大的框架。

掌握这六种通信方式,你就能像一位指挥家一样,让每一个组件各司其职,和谐共鸣。


✅ 下一篇预告

《从0死磕全栈第7天:React useEffect 副作用起了大作用之实现购物车功能》
—— 深入理解副作用、依赖数组、清理机制,打造带 localStorage 持久化的购物车!


📌 推荐阅读