React: Verhindern von erneuten Renderings mit React.memo und useContext-Hook.

Erstellt am 19. März 2019  ·  37Kommentare  ·  Quelle: facebook/react

Möchten Sie eine Funktion anfordern oder einen Fehler melden?

Insekt

Wie ist das aktuelle Verhalten?

Ich kann mich nicht auf Daten aus der Kontext-API verlassen, indem ich (useContext-Hook) verwende, um unnötige Neudarstellungen mit React.memo zu verhindern

Wenn das aktuelle Verhalten ein Fehler ist, stellen Sie bitte die Schritte zur Reproduktion und wenn möglich eine minimale Demo des Problems bereit. Fügen Sie den Link zu Ihrem JSFiddle (https://jsfiddle.net/Luktwrdm/) oder CodeSandbox (https://codesandbox.io/s/new) Beispiel unten ein:

React.memo(() => {
const [globalState] = useContext(SomeContext);

render ...

}, (prevProps, nextProps) => {

// How to rely on context in here?
// I need to rerender component only if globalState contains nextProps.value

});

Was ist das erwartete Verhalten?

Ich sollte irgendwie Zugriff auf den Kontext im zweiten Argument-Callback von React.memo haben, um das Rendern zu verhindern
Oder ich sollte die Möglichkeit haben, eine alte Instanz der Reaktionskomponente im Funktionsrumpf zurückzugeben.

Welche React-Versionen und welche Browser/Betriebssysteme sind von diesem Problem betroffen?
16.8.4

Hilfreichster Kommentar

Dies funktioniert wie geplant. Es gibt eine längere Diskussion darüber in https://github.com/facebook/react/issues/14110, wenn Sie neugierig sind.

Nehmen wir an, Sie haben aus irgendeinem Grund AppContext dessen Wert eine theme Eigenschaft hat, und Sie möchten nur einige ExpensiveTree bei appContextValue.theme Änderungen neu rendern.

TLDR bedeutet, dass Sie im Moment drei Optionen haben:

Option 1 (bevorzugt): Kontexte aufteilen, die sich nicht gemeinsam ändern

Wenn wir nur appContextValue.theme in vielen Komponenten benötigen, sich aber appContextValue selbst zu oft ändert, könnten wir ThemeContext von AppContext aufteilen.

function Button() {
  let theme = useContext(ThemeContext);
  // The rest of your rendering logic
  return <ExpensiveTree className={theme} />;
}

Jetzt werden bei jeder Änderung von AppContext ThemeContext Verbraucher nicht erneut gerendert.

Dies ist die bevorzugte Lösung. Dann brauchen Sie kein spezielles Rettungspaket.

Option 2: Teilen Sie Ihre Komponente in zwei Teile, fügen Sie memo dazwischen

Wenn Sie Kontexte aus irgendeinem Grund nicht aufteilen können, können Sie das Rendering dennoch optimieren, indem Sie eine Komponente in zwei Teile aufteilen und spezifischere Requisiten an die innere übergeben. Sie würden immer noch das äußere rendern, aber es sollte billig sein, da es nichts tut.

function Button() {
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"
  return <ThemedButton theme={theme} />
}

const ThemedButton = memo(({ theme }) => {
  // The rest of your rendering logic
  return <ExpensiveTree className={theme} />;
});

Option 3: Eine Komponente mit useMemo darin

Schließlich könnten wir unseren Code etwas ausführlicher gestalten, ihn aber in einer einzigen Komponente belassen, indem wir den Rückgabewert in useMemo umschließen und seine Abhängigkeiten angeben. Unsere Komponente würde immer noch erneut ausgeführt, aber React würde den untergeordneten Baum nicht erneut rendern, wenn alle useMemo Eingaben gleich sind.

function Button() {
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"

  return useMemo(() => {
    // The rest of your rendering logic
    return <ExpensiveTree className={theme} />;
  }, [theme])
}

In Zukunft könnte es noch mehr Lösungen geben, aber die haben wir jetzt.

Beachten Sie jedoch, dass Option 1 vorzuziehen ist – wenn sich der Kontext zu oft ändert, sollten Sie ihn aufteilen .

Alle 37 Kommentare

Dies funktioniert wie geplant. Es gibt eine längere Diskussion darüber in https://github.com/facebook/react/issues/14110, wenn Sie neugierig sind.

Nehmen wir an, Sie haben aus irgendeinem Grund AppContext dessen Wert eine theme Eigenschaft hat, und Sie möchten nur einige ExpensiveTree bei appContextValue.theme Änderungen neu rendern.

TLDR bedeutet, dass Sie im Moment drei Optionen haben:

Option 1 (bevorzugt): Kontexte aufteilen, die sich nicht gemeinsam ändern

Wenn wir nur appContextValue.theme in vielen Komponenten benötigen, sich aber appContextValue selbst zu oft ändert, könnten wir ThemeContext von AppContext aufteilen.

function Button() {
  let theme = useContext(ThemeContext);
  // The rest of your rendering logic
  return <ExpensiveTree className={theme} />;
}

Jetzt werden bei jeder Änderung von AppContext ThemeContext Verbraucher nicht erneut gerendert.

Dies ist die bevorzugte Lösung. Dann brauchen Sie kein spezielles Rettungspaket.

Option 2: Teilen Sie Ihre Komponente in zwei Teile, fügen Sie memo dazwischen

Wenn Sie Kontexte aus irgendeinem Grund nicht aufteilen können, können Sie das Rendering dennoch optimieren, indem Sie eine Komponente in zwei Teile aufteilen und spezifischere Requisiten an die innere übergeben. Sie würden immer noch das äußere rendern, aber es sollte billig sein, da es nichts tut.

function Button() {
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"
  return <ThemedButton theme={theme} />
}

const ThemedButton = memo(({ theme }) => {
  // The rest of your rendering logic
  return <ExpensiveTree className={theme} />;
});

Option 3: Eine Komponente mit useMemo darin

Schließlich könnten wir unseren Code etwas ausführlicher gestalten, ihn aber in einer einzigen Komponente belassen, indem wir den Rückgabewert in useMemo umschließen und seine Abhängigkeiten angeben. Unsere Komponente würde immer noch erneut ausgeführt, aber React würde den untergeordneten Baum nicht erneut rendern, wenn alle useMemo Eingaben gleich sind.

function Button() {
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"

  return useMemo(() => {
    // The rest of your rendering logic
    return <ExpensiveTree className={theme} />;
  }, [theme])
}

In Zukunft könnte es noch mehr Lösungen geben, aber die haben wir jetzt.

Beachten Sie jedoch, dass Option 1 vorzuziehen ist – wenn sich der Kontext zu oft ändert, sollten Sie ihn aufteilen .

Bei beiden Optionen wird das Rendern von Kindern verhindert, wenn sich das Thema nicht geändert hat.

@gaearon Sind die Buttons die Kinder oder rendern die Buttons Kinder? Mir fehlt etwas Kontext, wie diese verwendet werden.

Die Verwendung der Option unstable_Profiler 2 löst immer noch onRender Callbacks aus, ruft jedoch nicht die eigentliche Renderlogik auf. Vielleicht mache ich etwas falsch? ~ https://codesandbox.io/s/kxz4o2oyoo~ https://codesandbox.io/s/00yn9yqzjw

Ich habe das Beispiel aktualisiert, um es klarer zu machen.

Die Verwendung der unstable_Profiler-Option 2 löst weiterhin onRender-Callbacks aus, ruft jedoch nicht die eigentliche Renderlogik auf. Vielleicht mache ich etwas falsch? https://codesandbox.io/s/kxz4o2oyoo

Genau das ist der Sinn dieser Option. :-)

