Das Problem, das wir lösen
Der Kontext in Reaktion kann viele Werte enthalten und verschiedene Konsumenten des Kontexts können nur einen Teil der Werte verwenden. Wenn sich jedoch ein Wert aus dem Kontext ändert, werden alle Verbraucher (insbesondere alle verwendeten Komponenten useContext
) erneut gerendert , auch wenn sie nicht vom geänderten Teil der Daten abhängen. Das Problem ist ziemlich diskutiert und hat viele verschiedene Lösungen. Hier sind einige davon. Ich habe dieses Beispiel erstellt , um das Problem zu demonstrieren. Öffnen Sie einfach die Konsole und drücken Sie die Tasten.
Zweck
Unsere Lösung sollte die vorhandenen Codebasen auf ein Minimum beschränken. Ich möchte einen eigenen benutzerdefinierten Hook useSmartContext
mit derselben Signatur wie die von erstellen , der useContext
die Komponente jedoch nur dann neu rendert, wenn sich der verwendete Teil des Kontexts ändert.
Idee
Finden Sie heraus, was von der Komponente verwendet wird, indem Sie den Rückgabewert useSmartContext
in einen Proxy einschließen.
Implementierung
Schritt 1.
Wir kreieren unseren eigenen Haken.
const useSmartContext(context) {
const usedFieldsRef = useRef(new Set());
const proxyRef = useRef(
new Proxy(
{},
{
get(target, prop) {
usedPropsRef.current.add(prop);
return context._currentValue[prop];
}
}
)
);
return proxyRef.current;
}
Wir haben eine Liste erstellt, in der wir die verwendeten Kontextfelder speichern. Wir haben einen Proxy mit einer get
Falle erstellt, in der wir diese Liste ausfüllen. Target
es ist uns egal, also habe ich als erstes Argument ein leeres Objekt übergeben {}
.
Schritt 2.
Sie müssen den Wert des Kontexts abrufen, wenn er aktualisiert wird, und den Wert der Felder aus der Liste usedPropsRef
mit den vorherigen Werten vergleichen. Wenn sich etwas geändert hat, lösen Sie ein erneutes Rendern aus. useContext
Wir können nicht es verwenden in unseren Haken, sonst unser Haken beginnt auch erneutes Rendern für alle Änderungen verursachen. Hier beginnen Tänze mit einem Tamburin. Ich hatte ursprünglich gehofft, Kontextänderungen mit zu abonnieren context.Consumer
. Nämlich so:
React.createElement(context.Consumer, {}, (newContextVakue) => {/* handle */})
. . - , , , .
React
, useContext
. , , , . - . _currentValue
. , undefined
. ! Proxy , . Object.defineProperty
.
let val = context._currentValue;
let notEmptyVal = context._currentValue;
Object.defineProperty(context, "_currentValue", {
get() {
return val;
},
set(newVal) {
if (newVal) {
// !
}
val = newVal;
}
});
! : useSmartContext
Object.defineProperty
. useSmartContext
createContext
.
export const createListenableContext = () => {
const context = createContext();
const listeners = [];
let val = context._currentValue;
let notEmptyVal = context._currentValue;
Object.defineProperty(context, "_currentValue", {
get() {
return val;
},
set(newVal) {
if (newVal) {
listeners.forEach((cb) => cb(notEmptyVal, newVal));
notEmptyVal = newVal;
}
val = newVal;
}
});
context.addListener = (cb) => {
listeners.push(cb);
return () => listeners.splice(listeners.indexOf(cb), 1);
};
return context;
};
, . ,
const useSmartContext = (context) => {
const usedFieldsRef = useRef(new Set());
useEffect(() => {
const clear = context.addListener((prevValue, newValue) => {
let isChanged = false;
usedFieldsRef.current.forEach((usedProp) => {
if (!prevValue || newValue[usedProp] !== prevValue[usedProp]) {
isChanged = true;
}
});
if (isChanged) {
//
}
});
return clear;
}, [context]);
const proxyRef = useRef(
new Proxy(
{},
{
get(target, prop) {
usedFieldsRef.current.add(prop);
return context._currentValue[prop];
}
}
)
);
return proxyRef.current;
};
3.
. useState
, . , . - ?
// ...
const [, rerender] = useState();
const renderTriggerRef = useRef(true);
// ...
if (isChanged) {
renderTriggerRef.current = !renderTriggerRef.current;
rerender(renderTriggerRef.current);
}
, . . useContext
->useSmartContext
createContext
->createListenableContext
.
, !
, . .
Beim Schreiben dieses Artikels bin ich auf eine andere Bibliothek gestoßen, die das gleiche Problem bei der Optimierung von Neuzeichnungen bei Verwendung des Kontexts löst. Die Lösung dieser Bibliothek ist meiner Meinung nach die richtigste, die ich je gesehen habe. Die Quellen sind viel besser lesbar und sie gaben mir einige Ideen, wie wir unsere Beispielproduktion fertigstellen können, ohne die Art der Verwendung zu ändern. Wenn ich eine positive Antwort von Ihnen bekomme, werde ich über die neue Implementierung schreiben.
Vielen Dank für Ihre Aufmerksamkeit.