Jak porównać stare wartości i nowe wartości w Reactowym Hooku useEffect?

Załóżmy, że mam 3 wejścia: rate, sendAmount i receiveAmount. Umieściłem te 3 wejścia na useEffect diffing params. Zasady są następujące:

  • Jeśli sendamount się zmienił, obliczam receiveAmount = sendAmount * rate
  • Jeśli receiveamount się zmienił, obliczam sendAmount = receiveAmount / rate
  • jeśli kurs się zmienił, obliczam receiveAmount = sendAmount * rate Kiedy sendAmount > 0 lub obliczam sendAmount = receiveAmount / rate Kiedy receiveAmount > 0

Oto codesandbox https://codesandbox.io/s/pkl6vn7x6j aby zademonstrować problem.

Jest jest sposób, aby porównać oldValues i newValues Jak na componentDidUpdate zamiast zrobić 3 obsługi dla tej sprawy?

Dzięki


Oto moje ostateczne rozwiązanie z usePrevious https://codesandbox.io/s/30n01w2r06

W tym przypadku nie mogę użyć wielu useEffect, ponieważ każda zmiana prowadzi do tego samego wywołania sieciowego. Dlatego też używam changeCount do śledzenia zmian. To changeCount również pomocne do śledzenia zmian tylko z lokalnego, więc mogę zapobiec niepotrzebnym połączeń sieciowych z powodu zmian na serwerze.

Author: rwinzhang, 2018-11-23

12 answers

Możesz napisać własny hook, aby dostarczyć poprzednie właściwości używając useRef

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

A następnie użyj go w useEffect

const Component = (props) => {
    const {receiveAmount, sendAmount } = props
    const prevAmount = usePrevious({receiveAmount, sendAmount});
    useEffect(() => {
        if(prevAmount.receiveAmount !== receiveAmount) {

         // process here
        }
        if(prevAmount.sendAmount !== sendAmount) {

         // process here
        }
    }, [receiveAmount, sendAmount])
}

Jednak jego jaśniejsze i prawdopodobnie lepsze i jaśniejsze do odczytania i zrozumienia, jeśli używasz dwóch useEffect oddzielnie dla każdego ID zmiany chcesz je przetwarzać oddzielnie

 290
Author: Shubham Khatri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-23 12:22:38

Okaż, że ktoś szuka wersji maszynopisu usePrevious:

W module .tsx:

import { useEffect, useRef } from "react";

const usePrevious = <T extends unknown>(value: T): T | undefined => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

Lub w module .ts:

import { useEffect, useRef } from "react";

const usePrevious = <T>(value: T): T | undefined => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
 74
Author: SeedyROM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-19 22:49:40

Opcja 1-Uruchom useEffect po zmianie wartości

const Component = (props) => {

  useEffect(() => {
    console.log("val1 has changed");
  }, [val1]);

  return <div>...</div>;
};

Demo

Opcja 2-useHasChanged hook

Porównywanie bieżącej wartości do poprzedniej jest powszechnym wzorcem i uzasadnia własny hook, który ukrywa szczegóły implementacji.

const Component = (props) => {
  const hasVal1Changed = useHasChanged(val1)

  useEffect(() => {
    if (hasVal1Changed ) {
      console.log("val1 has changed");
    }
  });

  return <div>...</div>;
};

const useHasChanged= (val: any) => {
    const prevVal = usePrevious(val)
    return prevVal !== val
}

const usePrevious = (value) => {
    const ref = useRef();
    useEffect(() => {
      ref.current = value;
    });
    return ref.current;
}


Demo

 44
Author: Ben Carp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-28 13:05:53

Wychodząc z zaakceptowanej odpowiedzi, alternatywnego rozwiązania, które nie wymaga niestandardowego Hooka:

const Component = ({ receiveAmount, sendAmount }) => {
  const prevAmount = useRef({ receiveAmount, sendAmount }).current;
  useEffect(() => {
    if (prevAmount.receiveAmount !== receiveAmount) {
     // process here
    }
    if (prevAmount.sendAmount !== sendAmount) {
     // process here
    }
    return () => { 
      prevAmount.receiveAmount = receiveAmount;
      prevAmount.sendAmount = sendAmount;
    };
  }, [receiveAmount, sendAmount]);
};

Zakłada to, że rzeczywiście potrzebujesz odniesienia do poprzednich wartości dla czegokolwiek w bitach" proces tutaj". W przeciwnym razie, o ile twoje uwarunkowania nie wykraczają poza proste !== porównanie, najprostszym rozwiązaniem byłoby tutaj:

const Component = ({ receiveAmount, sendAmount }) => {
  useEffect(() => {
     // process here
  }, [receiveAmount]);

  useEffect(() => {
     // process here
  }, [sendAmount]);
};
 11
Author: Drazen Bjelovuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-03 17:30:16

Właśnie opublikowałem react-delta który rozwiązuje dokładnie ten scenariusz. Moim zdaniem useEffect ma zbyt wiele obowiązków.

Obowiązki

  1. porównuje wszystkie wartości w tablicy zależności za pomocą Object.is
  2. uruchamia wywołania efektu / czyszczenia na podstawie wyniku # 1

Rozbijanie Obowiązków

react-delta łamie obowiązki useEffect na kilka mniejszych hooków.

Odpowiedzialność #1

Odpowiedzialność #2

Z mojego doświadczenia wynika, że takie podejście jest bardziej elastyczne, czyste i zwięzłe niż useEffect/useRef rozwiązania.

 6
Author: Austin Malerba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-12 15:31:48

Dla naprawdę prostego porównania prop możesz użyć useEffect, aby łatwo sprawdzić, czy prop został zaktualizowany.

