javascript - Ascultă modificările unei valori dintr-un obiect în contextul React


Având următorul React Context Provider. O simplă clasă Counter cu 2 metode, stocată în contextul React.

import { createContext, useContext } from "react";

class Counter {
  public count: number = 0;

  getCount = () => {
    return this.count;
  };

  incrementCount = () => {
    this.count = this.count + 1;
  };
}

type CounterContextType = {
  counter: Counter;
};

const defaults: CounterContextType = {
  counter: new Counter()
};

const CounterContext = createContext<CounterContextType>(defaults);

export const CounterProvider: React.FC = ({ children }) => {
  const counter = new Counter();

  return (
    <CounterContext.Provider
      value={{
        counter
      }}
    >
      {children}
    </CounterContext.Provider>
  );
};

export const useCounter = () => {
  return useContext(CounterContext);
};

Vreau să ascult modificările proprietății count a instanței Counter.

Iată ce am încercat:

import { useMemo } from "react";
import { CounterProvider, useCounter } from "./CounterProvider";

const DisplayWithMethod = () => {
  const { counter } = useCounter();

  return <div>Method: {counter.getCount()}</div>;
};

const DisplayWithProperty = () => {
  const { counter } = useCounter();

  return <div>Prop: {counter.count}</div>;
};

const DisplayWithMemo = () => {
  const { counter } = useCounter();

  const val = useMemo(() => counter.count, [counter.count]);

  return <div>Memo: {val}</div>;
};

const Button = () => {
  const { counter } = useCounter();

  return <button onClick={counter.incrementCount}>Increment</button>;
};

export default function App() {
  return (
    <CounterProvider>
      <DisplayWithMethod />
      <DisplayWithProperty />
      <DisplayWithMemo />
      <Button />
    </CounterProvider>
  );
}

Niciunul dintre acestea nu funcționează deoarece instanța counter nu se schimbă niciodată, așadar nu se declanșează re-renderizări. Ai vreo idee despre cum să faci ca acest lucru să funcționeze păstrând totodată o structură de clasă pentru Counter?

https://codesandbox.io/s/nostalgic-fast-cyflg

Răspuns #1

Problema este că React nu înțelege că numărul s-a schimbat și nu face rerender. Poți scăpa de problemă folosind hook-ul useState.

Ar trebui să schimbi definițiile de tip ca mai jos și să construiești instanțe de counter folosind output-urile hook-ului useState.

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

type CounterContextType = {
  counter: {
    count: number;
    getCount: () => number;
    incrementCount: () => void;
  };
};

const defaults: CounterContextType = {
  counter: {
    count: 0,
    getCount: () => 0,
    incrementCount: () => undefined
  }
};

const CounterContext = createContext<CounterContextType>(defaults);

export const CounterProvider: React.FC = ({ children }) => {
  const [count, setCount] = useState<number>(0);

  return (
    <CounterContext.Provider
      value={{
        counter: {
          count,
          getCount: () => count,
          incrementCount: () => {
            setCount((prevCount) => prevCount + 1);
          }
        }
      }}
    >
      {children}
    </CounterContext.Provider>
  );
};

export const useCounter = () => {
  return useContext(CounterContext);
};

Cutie de cod