Vielleicht wäre eine gute Lösung dafür, die Möglichkeit zu haben, den Kontext zu "nehmen" und die Komponente nur dann neu zu rendern, wenn der Rückruf true zurückgibt, z.
useContext(ThemeContext, (contextData => contextData.someArray.length !== 0 ));

Das Hauptproblem bei Hooks, das ich tatsächlich kennengelernt habe, ist, dass wir nicht innerhalb eines Hooks verwalten können, was von einer Komponente zurückgegeben wird - um das Rendern zu verhindern, den gespeicherten Wert zurückzugeben usw.

Wenn wir könnten, wäre es nicht komponierbar.

https://overreacted.io/why-isnt-xa-hook/#not -a-hook-usebailout

Option 4: Verwenden Sie keinen Kontext für die Datenweitergabe, sondern ein Datenabonnement. Verwenden Sie useSubscription (weil es schwer ist, alle Fälle abzudecken).

Es gibt eine andere Möglichkeit, das erneute Rendern zu vermeiden.
"Sie müssen den JSX eine Stufe höher aus der Re-Rendering-Komponente verschieben, dann wird er nicht jedes Mal neu erstellt."

Mehr Infos hier

Vielleicht wäre eine gute Lösung dafür, die Möglichkeit zu haben, den Kontext zu "nehmen" und die Komponente nur dann neu zu rendern, wenn der Rückruf true zurückgibt, z.
useContext(ThemeContext, (contextData => contextData.someArray.length !== 0 ));