const myComponent = ({ prop }) => {
  useEffect(() => {
    ---Do stuffhere----
  }, [prop])
}

useEffect uruchomi Kod tylko wtedy, gdy prop się zmieni.

 4
Author: Charlie Tupman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-30 03:16:32

Ponieważ stan nie jest ściśle powiązany z instancją komponentu w komponentach funkcyjnych, poprzedni stan nie może zostać osiągnięty w useEffect bez uprzedniego zapisania go, na przykład za pomocą useRef. Oznacza to również, że aktualizacja stanu została prawdopodobnie nieprawidłowo zaimplementowana w niewłaściwym miejscu, ponieważ poprzedni stan jest dostępny wewnątrz setState funkcji updater.

Jest to dobry przypadek użycia dla useReducer, który zapewnia magazyn podobny do Redux i pozwala zaimplementować odpowiedni wzór. Aktualizacje stanu są wykonywane jawnie, więc nie ma potrzeby, aby dowiedzieć się, która własność państwa jest aktualizowana; jest to już jasne z wysłanej akcji.

Oto przykład Jak to może wyglądać:

function reducer({ sendAmount, receiveAmount, rate }, action) {
  switch (action.type) {
    case "sendAmount":
      sendAmount = action.payload;
      return {
        sendAmount,
        receiveAmount: sendAmount * rate,
        rate
      };
    case "receiveAmount":
      receiveAmount = action.payload;
      return {
        sendAmount: receiveAmount / rate,
        receiveAmount,
        rate
      };
    case "rate":
      rate = action.payload;
      return {
        sendAmount: receiveAmount ? receiveAmount / rate : sendAmount,
        receiveAmount: sendAmount ? sendAmount * rate : receiveAmount,
        rate
      };
    default:
      throw new Error();
  }
}

function handleChange(e) {
  const { name, value } = e.target;
  dispatch({
    type: name,
    payload: value
  });
}

...
const [state, dispatch] = useReducer(reducer, {
  rate: 2,
  sendAmount: 0,
  receiveAmount: 0
});
...
 3
Author: Estus Flask,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-23 14:45:23

Użycie Ref wprowadzi nowy rodzaj błędu do aplikacji.

Zobaczmy ten przypadek używając usePrevious, który ktoś skomentował wcześniej:

  1. prop.minTime: 5 = = > ref.current = 5 / set ref.current
  2. prop.minTime: 5 = = > ref.current = 5 / nowa wartość jest równa ref.current
  3. prop.minTime: 8 = = > ref.current = 5 / nowa wartość nie jest równa ref.current
  4. prop.minTime: 5 = = > ref.current = 5 / nowa wartość jest równa ref.current

Jak widzimy tutaj, my nie aktualizujemy wewnętrznego ref, ponieważ używamy useEffect

 3
Author: santomegonzalo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-28 15:32:23

Jeśli wolisz useEffect podejście zastępcze:

const usePreviousEffect = (fn, inputs = []) => {
  const previousInputsRef = useRef([...inputs])

  useEffect(() => {
    fn(previousInputsRef.current)
    previousInputsRef.current = [...inputs]
  }, inputs)
}

I użyj go tak:

usePreviousEffect(
  ([prevReceiveAmount, prevSendAmount]) => {
    if (prevReceiveAmount !== receiveAmount) // side effect here
    if (prevSendAmount !== sendAmount) // side effect here
  },
  [receiveAmount, sendAmount]
)

Zauważ, że pierwszy Czas wykonania efektu, poprzednie wartości przekazane do twojego fn będą takie same jak początkowe wartości wejściowe. Ma to dla Ciebie znaczenie tylko wtedy, gdy chcesz coś zrobić, gdy wartość nie zmienia się .

 3
Author: J. VanLeeuwen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-12 15:53:49

Oto Niestandardowy hook, którego używam, który moim zdaniem jest bardziej intuicyjny niż używanie usePrevious.

import { useRef, useEffect } from 'react'

// useTransition :: Array a => (a -> Void, a) -> Void
//                              |_______|  |
//                                  |      |
//                              callback  deps
//
// The useTransition hook is similar to the useEffect hook. It requires
// a callback function and an array of dependencies. Unlike the useEffect
// hook, the callback function is only called when the dependencies change.
// Hence, it's not called when the component mounts because there is no change
// in the dependencies. The callback function is supplied the previous array of
// dependencies which it can use to perform transition-based effects.
const useTransition = (callback, deps) => {
  const func = useRef(null)

  useEffect(() => {
    func.current = callback
  }, [callback])

  const args = useRef(null)

  useEffect(() => {
    if (args.current !== null) func.current(...args.current)
    args.current = deps
  }, deps)
}

Użyłbyś useTransition w następujący sposób.

useTransition((prevRate, prevSendAmount, prevReceiveAmount) => {
  if (sendAmount !== prevSendAmount || rate !== prevRate && sendAmount > 0) {
    const newReceiveAmount = sendAmount * rate
    // do something
  } else {
    const newSendAmount = receiveAmount / rate
    // do something
  }
}, [rate, sendAmount, receiveAmount])
Mam nadzieję, że to pomoże.
 1
Author: Aadit M Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-08 13:22:19

Możesz użyć useImmer w przeciwieństwie do useState i uzyskać dostęp do stanu. Przykład: https://css-tricks.com/build-a-chat-app-using-react-hooks-in-100-lines-of-code/

 0
Author: joemillervi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-18 03:57:38

W Twoim przypadku (obiekt prosty):

useEffect(()=>{
  // your logic
}, [rate, sendAmount, receiveAmount])

W innym przypadku (obiekt złożony)

const {cityInfo} = props;
useEffect(()=>{
  // some logic
}, [cityInfo.cityId])
 0
Author: JChen___,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-28 09:00:02