正常情况下,可以直接使用 props
来传递数据到子组件。props
是 React 中最直接的数据传递方式,适用于大多数场景。
当后代组件层级很深,且只有一个后代组件使用到该 props
,你应该使用组件组装(component composition)。这种方式可以减少中间组件的冗余代码,同时保持组件的可维护性。
当后代组件层级很深,且有多个后代(同级或不同级)组件需要访问相同的数据时,你应该使用 Context
。Context
提供了一种在组件树中传递数据的方式,而无需手动逐层传递 props
。
组件组装(Component Composition)
组件组装是一种通过组合组件来传递数据的方式,而不是通过层层传递 props
。这种方式可以减少中间组件的冗余代码,同时保持组件的可维护性和可复用性。
场景说明
假设有一个 Page
组件,它需要将 user
和 avatarSize
传递给一个嵌套很深的 Avatar
组件。如果直接传递 props
,代码会变得冗余且难以维护:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
组件组装解决方案
通过将 Avatar
组件直接传递给中间组件,避免了层层传递 props
:
function Page({ user, avatarSize }) {
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
// 中间组件无需关心 `user` 和 `avatarSize`
function PageLayout({ userLink }) {
return (
<div>
<NavigationBar />
{userLink}
</div>
);
}
function NavigationBar() {
return <div>Navigation</div>;
}
function Link({ href, children }) {
return <a href={href}>{children}</a>;
}
function Avatar({ user, size }) {
return <img src={user.avatarUrl} alt={user.name} style={{ width: size, height: size }} />;
}
这种方式减少了 props
的传递层级,使代码更加简洁,同时保持了组件的解耦。
如何使用 Context?
创建 Context
const SomeContext = React.createContext('default value');
React.createContext
创建一个 Context 对象。defaultValue
参数仅在组件树中没有匹配的 Provider
时生效,可用于测试或默认行为。
父组件挂载
当需要在使用时改变 context 值时,就使用 Provider
挂载:
<SomeContext.Provider value={value}>
<MyComponent />
</SomeContext.Provider>
提示:当不需要改变 context 的值时,你可以不使用
Provider
,直接使用默认值。https://github.com/facebook/react/issues/17912
如果定制化代码较多,建议将 Provider
抽到单独文件:
export const OrderProvider = ({ children }) => {
const [total, setTotal] = useState(0);
const onAdd = (num) => {
setTotal(total + num);
};
return <OrderContext.Provider value={{ total, onAdd }}>{children}</OrderContext.Provider>;
};
使用此 Provider
文件来挂载:
import { OrderProvider } from './provider';
export default function App() {
return <OrderProvider>your app here</OrderProvider>;
}
后代组件使用
Function Component 方式
function MyComponent() {
const value = React.useContext(SomeContext);
return <span>{value}</span>;
}
Class Component 方式
使用 contextType
来读取,然后通过 this.context
来使用:
class MyComponent extends React.Component {
static contextType = SomeContext;
render() {
return <span>{this.context}</span>;
}
}
注意事项
- 性能问题:Context 的变化会导致所有订阅的组件重新渲染。如果需要优化性能,可以结合
React.memo
或useMemo
来避免不必要的渲染。 - 默认值的使用:默认值仅在没有
Provider
时生效,因此在生产环境中,建议始终使用Provider
包裹组件。 - 组件复用性:使用 Context 时,组件的复用性可能会降低,因为它们依赖于外部的上下文。因此,仅在必要时使用 Context。
结论
在选择数据传递方式时,优先顺序如下:
- 使用
props
,适用于简单的父子组件数据传递。 - 使用组件组装(component composition),适用于深层嵌套但只有一个组件需要数据的场景。
- 使用
Context
,适用于多个后代组件需要共享数据的场景。