Das Hauptproblem bei Hooks, das ich tatsächlich kennengelernt habe, ist, dass wir nicht innerhalb eines Hooks verwalten können, was von einer Komponente zurückgegeben wird - um das Rendern zu verhindern, den gespeicherten Wert zurückzugeben usw.

Anstelle von wahr/falsch hier... könnten wir eine identitätsbasierte Funktion bereitstellen, die es uns ermöglicht, die Daten aus dem Kontext zu subsetieren?

const contextDataINeed = useContext(ContextObj, (state) => state['keyICareAbout'])

wobei useContext nicht in dieser Komponente erscheinen würde, es sei denn, das Ergebnis des Selektors fn unterschied sich in Bezug auf die Identität vom vorherigen Ergebnis derselben Funktion.

fand diese Bibliothek, dass dies die Lösung für Facebook sein könnte, um Hooks zu integrieren https://blog.axlight.com/posts/super-performant-global-state-with-react-context-and-hooks/

Es gibt eine andere Möglichkeit, das erneute Rendern zu vermeiden.
"Sie müssen den JSX eine Stufe höher aus der Re-Rendering-Komponente verschieben, dann wird er nicht jedes Mal neu erstellt."

Mehr Infos hier

Das Problem ist, dass es kostspielig sein kann, den Komponentenbaum umzustrukturieren, nur um ein erneutes Rendern von oben nach unten zu verhindern.

@fuleinist Letztendlich unterscheidet es sich nicht so sehr von MobX, obwohl es für einen bestimmten Anwendungsfall stark vereinfacht ist. MobX funktioniert bereits so (auch unter Verwendung von Proxies), der Zustand wird mutiert und Komponenten, die bestimmte Bits des Zustands verwenden, werden neu gerendert, sonst nichts.

@gaearon Ich weiß nicht, ob mir etwas fehlt, aber ich habe deine zweite und dritte Option ausprobiert und sie funktionieren nicht richtig. Ich bin mir nicht sicher, ob dies nur ein Fehler bei der Chrome-Erweiterung ist oder ob es einen anderen Haken gibt. Hier ist mein einfaches Formularbeispiel, bei dem ich beide Eingaben neu rendern sehe. In der Konsole sehe ich, dass Memo seinen Job macht, aber DOM ständig neu gerendert wird. Ich habe 1000 Elemente ausprobiert und das onChange-Ereignis ist wirklich langsam, deshalb denke ich, dass memo() nicht richtig mit dem Kontext funktioniert. Danke für jeden Rat:

Hier ist eine Demo mit 1000 Elementen/Textfeldern. Aber in dieser Demo zeigen die Entwicklertools kein erneutes Rendern an. Sie müssen lokale Quellen herunterladen, um es zu testen: https://codesandbox.io/embed/zen-firefly-d5bxk

import React, { createContext, useState, useContext, memo } from "react";

const FormContext = createContext();

