组件之间传参数

React 组件之间传参数:

最核心的一句话:

父传子用 props,子传父用 callback,兄弟组件通常通过共同父组件传递;跨层级可以用 Context,更复杂的全局状态可以用 Redux/Zustand 等状态管理方案。


一、先看完整分类

                    React 组件通信
       ┌─────────────────┼─────────────────┐
       ↓                 ↓                 ↓
     父 → 子           子 → 父           兄弟组件
       │                 │                 │
     props          callback函数       状态提升
       │                 │                 │
       └─────────────────┴────────┬────────┘
                              共同父组件


       跨层级组件
      ┌─────┴─────┐
      ↓           ↓
   Context    状态管理
          Redux / Zustand

另外还有一些特殊方式:

  • ref / useImperativeHandle:父组件调用子组件暴露的方法
  • URL / Router 参数:页面级组件之间传递
  • 浏览器存储:localStorage/sessionStorage
  • 事件总线:特殊场景使用,不建议作为常规方案

二、父组件 → 子组件:props

这是 React 最基本、最重要的组件通信方式。

例如:

function Parent() {
  const user = {
    id: 1,
    name: "Tom"
  };

  return <Child user={user} />;
}

子组件:

function Child({ user }) {
  return (
    <div>
      {user.name}
    </div>
  );
}

数据流:

Parent
   │ props
Child

可以传:

<Child
  name="Tom"
  age={20}
  user={user}
  onClick={handleClick}
  items={items}
/>

本质上都是:

props

三、父传子不仅可以传数据,也可以传函数

例如:

function Parent() {

  const handleDelete = (id) => {
    console.log("delete", id);
  };

  return (
    <Child
      onDelete={handleDelete}
    />
  );
}

子组件:

function Child({ onDelete }) {

  return (
    <button onClick={() => onDelete(100)}>
      Delete
    </button>
  );
}

这里实际上是:

Parent
   │ onDelete function
Child

所以:

props 不仅可以传数据,也可以传函数。

这正是实现“子传父”的基础。


四、子组件 → 父组件:Callback

React 没有类似 Vue $emit 的机制。

React 最常见的方式是:

父组件把一个函数通过 props 传给子组件,子组件调用这个函数,把数据传回父组件。

例如:

function Parent() {

  const handleMessage = (message) => {
    console.log("来自子组件:", message);
  };

  return (
    <Child
      onMessage={handleMessage}
    />
  );
}

子组件:

function Child({ onMessage }) {

  const handleClick = () => {
    onMessage("Hello Parent");
  };

  return (
    <button onClick={handleClick}>
      Send
    </button>
  );
}

数据流:

Parent
   │ function
Child
   │ callback(data)
Parent

五、这也是 React 单向数据流的核心

React 强调:

             Parent
       props ↓
          Child

而不是让 Child 直接修改 Parent 的数据。

正确的方式:

Parent
  │ state
Child
  │ callback
Parent
  │ setState
重新 render

例如:

function Parent() {

  const [count, setCount] = useState(0);

  return (
    <>
      <div>{count}</div>

      <Child
        onIncrement={() => setCount(c => c + 1)}
      />
    </>
  );
}

子组件:

function Child({ onIncrement }) {

  return (
    <button onClick={onIncrement}>
      +1
    </button>
  );
}

六、兄弟组件之间:状态提升

这是面试非常常见的问题:

两个兄弟组件怎么传参数?

例如:

        Parent
        /    \
       ↓      ↓
   ChildA   ChildB

ChildA 想把数据传给 ChildB。

不要直接:

ChildA → ChildB

而是:

ChildA
Parent
ChildB

也就是:

把共享状态提升到最近的共同父组件。


示例

function Parent() {

  const [message, setMessage] = useState("");

  return (
    <>
      <ChildA
        onMessage={setMessage}
      />

      <ChildB
        message={message}
      />
    </>
  );
}

ChildA:

function ChildA({ onMessage }) {

  return (
    <button onClick={() => onMessage("Hello")}>
      Send
    </button>
  );
}

ChildB:

function ChildB({ message }) {

  return (
    <div>
      {message}
    </div>
  );
}

数据流:

              Parent
             /      \
            ↓        ↓
        ChildA      ChildB
            │          ↑
            │          │
            └──────────┘
               state

这叫:

Lifting State Up(状态提升)


七、跨层级组件:Context

假设组件结构:

App
 ├── A
 │    └── B
 │         └── C
 │              └── D

现在 App 有一个:

user

需要传给 D。

传统 props:

App
A
B
C
D

即使 A/B/C 根本不需要 user,也得一层层传。

这就是:

Prop Drilling(Props 逐层传递)


八、使用 Context

可以:

const UserContext = createContext(null);

Provider:

function App() {

  const user = {
    id: 1,
    name: "Tom"
  };

  return (
    <UserContext.Provider value={user}>
      <A />
    </UserContext.Provider>
  );
}

D:

function D() {

  const user = useContext(UserContext);

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

数据:

App
 │ Context Provider
 A
 B
 C
 D
 │ useContext
 user

中间组件:

A
B
C

完全不需要传 user


九、Context 适合什么场景?

典型场景:

1. 用户信息

CurrentUser

2. Theme

light / dark

3. 国际化

zh-CN / en-US

4. 权限

roles / permissions

5. 全局配置

API config

十、Context 不等于 Redux

这是面试经常问的:

Context 和 Redux 有什么区别?

简单理解:

Context
解决“数据怎么传过去”

而 Redux:

Redux
解决“复杂共享状态怎么管理”

例如:

大型应用
    ├── User
    ├── Cart
    ├── Order
    ├── Permission
    ├── Notification
    └── Product

如果状态之间存在大量:

读取
修改
异步操作
派生状态
状态追踪

通常会考虑状态管理库。

现代 React 项目除了 Redux,也经常使用 Zustand 等方案。


十一、ref:父组件调用子组件方法

这是比较特殊的一种通信方式。

正常情况下:

Parent
   ↓ props
Child

但是有时候父组件需要:

“直接调用子组件的方法。”

例如:

Parent
调用 Child.focus()

可以使用:

useRef

配合:

useImperativeHandle

示例

子组件:

const Input = forwardRef(function Input(props, ref) {

  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus() {
      inputRef.current.focus();
    }
  }));

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

父组件:

function Parent() {

  const inputRef = useRef();

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

  return (
    <>
      <Input ref={inputRef} />

      <button onClick={handleClick}>
        Focus
      </button>
    </>
  );
}

数据/控制关系:

Parent
   │ ref
Child
   │ expose
focus()

十二、什么时候使用 ref?

比较典型的是:

focus()
scrollTo()
open()
close()
reset()
play()
pause()

例如:

modalRef.current.open();

但是不要把它当成普通状态传递机制。

React 更推荐:

props
state
callback
context

而不是大量使用:

ref

因为 ref 是一种命令式操作


十三、Router 参数

如果是页面之间传递参数,还可以使用 React Router。

例如:

/users/100

其中:

100

就是:

userId

页面可以读取:

const { userId } = useParams();

也可以通过 query:

/users?id=100

读取:

const [searchParams] = useSearchParams();

const id = searchParams.get("id");

这类参数实际上是:

URL 层面的组件通信。

特别适合:

详情页
搜索条件
分页
筛选条件
页面状态

十四、localStorage / sessionStorage

还有一种方式:

Component A
localStorage
Component B

例如:

localStorage.setItem(
  "token",
  token
);

另一个组件:

const token = localStorage.getItem("token");

但是需要注意:

localStorage 更适合持久化数据,不应该作为普通 React 组件通信的首选方案。

因为它本身不会自动触发 React render。


十五、事件总线

还可以:

Component A
 Event Bus
Component B

例如以前常见:

EventEmitter
Pub/Sub
window.dispatchEvent

这种方式可以实现:

A → B

甚至:

A → X
A → Y
A → Z

但是在现代 React 项目里:

不推荐把 Event Bus 当成主要组件通信机制。

因为容易造成:

数据来源不清楚
生命周期难管理
事件订阅泄漏
代码难维护
调试困难

十六、最终可以整理成这张表

场景推荐方式典型 API
父 → 子Propsprops
子 → 父CallbackonXXX
兄弟 → 兄弟状态提升useState
跨多层组件ContextcreateContext/useContext
大型应用共享状态State ManagementRedux/Zustand
父调用子方法RefuseRef/useImperativeHandle
页面 → 页面URL 参数React Router
持久化共享数据StoragelocalStorage
解耦事件通信Event BusEventEmitter

十七、总结

组件通信
    ├── 父子?
    │    │
    │    ├── 父 → 子 → props
    │    │
    │    └── 子 → 父 → callback
    ├── 兄弟?
    │    │
    │    └── 状态提升到共同父组件
    ├── 跨很多层?
    │    │
    │    └── Context
    ├── 多个模块共享复杂状态?
    │    │
    │    └── Redux / Zustand
    ├── 父组件需要命令式调用子组件?
    │    │
    │    └── ref / useImperativeHandle
    └── 页面之间?
         └── Router 参数

Vincent zhai
Vincent zhai
Full-Stack Engineer