一、组件通信方式
1. 父 → 子:Props
jsx
function Parent() {
return <Child name="小明" age={18} />;
}
function Child({ name, age }) {
return <p>{name},{age}岁</p>;
}
2. 子 → 父:回调函数
jsx
function Parent() {
const handleMsg = (msg) => console.log('收到:', msg);
return <Child onSend={handleMsg} />;
}
function Child({ onSend }) {
return <button onClick={() => onSend('你好')}>发送</button>;
}
3. 兄弟组件:状态提升
jsx
// 把共享状态提升到共同的父组件
function Parent() {
const [value, setValue] = useState('');
return (
<>
<InputBox onChange={setValue} />
<Display text={value} />
</>
);
}
4. 跨层级:Context
jsx
const ThemeCtx = createContext('light');
function App() {
return (
<ThemeCtx.Provider value="dark">
<Page /> {/* 中间隔了很多层 */}
</ThemeCtx.Provider>
);
}
function DeepChild() {
const theme = useContext(ThemeCtx); // 直接拿到,不用层层传递
return <div className={theme}>内容</div>;
}
5. 全局状态:Redux / Zustand
适合大型应用,多个不相关组件需要共享状态时使用(详见 06.md)。
通信方式速查
| 场景 | 推荐方式 |
|---|---|
| 父 → 子 | Props |
| 子 → 父 | 回调函数 |
| 兄弟 | 状态提升 |
| 跨多层 | Context |
| 全局复杂状态 | Redux / Zustand |
二、高阶组件(HOC)
一句话:接收一个组件,返回一个增强后的新组件。本质是函数,不是组件。
jsx
// HOC:给任意组件添加"加载中"功能
function withLoading(WrappedComponent) {
return function EnhancedComponent({ isLoading, ...props }) {
if (isLoading) return <div>加载中...</div>;
return <WrappedComponent {...props} />;
};
}
// 使用
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading isLoading={true} users={[]} />
常见 HOC:
React.memo()--- 性能优化connect()--- Redux 连接(旧写法)withRouter()--- React Router(旧写法)
HOC 的问题:
- 嵌套多了形成"包装地狱"
- props 来源不清晰
- 现在基本被 Hooks 替代了
三、Render Props
一句话:通过 props 传一个渲染函数,让组件把数据"交出来",由外部决定怎么渲染。
jsx
// 鼠标位置追踪组件
function MouseTracker({ render }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
const handleMove = (e) => setPos({ x: e.clientX, y: e.clientY });
return <div onMouseMove={handleMove}>{render(pos)}</div>;
}
// 使用:同一个逻辑,不同的渲染方式
<MouseTracker render={({ x, y }) => <p>鼠标在 ({x}, {y})</p>} />
<MouseTracker render={({ x, y }) => <div style={{ left: x, top: y }} className="cursor" />} />
现在也基本被自定义 Hook 替代了(useMousePosition)。
四、组合 vs 继承
React 推荐组合,不推荐继承。
jsx
// ✅ 组合:通过 children 或 props 组合
function Card({ title, children }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">{children}</div>
</div>
);
}
<Card title="用户信息">
<p>姓名:小明</p>
<p>年龄:18</p>
</Card>
// ✅ 插槽模式:多个内容区域
function Layout({ header, sidebar, content }) {
return (
<div className="layout">
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{content}</main>
</div>
);
}
<Layout
header={<NavBar />}
sidebar={<Menu />}
content={<Page />}
/>
五、错误边界(Error Boundary)
一句话:捕获子组件树的 JS 错误,显示降级 UI,防止整个页面白屏。
jsx
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true }; // 触发降级 UI
}
componentDidCatch(error, info) {
console.error('组件错误:', error, info); // 上报错误
}
render() {
if (this.state.hasError) {
return <h2>出错了,请刷新页面</h2>;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<App /> {/* App 内部报错不会白屏,而是显示降级 UI */}
</ErrorBoundary>
注意:错误边界捕获不了这些错误:
- 事件处理函数中的错误(用 try/catch)
- 异步代码(setTimeout、Promise)
- 服务端渲染
- 错误边界自身的错误
目前只能用类组件写错误边界,函数组件暂不支持。可以用
react-error-boundary库简化。
六、Portals
一句话:把组件渲染到 DOM 树的其他位置,而不是父组件内部。
jsx
import { createPortal } from 'react-dom';
function Modal({ children, onClose }) {
// 虽然 Modal 在组件树中是 App 的子组件
// 但实际 DOM 渲染到了 document.body 下
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}
// 使用
function App() {
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => setShow(true)}>打开弹窗</button>
{show && <Modal onClose={() => setShow(false)}>弹窗内容</Modal>}
</div>
);
}
典型场景:弹窗(Modal)、提示框(Tooltip)、全局通知(Toast)。
Portal 虽然 DOM 位置变了,但 React 事件冒泡仍然按组件树走,不按 DOM 树走。
七、高频面试题
Q1:React 组件通信有哪些方式?
父→子用 Props,子→父用回调函数,兄弟用状态提升,跨层级用 Context,全局状态用 Redux/Zustand。
Q2:HOC 和自定义 Hook 怎么选?
优先用自定义 Hook。HOC 是类组件时代的方案,存在嵌套地狱和 props 来源不清的问题。Hook 更直观、更灵活、更容易组合。
Q3:为什么 React 推荐组合不推荐继承?
- 组合更灵活:通过 props 和 children 可以任意组合
- 继承耦合太强:子类依赖父类实现,改父类可能影响所有子类
- React 官方明确说:在 Facebook 数千个组件中,没有找到需要继承的场景
Q4:错误边界能捕获所有错误吗?
不能。只能捕获渲染过程、生命周期、构造函数中的错误。事件处理、异步代码、服务端渲染中的错误捕获不了,需要用 try/catch 或 window.onerror。
Q5:Portal 的事件冒泡怎么走?
按组件树冒泡,不按 DOM 树。即使 Modal 渲染到了 body 下,点击 Modal 内部的事件仍然会冒泡到 React 组件树中的父组件。