const FormProvider = ({ initialValues, children }) => {
  const [values, setValues] = useState(initialValues);

  const value = {
    values,
    setValues
  };

  return <FormContext.Provider value={value}>{children}</FormContext.Provider>;
};

const TextField = memo(
  ({ name, value, setValues }) => {
    console.log(name);
    return (
      <input
        type="text"
        value={value}
        onChange={e => {
          e.persist();
          setValues(prev => ({
            ...prev,
            [name]: e.target.value
          }));
        }}
      />
    );
  },
  (prev, next) => prev.value === next.value
);

const Field = ({ name }) => {
  const { values, setValues } = useContext(FormContext);

  const value = values[name];

  return <TextField name={name} value={value} setValues={setValues} />;
};

const App = () => (
  <FormProvider initialValues={{ firstName: "Marr", lastName: "Keri" }}>
    First name: <Field name="firstName" />
    <br />
    Last name: <Field name="lastName" />
  </FormProvider>
);

export default App;

image

Auf der anderen Seite funktioniert dieser Ansatz ohne Kontext korrekt, ist immer noch im Debugging langsamer als ich erwartet hatte, aber zumindest ist das erneute Rendern in Ordnung

import React, { useState, memo } from "react";
import ReactDOM from "react-dom";

const arr = [...Array(1000).keys()];

const TextField = memo(
  ({ index, value, onChange }) => (
    <input
      type="text"
      value={value}
      onChange={e => {
        console.log(index);
        onChange(index, e.target.value);
      }}
    />
  ),
  (prev, next) => prev.value === next.value
);

