防止不必要的子元素重新渲染
Prevent Unnecessary Re-rendering of Child Elements
我正在 React 中创建一个全局通知组件,它使用 Context
为其子项提供 createNotification
句柄。通知与 props.children
一起呈现。如果 props.children
没有改变,有没有办法阻止重新渲染?
我试过用React.memo
和useMemo(props.children, [props.children])
都不行。
const App = () => {
return (
<Notifications>
<OtherComponent/>
</Notifications/>
);
}
const Notifications = (props) => {
const [notifications, setNotifications] = useState([]);
const createNotification = (newNotification) => {
setNotifications([...notifications, ...newNotification]);
}
const NotificationElems = notifications.map((notification) => <Notification {...notification}/>);
return (
<NotificationContext.Provider value={createNotification}>
<React.Fragment>
{NotificationElems}
{props.children}
</React.Fragment>
</NotificationContext.Provider>
);
};
const OtherComponent = () => {
console.log('Re-rendered');
return <button onClick={() => useContext(NotificationContext)(notification)}>foo</button>
}
每次创建新的 notification
时,都会重新呈现 props.children
,即使其中没有任何实际更改。它只是在旁边添加元素。如果您有一个大型应用程序并且所有内容都为每个显示的 notification
重新呈现,这可能会非常昂贵。如果没有办法阻止这种情况,我该如何拆分它才能做到这一点:
<div>
<OtherComponent/>
<Notifications/>
</div>
并与 OtherComponent
分享 createNotification
句柄?
您需要使用 useCallback
挂钩来创建 createNotification
命令式处理程序。否则,您将在 Notifications
组件的每个渲染器上创建一个新函数,这将导致所有使用您的上下文的组件重新渲染,因为每当您添加通知时,您总是会传递一个新的处理程序。
此外,您可能并不打算将 newNotification
散布到通知数组中。
接下来需要做的是在 setNotifications
中提供 setState 的 updater callback version。它会传递当前的通知列表,您可以使用附加新的通知。这使您的回调独立于通知状态的当前值。在不使用updater函数的情况下根据当前状态更新状态通常是错误的,因为react是批量更新的。
const Notifications = props => {
const [notifications, setNotifications] = useState([]);
// use the useCallback hook to create a memorized handler
const createNotification = useCallback(
newNotification =>
setNotifications(
// use the callback version of setState
notifications => [...notifications, newNotification],
),
[],
);
const NotificationElems = notifications.map((notification, index) => <Notification key={index} {...notification} />);
return (
<NotificationContext.Provider value={createNotification}>
<React.Fragment>
{NotificationElems}
{props.children}
</React.Fragment>
</NotificationContext.Provider>
);
};
另一个问题是您有条件地调用 useContext
挂钩,这是不允许的。 Hooks must be called unconditionally:
const OtherComponent = () => {
// unconditiopnally subscribe to context
const createNotification = useContext(NotificationContext);
console.log('Re-rendered');
return <button onClick={() => createNotification({text: 'foo'})}>foo</button>;
};
完整的工作示例:
我正在 React 中创建一个全局通知组件,它使用 Context
为其子项提供 createNotification
句柄。通知与 props.children
一起呈现。如果 props.children
没有改变,有没有办法阻止重新渲染?
我试过用React.memo
和useMemo(props.children, [props.children])
都不行。
const App = () => {
return (
<Notifications>
<OtherComponent/>
</Notifications/>
);
}
const Notifications = (props) => {
const [notifications, setNotifications] = useState([]);
const createNotification = (newNotification) => {
setNotifications([...notifications, ...newNotification]);
}
const NotificationElems = notifications.map((notification) => <Notification {...notification}/>);
return (
<NotificationContext.Provider value={createNotification}>
<React.Fragment>
{NotificationElems}
{props.children}
</React.Fragment>
</NotificationContext.Provider>
);
};
const OtherComponent = () => {
console.log('Re-rendered');
return <button onClick={() => useContext(NotificationContext)(notification)}>foo</button>
}
每次创建新的 notification
时,都会重新呈现 props.children
,即使其中没有任何实际更改。它只是在旁边添加元素。如果您有一个大型应用程序并且所有内容都为每个显示的 notification
重新呈现,这可能会非常昂贵。如果没有办法阻止这种情况,我该如何拆分它才能做到这一点:
<div>
<OtherComponent/>
<Notifications/>
</div>
并与 OtherComponent
分享 createNotification
句柄?
您需要使用 useCallback
挂钩来创建 createNotification
命令式处理程序。否则,您将在 Notifications
组件的每个渲染器上创建一个新函数,这将导致所有使用您的上下文的组件重新渲染,因为每当您添加通知时,您总是会传递一个新的处理程序。
此外,您可能并不打算将 newNotification
散布到通知数组中。
接下来需要做的是在 setNotifications
中提供 setState 的 updater callback version。它会传递当前的通知列表,您可以使用附加新的通知。这使您的回调独立于通知状态的当前值。在不使用updater函数的情况下根据当前状态更新状态通常是错误的,因为react是批量更新的。
const Notifications = props => {
const [notifications, setNotifications] = useState([]);
// use the useCallback hook to create a memorized handler
const createNotification = useCallback(
newNotification =>
setNotifications(
// use the callback version of setState
notifications => [...notifications, newNotification],
),
[],
);
const NotificationElems = notifications.map((notification, index) => <Notification key={index} {...notification} />);
return (
<NotificationContext.Provider value={createNotification}>
<React.Fragment>
{NotificationElems}
{props.children}
</React.Fragment>
</NotificationContext.Provider>
);
};
另一个问题是您有条件地调用 useContext
挂钩,这是不允许的。 Hooks must be called unconditionally:
const OtherComponent = () => {
// unconditiopnally subscribe to context
const createNotification = useContext(NotificationContext);
console.log('Re-rendered');
return <button onClick={() => createNotification({text: 'foo'})}>foo</button>;
};
完整的工作示例: