サーバーからWebSocketで通知を受信するプロバイダーがあります。
export const MessageProvider: React.FC<ProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, messageInitialState);
useEffect(() => {
let cancelled: boolean = false;
__get(cancelled, undefined);
return () => {
cancelled = true;
}
}, []);
useEffect(__wsEffect, []);
const get = useCallback<(cancelled: boolean, userId: number | undefined) => Promise<void>>(__get, []);
const put = useCallback<(message: Message) => Promise<void>>(__put, []);
const value = { ...state, get, put };
return (
<MessageContext.Provider value={value}>
{children}
</MessageContext.Provider>
)
...
function __wsEffect() {
log('{__wsEffect}', 'Connecting');
let cancelled: boolean = false;
const ws = newWebSocket(async (payload) => {
if (cancelled) {
return;
}
const message: Message = payload as Message;
await storageSet(message, __key(message.id));
dispatch({ actionState: ActionState.SUCCEEDED, actionType: ActionType.SAVE, data: message });
});
return () => {
log('{__wsEffect} Disconnecting');
cancelled = true;
ws.close();
}
}
}
次に、コンポーネントで、次のようにこのコンテキストを使用します。
export const MessageListPage: React.FC<RouteComponentProps> = ({ history }) => {
const { data, executing, actionType, actionError, get, put } = useContext(MessageContext);
...
return (
<IonPage id='MessageListPage'>
<IonContent fullscreen>
<IonLoading isOpen={executing && actionType === ActionType.GET} message='Fetching...' />
{
!executing && !actionError && data && (
<div>
<IonList>
{
groupBySender()
.map(__data =>
<div>
<MessageListItem key={__data.sender} sender={__data.sender} messages={__data.messages} />
</div>
)
}
</IonList>
</div>
)
}
</IonContent>
</IonPage>
);
}
そして何が起こるかというとdata
、コンテキストから更新されるたびに、このページが再レンダリングされ、すべてMessageListItem
が再レンダリングされるようになります。
重要なのは、変更が影響を与える可能性のあるMessageListItemのみを再レンダリングしたいということです。これは可能ですか?useMemoとmemoを使うことを考えましたが、これらのオブジェクトを動的に作成しているので、その方法がわかりません。
これも可能ですか?
私は次のようなことを達成するよう努めています。
user a - x unread messages
user b - y unread messages
...
そして、ユーザーをクリックすると、アイテムが展開され、それらのメッセージが表示されます。しかし、通知を受け取ると、すべてがレンダリングされ、アイテムは展開されていない状態に戻ります。
まったく同じ問題が発生しましたが、プロバイダーの子の再レンダリングを制御できないようです。React.memo
コンテキスト値は小道具ではないため、上記のソリューションは機能しません。
この問題に対処する人気が高まっているnpmパッケージがあります:https://www.npmjs.com/package/react-tracked
この記事を読んで、より多くのオプションを入手できます:https://blog.axlight.com/posts/4-options-to-prevent-extra-rerenders-with-react-context/
私は個人的に、イベントリスナーとローカルストレージを備えたフックを使用して、を使用せずにグローバル状態にするための適切なコンポーネントの反応性を取得することになりましたuseContext
。
そんな感じ:
import React, { createContext, useEffect, useState } from "react";
import ReactDOM from "react-dom";
type Settings = {
foo?: string;
}
const useSettings = () => {
let initialSettings: Settings = {
foo: undefined
};
try {
initialSettings = JSON.parse(localStorage.getItem("settings")) as Settings;
} catch(e) {};
const [settings, setSettings] = useState<Settings>(initialSettings);
useEffect(() => {
const listener = () => {
if (localStorage.getItem("settings") !== JSON.stringify(settings)) {
try {
setSettings(JSON.parse(localStorage.getItem("settings")) as Settings);
} catch(e) {};
}
}
window.addEventListener("storage", listener);
return () => window.removeEventListener("storage", listener);
}, []);
const setSettingsStorage = (newSettings: Settings) => {
localStorage.setItem("settings", JSON.stringify(newSettings));
}
return [settings, setSettingsStorage];
}
const Content = () => {
const [settings, setSettings] = useSettings();
return <>Hello</>;
}
ReactDOM.render(
<Content settings={{foo: "bar"}} />,
document.getElementById("root")
);
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加