Le problème que nous résolvons
Le contexte dans react peut contenir de nombreuses valeurs et différents consommateurs du contexte ne peuvent utiliser qu'une partie des valeurs. Cependant, lorsqu'une valeur change par rapport au contexte, tous les consommateurs (en particulier, tous les composants qui l'utilisent useContext
) seront renvoyés , même s'ils ne dépendent pas de la partie modifiée des données. Le problème est assez discuté et a de nombreuses solutions différentes. En voici quelques-uns. J'ai créé cet exemple pour illustrer le problème. Ouvrez simplement la console et appuyez sur les boutons.
but
Notre solution devrait modifier au minimum les bases de code existantes. Je souhaite créer mon propre hook personnalisé useSmartContext
avec la même signature que celle de useContext
, mais qui ne rendra le composant que lorsque la partie utilisée du contexte change.
Idée
Découvrez ce qui est utilisé par le composant en encapsulant la useSmartContext
valeur de retour dans un proxy.
Mise en œuvre
Étape 1.
Nous créons notre propre crochet.
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;
}
Nous avons créé une liste dans laquelle nous stockerons les champs de contexte utilisés. Nous avons créé un proxy avec un get
piège dans lequel nous remplissons cette liste. Target
cela n'a pas d'importance pour nous, alors j'ai passé un objet vide comme premier argument {}
.
Étape 2.
Vous devez obtenir la valeur du contexte lors de sa mise à jour et comparer la valeur des champs de la liste usedPropsRef
avec les valeurs précédentes. Si quelque chose a changé, déclenchez un nouveau rendu. useContext
Nous ne pouvons pas l' utiliser dans notre hook, sinon notre hook commencera également à provoquer un nouveau rendu pour toutes les modifications. Ici commencent les danses avec un tambourin. J'espérais à l'origine m'abonner aux changements de contexte avec context.Consumer
. A savoir comme ceci:
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
.
, !
, . .
En écrivant cet article, je suis tombé sur une autre bibliothèque qui résout le même problème avec l'optimisation des redessins lors de l'utilisation du contexte. La solution de cette bibliothèque, à mon avis, est la plus correcte que j'ai vue. Ses sources sont beaucoup plus lisibles et elles m'ont donné quelques idées sur la façon de préparer notre exemple de production sans changer le mode d'utilisation. Si je rencontre une réponse positive de votre part, j'écrirai sur la nouvelle implémentation.
Merci à tous pour votre attention.