React 一面:组件通信、列表 key 与表单处理

很多公司前端一面会有几道 React 相关的问题,这一篇挑三个高频主题:组件通信、列表 key 以及表单处理,配上几道典型题的参考答案。

常见的组件通信方式:

  • 父 → 子:通过 props 传递数据和回调;
  • 子 → 父:通过 props 传下来的回调触发(例如 onChange);
  • 兄弟组件:
    • 通过共同的父组件“状态提升”;
    • 或通过 Context/状态管理库共享状态。

一面里常见的问法是:

  • “React 组件之间有哪些通信方式?”
  • “什么时候应该用 Context,而不是一层层传 props?”

参考讲法:

  • 优先考虑状态提升:当多个子组件需要共享数据时,把状态提到共同父级,通过 props 分发;
  • 如果层级太深且 props drilling 明显影响可维护性,可以用 Context 做一层“全局订阅”;
  • 对于更复杂的场景(多模块共享、异步流、持久化),再考虑 Redux/Zustand 等状态管理库。

在 React 里,使用 Array.map 渲染列表时,需要给每个元素一个稳定的 key

1{items.map((item) => (
2  <Item key={item.id} data={item} />
3))}

一面常见考点:

  • 为什么不推荐用数组索引作为 key?
  • key 的作用是什么,错用 key 会带来什么问题?

参考回答要点:

  • key 用来帮助 React 判断“哪些元素是新增/删除/复用”,从而最小化 DOM 操作;
  • 如果用索引作为 key,在列表前面插入/删除元素时,后面的组件会被错误复用,可能导致:
    • 输入框内容错位;
    • 组件内部状态混乱;
    • 动画/过渡异常。

可以用一个简单例子说明“输入框绑定列表项时错用索引 key 会导致输入错位”,面试官会更有画面感。

React 中表单通常有两种处理方式:

  • 受控组件(Controlled Component)

    • 表单的值完全由 React state 控制;
    • 每次输入都会触发 onChange 更新 state,渲染时把 state 作为 value;
    • 适合需要实时校验/联动的复杂表单。
  • 非受控组件(Uncontrolled Component)

    • 通过 defaultValue/ref 直接操作 DOM 获取值;
    • React 不实时追踪值变化;
    • 适合简单场景或第三方库集成。

一面中常见问题是“说一下受控组件和非受控组件的区别”。
可以顺带提一下:复杂业务表单一般会选受控或受控 + 表单库(如 Formik/React Hook Form),因为更易做校验和状态管理。

参考答案要点:

  • 父子通信:通过 props 传数据、回调;
  • 兄弟通信:状态提升到共同父组件,或者使用 Context/状态管理库;
  • 远亲组件:使用 Context 提供/消费全局状态,避免层层 props 传递。

可以顺带提到:

“我会优先用状态提升解决问题,只有在 props drilling 明显影响可维护性时才引入 Context 或全局 store。”

参考答案要点:

  • key 是 React 识别列表中元素身份的依据,用于决定哪些节点复用、哪些需要创建/删除;
  • 使用稳定的业务 id 可以让 React 正确复用组件实例;
  • 使用索引作为 key,在列表前面插入/删除元素时,会导致:
    • 组件实例错误复用;
    • 内部状态(如输入框内容)与数据错位。

可以加一句:

“索引 key 在不会插入/删除,只会 push/pop 的纯展示列表里相对安全,但我在业务代码里基本都会优先选稳定 id。”

参考答案要点:

  • 受控组件:表单值由 React state 单一来源控制,valueonChange 绑定在一起;
    • 适合需要实时校验/联动/回显的复杂表单;
  • 非受控组件:值主要存储在 DOM 中,通过 ref/defaultValue 获取或设置;
    • 适合简单表单、性能敏感场景或与非 React 组件集成。

可以补一句实际经验:

“中大型业务表单我会基本用受控 + 表单库,小而独立的输入框偶尔会用非受控简化实现,但要注意不要混乱两种模式。”