React 组件通信方式

Props、Context、Redux、Ref 四种组件通信方式的使用场景和实现

问题

React 中组件之间如何通信?常见的通信方式有哪些?

解答

1. Props - 父子组件通信

最基础的通信方式,父组件通过 props 向子组件传递数据和回调函数。

// 父组件
function Parent() {
  const [count, setCount] = useState(0);

  // 通过 props 传递数据和回调
  return (
    <Child 
      count={count} 
      onIncrement={() => setCount(count + 1)} 
    />
  );
}

// 子组件
function Child({ count, onIncrement }) {
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={onIncrement}>+1</button>
    </div>
  );
}

2. Context - 跨层级通信

避免 props 逐层传递,适合主题、用户信息等全局数据。

// 创建 Context
const ThemeContext = createContext('light');

// 顶层提供数据
function App() {
  const [theme, setTheme] = useState('light');

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

// 中间层不需要传递 props
function Page() {
  return <Button />;
}

// 任意深层组件直接消费
function Button() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button 
      style={{ background: theme === 'dark' ? '#333' : '#fff' }}
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
    >
      切换主题
    </button>
  );
}

3. Redux - 全局状态管理

适合复杂应用的状态管理,单向数据流,可预测。

// store.js - 创建 store
import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; },
    decrement: (state) => { state.value -= 1; },
  },
});

export const { increment, decrement } = counterSlice.actions;
export const store = configureStore({ reducer: { counter: counterSlice.reducer } });

// App.jsx - 提供 store
import { Provider } from 'react-redux';
import { store } from './store';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

// Counter.jsx - 使用 store
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store';

function Counter() {
  // 读取状态
  const count = useSelector((state) => state.counter.value);
  // 派发 action
  const dispatch = useDispatch();

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

4. Ref - 直接访问组件实例或 DOM

父组件直接调用子组件的方法,需配合 forwardRefuseImperativeHandle

import { useRef, forwardRef, useImperativeHandle } from 'react';

// 子组件暴露方法给父组件
const Input = forwardRef((props, ref) => {
  const inputRef = useRef();

  // 定义暴露给父组件的方法
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
    clear: () => { inputRef.current.value = ''; },
  }));

  return <input ref={inputRef} {...props} />;
});

// 父组件调用子组件方法
function Form() {
  const inputRef = useRef();

  return (
    <div>
      <Input ref={inputRef} placeholder="输入内容" />
      <button onClick={() => inputRef.current.focus()}>聚焦</button>
      <button onClick={() => inputRef.current.clear()}>清空</button>
    </div>
  );
}

通信方式对比

方式适用场景复杂度
Props父子组件、层级浅
Context跨多层、全局配置
Redux大型应用、复杂状态
Ref命令式操作、访问 DOM

关键点

  • Props 是单向数据流的基础,子传父通过回调函数
  • Context 解决 props drilling,但频繁更新会导致性能问题
  • Redux 遵循单一数据源、状态只读、纯函数修改三原则
  • Ref 打破了声明式编程,应谨慎使用
  • 优先使用 Props,按需引入其他方案,避免过度设计