const App = () => {
  const [state, setState] = useState(arr.map(x => ({ name: x })));

  const onChange = (index, value) =>
    setState(prev => {
      return prev.map((item, i) => {
        if (i === index) return { name: value };

        return item;
      });
    });

  return state.map((item, i) => (
    <div key={i}>
      <TextField index={i} value={item.name} onChange={onChange} />
    </div>
  ));
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

image

@marrkeri Ich sehe im ersten Code-Snippet nichts Falsches. Die in den Entwicklungstools hervorgehobene Komponente ist Field , die den Kontext verwendet, nicht die TextField die eine Memokomponente ist und die areEqual-Funktion implementiert.

Ich denke, das Leistungsproblem im Codesandbox-Beispiel kommt von den 1000 Komponenten, die den Kontext verwenden. Refaktorieren Sie es in eine Komponente, die den Kontext verwendet, sagen wir Fields , und geben Sie von dieser Komponente (mit einer Zuordnung) ein TextField für jeden Wert zurück.

@marrkeri Ich sehe im ersten Code-Snippet nichts Falsches. Die in den Entwicklungstools hervorgehobene Komponente ist Field , die den Kontext verwendet, nicht die TextField die eine Memokomponente ist und die areEqual-Funktion implementiert.

Ich denke, das Leistungsproblem im Codesandbox-Beispiel kommt von den 1000 Komponenten, die den Kontext verwenden. Refaktorieren Sie es in eine Komponente, die den Kontext verwendet, sagen wir Fields , und geben Sie von dieser Komponente (mit einer Zuordnung) ein TextField für jeden Wert zurück.

Wie du sagtest, war ich unter dem gleichen Gedanken, dass sollte jedes Mal neu gerendert werden, aber ( ) nur bei Wertänderung. Aber es sind wahrscheinlich nur Probleme mit den Entwicklertools, weil ich das Padding hinzugefügt habe und statt dessen wird neu gerendert. Überprüfen Sie dieses Bild
image

image

Ich habe Ihren zweiten Punkt zum Refactoring auf eine Komponente nicht verstanden . Könnten Sie bitte Schnappschüsse machen? Und was haltet ihr von der maximal angezeigten Anzahl von welche sind ok ohne zu verzögern? Ist 1000 zu viel?

@marrkeri Ich habe so etwas vorgeschlagen: https://codesandbox.io/s/little-night-p985y.

Ist dies der Grund, warum React-Redux bei der Migration zu Hooks aufhören musste , die Vorteile der Stable-Context-API zu nutzen und den aktuellen Status an den Kontext weiterzugeben?

Scheint also so zu sein

Option 4: Übergeben Sie eine Subscribe-Funktion als Kontextwert, genau wie in der Legacy-Kontext-Ära

Dies ist möglicherweise die einzige Option, wenn Sie die Verwendungen nicht kontrollieren (für Optionen 2-3 erforderlich) und nicht alle möglichen Selektoren aufzählen können (für Option 1 erforderlich), aber dennoch eine Hooks-API verfügbar machen möchten

const MyContext = createContext()
export const Provider = ({children}) => (
  <MyContext.provider value={{subscribe: listener => ..., getValue: () => ...}}>
    {children}
  </MyContext.provider>
)

export const useSelector = (selector, equalityFunction = (a, b) => a === b) => {
  const {subscribe, getValue} = useContext(MyContext)
  const [value, setValue] = useState(getValue())
  useEffect(() => subscribe(state => {
      const newValue = selector(state)
      if (!equalityFunction(newValue, value) {
        setValue(newValue)
      }
  }), [selector, equalityFunction])
}

@Hypnosphi : Wir haben aufgehört, den Store-Zustand im Kontext zu übergeben (die v6-Implementierung) und sind zurück zu direkten Store-Abonnements (die v7-Implementierung) gewechselt, aufgrund einer Kombination von Leistungsproblemen und der Unfähigkeit, kontextbedingte Updates zu retten (was es machte unmöglich, eine React-Redux-Hooks-API basierend auf dem v6-Ansatz zu erstellen).

Weitere Informationen finden Sie in meinem Beitrag Die Geschichte und Implementierung von React-Redux .

Ich habe den Thread durchgelesen, aber ich frage mich immer noch - sind die einzigen Optionen, die heute verfügbar sind, um bei Kontextänderung bedingt neu zu rendern, "Optionen 1 2 3 4" oben aufgeführt? Ist etwas anderes in Arbeit, um dies offiziell anzugehen, oder werden die "4 Lösungen" als akzeptabel genug angesehen?

Ich habe im anderen Thread geschrieben, aber nur für den Fall. Hier ist eine _inoffizielle_ Problemumgehung.
useContextSelector- Vorschlag und use-context-selector- Bibliothek im Userland.

ehrlich gesagt, das lässt mich denken, dass das Framework einfach nicht bereit ist, vollständig in Funktionen und Hooks einzusteigen, wie wir es erwarten. Mit Klassen und Lebenszyklusmethoden hatte man eine ordentliche Möglichkeit, diese Dinge zu kontrollieren, und jetzt mit Hooks ist es eine viel schlechtere, weniger lesbare Syntax

Option 3 scheint nicht zu funktionieren. Mache ich etwas falsch? https://stackblitz.com/edit/react-w8gr8z

@Martin , dem

Das Hakenmuster kann mit gut organisierter Dokumentation und Code gelesen werden
Struktur und Reaktionsklassen und Lebenszyklus sind alle durch funktionale ersetzbar
gleichwertige.

Leider kann das reaktive Muster nicht durch React Along via . erreicht werden
entweder React-Klassen oder Hooks.

Am Sa, 11. Januar 2020 um 9:44 Uhr Martin Genev [email protected]
schrieb:

ehrlich gesagt, das lässt mich denken, dass das Framework einfach nicht einsatzbereit ist
vollständig in Funktionen und Hooks ein, wie wir dazu ermutigt werden. Mit Klassen
und Lifecycle-Methoden hatten Sie eine saubere Möglichkeit, diese Dinge zu kontrollieren und
Jetzt mit Hooks ist es eine viel schlimmere, weniger lesbare Syntax


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/facebook/react/issues/15156?email_source=notifications&email_token=AAI4DWUJ7WUXMHAR6F2KVXTQ5D25TA5CNFSM4G7UEEO2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMV2HJKTLV
oder abmelden
https://github.com/notifications/unsubscribe-auth/AAI4DWUCO7ORHV5OSDCE35TQ5D25TANCNFSM4G7UEEOQ
.

@mgenev Option 3 verhindert das erneute Rendern des Kindes ( <ExpensiveTree /> , Name spricht für sich selbst)

@Hypnosphi danke, das war richtig.
https://stackblitz.com/edit/react-ycfyye

Ich habe umgeschrieben und jetzt werden die eigentlichen Rendering-(Anzeige-)Komponenten nicht neu gerendert, aber alle Container (mit dem Kontext verbunden) rendern bei jeder Änderung der Requisite im Kontext, egal ob sie in ihnen verwendet wird. Die einzige Option, die ich sehen kann, besteht darin, die Kontexte aufzuteilen, aber einige Dinge sind wirklich global und werden auf höchster Ebene umbrochen, und eine Änderung an einer Requisite in ihnen führt dazu, dass alle Container in der gesamten App ausgelöst werden. Verstehe nicht, wie das für die Leistung gut sein kann...

Gibt es irgendwo ein gutes Beispiel dafür, wie man das performant macht? Die offiziellen Dokumente sind wirklich begrenzt

Sie können meine Option 4 ausprobieren, die im Grunde das ist, was React-Redux intern macht
https://github.com/facebook/react/issues/15156#issuecomment -546703046

Um die Funktion subscribe zu implementieren, können Sie etwas wie Observables oder EventEmitter verwenden oder einfach selbst eine grundlegende Abonnementlogik schreiben:

function StateProvider({children}) {
  const [state, dispatch] = useReducer(reducer, initialState)
  const listeners = useRef([])
  const subscribe = listener => {
    listeners.current.push(listener)
  }
  useEffect(() => {
    listeners.current.forEach(listener => listener(state)
  }, [state])

  return (
    <DispatchContext.Provider value={dispatch}>
      <SubscribeContext.Provider value={{subscribe, getValue: () => state}}>
          {children}      
      </SubscribeContext.Provider>
    </DispatchContext.Provider>
  );
}

Für diejenigen, die Interesse haben, hier der Vergleich verschiedener Bibliotheken, die etwas mit diesem Thema zu tun haben.
https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-mode

Meine letzte Anstrengung besteht darin, die Unterstützung von State Branching mit useTransition zu überprüfen, die im kommenden Concurrent Mode wichtig sein würde.
Grundsätzlich ist es in Ordnung, wenn wir React-Zustand und -Kontext auf normale Weise verwenden. (Ansonsten brauchen wir einen Trick, zum Beispiel diesen .)

Danke @dai-shi Deine Pakete gefallen mir sehr gut und ich überlege sie zu übernehmen.

@dai-shi hallo, ich habe gerade deine react-tracked Bibliothek gefunden und sie sieht wirklich gut aus, wenn sie Leistungsprobleme mit Kontexten löst, wie sie es verspricht. Ist es noch aktuell oder besser etwas anderes? Hier sehe ich ein gutes Beispiel für seine Verwendung und zeigt auch, wie man mit use-reducer-async Middleware-Niveau macht https://github.com/dai-shi/react-tracked/blob/master/examples/12_async/src/store .ts Danke dafür. Derzeit habe ich etwas Ähnliches mit useReducer , dispatch mit meiner eigenen für asynchrone Middleware umschlossen und Context aber ich habe Bedenken wegen zukünftiger Rendering-Leistungsprobleme aufgrund von Kontextumbrüchen.

@bobrosoft react-tracked ist ziemlich stabil, würde ich sagen (sicherlich als ein Entwicklerprodukt). Feedback ist sehr willkommen und so würde eine Bibliothek verbessert. Derzeit verwendet es intern ein nicht dokumentiertes Feature von React, und ich hoffe, wir können es in Zukunft durch ein besseres Primitiv ersetzen. use-reducer-async ist fast wie ein einfacher Syntaxzucker, der nie schief gehen würde.

Dieser HoC hat bei mir funktioniert:
````
import React, { useMemo, ReactElement, FC } from 'react';
Import von 'lodash/reduce' reduzieren;

type Selector = (Kontext: beliebig) => beliebig;

Schnittstelle SelectorObject {
}

const withContext = (
Komponente: FC,
Kontext: beliebig,
Selektoren: SelectorObject,
): FC => {
return (props: any): ReactElement => {
const Consumer = ({ context }: any): ReactElement => {
const contextProps = reduzieren(
Selektoren,
(acc: any, selector: Selector, key: string): any => {
const-Wert = Selektor(Kontext);
acc[key] = Wert;
Rücksendung gem;
},
{},
);
zurück useMemo(
(): ReactElement => ,
[...Object.values(props), ...Object.values(contextProps)],
);
};
Rückkehr (

{(Kontext: beliebig): ReactElement => }

);
};
};

Standard mit Kontext exportieren;
````

Anwendungsbeispiel:

export default withContext(Component, Context, { value: (context): any => context.inputs.foo.value, status: (context): any => context.inputs.foo.status, });

Dies könnte als das Kontextäquivalent von redux mapStateToProps angesehen werden

Ich habe einen hoc erstellt, der in Redux fast sehr ähnlich zu connect() ist

const withContext = (
  context = createContext(),
  mapState,
  mapDispatchers
) => WrapperComponent => {
  function EnhancedComponent(props) {
    const targetContext = useContext(context);
    const { ...statePointers } = mapState(targetContext);
    const { ...dispatchPointers } = mapDispatchers(targetContext);
    return useMemo(
      () => (
        <WrapperComponent {...props} {...statePointers} {...dispatchPointers} />
      ),
      [
        ...Object.values(statePointers),
        ...Object.values(props),
        ...Object.values(dispatchPointers)
      ]
    );
  }
  return EnhancedComponent;
};

Implementierung :

const mapActions = state => {
  return {};
};

const mapState = state => {
  return {
    theme: (state && state.theme) || ""
  };
};
export default connectContext(ThemeContext, mapState, mapActions)(Button);


Update: Letztendlich bin ich auf EventEmitter umgestiegen, um sich schnell ändernde, granulare Daten mit dynamischen Listenern (bei Mausbewegung) zu erhalten. Mir wurde klar, dass es das bessere Werkzeug für den Job war. Der Kontext eignet sich hervorragend für die allgemeine gemeinsame Nutzung von Daten, jedoch nicht bei hohen Aktualisierungsraten.

Kommt es nicht darauf an, den Kontext deklarativ zu abonnieren oder nicht? Bedingtes Umschließen einer Komponente in eine andere, die useContext() verwendet.

Die Hauptanforderung besteht darin, dass die innere Komponente keinen Zustand haben kann, da sie aufgrund der Verzweigung effektiv eine andere Instanz ist. Oder vielleicht können Sie die Komponente vorab rendern und dann cloneElement verwenden, um die Requisiten zu aktualisieren.

Einige Komponenten haben nichts mit dem Kontext beim Rendern zu tun, sondern müssen "nur einen Wert daraus lesen". Was vermisse ich?
context

Kann Context._currentValue sicher in der Produktion verwendet werden?

Ich versuche, Komponenten für Kontexte zu abonnieren, die möglichst billig zu rendern sind. Aber dann fand ich mich dazu, Requisiten wie auf die alte Art mit einem Sphagetti-Code zu übergeben oder Memos zu verwenden, um zu vermeiden, dass Updates abonniert werden, wenn mit Updates nichts logisches zu tun hat. Memo-Lösungen sind gut, wenn Ihr Komponenten-Rendering von Kontexten abhängt, aber ansonsten ...

@vamshi9666 hast du das

Ich fand https://recoiljs.org/ eine gute Lösung für dieses Problem. Ich fände es toll, wenn du es in React integrieren würdest.

@vamshi9666 hast du das

Ich habe es nur an einer Stelle verwendet und mein ursprüngliches Ziel ist es, die Zustandsverwaltung von jsx zu isolieren, aber auch kein Nicht-Boilerplate zu erstellen. Als ich die Mutationsfunktionalität erweitert habe, sah es dem Reducer- und Action-Creator-Muster von Redux sehr ähnlich. Was noch besser ist. Aber ich sehe keinen Sinn, etwas neu zu erfinden, wenn es tatsächlich schon da ist.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen