Czy Mogę ustawić stan wewnątrz Hooka useEffect

Powiedzmy, że mam pewien stan, który jest zależny od innego stanu(np. gdy a zmienia się, chcę, aby B zmienił).

Czy właściwe jest utworzenie Hooka, który obserwuje A i ustawia B wewnątrz Hooka useEffect?

Czy efekty będą kaskadowe tak, że po kliknięciu przycisku pierwszy efekt uruchomi się, powodując zmianę b, powodując uruchomienie drugiego efektu, przed następnym renderowaniem? Czy są jakieś minusy wydajności do strukturyzowania kodu w ten sposób?

let MyComponent = props => {
  let [a, setA] = useState(1)
  let [b, setB] = useState(2)
  useEffect(
    () => {
      if (/*some stuff is true*/) {
        setB(3)
      }
    },
    [a],
  )
  useEffect(
    () => {
      // do some stuff
    },
    [b],
  )

  return (
    <button
      onClick={() => {
        setA(5)
      }}
    >
      click me
    </button>
  )
}
Author: Bogdan D, 2018-12-10

5 answers

Efekty są zawsze wykonywane po zakończeniu fazy renderowania, nawet jeśli ustawisz stan wewnątrz jednego efektu, inny efekt odczyta zaktualizowany stan i podejmie na nim działania dopiero po zakończeniu fazy renderowania.

Powiedziawszy, że prawdopodobnie lepiej jest podjąć oba działania w tym samym efekcie, chyba że istnieje możliwość, że b może się zmienić z powodów innych niż changing a w którym to przypadku również chcesz wykonać tę samą logikę

 33
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
2019-06-11 13:52:11

Ogólnie rzecz biorąc, użycie setState wewnątrz useEffect stworzy nieskończoną pętlę, której najprawdopodobniej nie chcesz wywołać. Jest kilka wyjątków od tej zasady, o których później się dowiem.

useEffect jest wywoływane po każdym renderowaniu i kiedy setState jest używane wewnątrz niego, spowoduje to ponowne renderowanie komponentu, który wywoła useEffect i tak dalej i tak dalej.

Jednym z popularnych przypadków, że użycie useState wewnątrz useEffect nie spowoduje nieskończonej pętli, jest przekazanie pustej tablicy jako drugi argument useEffect Jak useEffect(() => {....}, []), co oznacza, że funkcja efektu powinna być wywołana raz: tylko po pierwszym montowaniu/renderowaniu. Jest to powszechnie używane, gdy wykonujesz pobieranie danych w komponencie i chcesz zapisać dane żądania w stanie komponentu.

 117
Author: Hossam Mourad,
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-13 07:05:23

W przyszłości może to również pomóc:]}

Jest ok, aby używać setState w useEffect wystarczy mieć uwagę, jak już opisano, aby nie tworzyć pętli.

Ale to nie jedyny problem, który może wystąpić. Zobacz poniżej:

Wyobraź sobie, że masz komponent Comp, który odbiera props od rodzica i zgodnie ze zmianą props chcesz ustawić stan Comp. Z jakiegoś powodu musisz zmienić dla każdego rekwizytu w innym useEffect:

NIE RÓB To

useEffect(() => {
  setState({ ...state, a: props.a });
}, [props.a]);

useEffect(() => {
  setState({ ...state, b: props.b });
}, [props.b]);

Może nigdy nie zmienić stanu a, jak widać w tym przykładzie: https://codesandbox.io/s/confident-lederberg-dtx7w

Powodem, dla którego tak się dzieje w tym przykładzie jest to, że oba efekty działania są uruchamiane w tym samym cyklu Reacta, gdy zmienisz oba prop.a i prop.b, więc wartość {...state} kiedy to zrobisz setState są dokładnie takie same w obu useEffect, ponieważ są w tym samym kontekście. Po uruchomieniu drugiego setState zastąpi on pierwszy setState.

DO THIS INSTEAD

Rozwiązaniem tego problemu jest w zasadzie wywołanie setState w następujący sposób:

useEffect(() => {
  setState(state => ({ ...state, a: props.a }));
}, [props.a]);

useEffect(() => {
  setState(state => ({ ...state, b: props.b }));
}, [props.b]);

Sprawdź rozwiązanie tutaj: https://codesandbox.io/s/mutable-surf-nynlx

Teraz zawsze otrzymujesz najbardziej aktualną i poprawną wartość stanu, gdy kontynuujesz setState.

Mam nadzieję, że to komuś pomoże!

 66
Author: while true,
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-23 08:48:07

useEffect może zaczepić pewien rekwizyt lub stan. tak więc, rzeczą, którą musisz zrobić, aby uniknąć nieskończonej pętli hook, jest związanie jakiejś zmiennej lub stanu do efektu

Na Przykład:

useEffect(myeffectCallback, [])

Powyższy efekt zostanie wywołany tylko po wyrenderowaniu komponentu. jest to podobne do componentDidMount cyklu życia

const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
  setSomething(0)
}, myState)

Powyższy efekt odpali tylko Mój stan się zmienił to jest podobne do componentDidUpdate tyle że nie każdy zmieniający się stan odpali go.

Możesz przeczytać więcej szczegółów, chociaż ten link

 27
Author: Dogies007,
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-23 00:38:44

▶ 1. Czy Mogę ustawić stan wewnątrz Hooka useEffect?

W zasadzie można dowolnie ustawić stan tam, gdzie jest to potrzebne - w tym wewnątrz useEffect i nawet podczas renderowania . Upewnij się, że unikasz nieskończonych pętli, ustawiając Hook deps poprawnie i / lub warunkowo.


▶ 2. Powiedzmy, że mam jakiś stan, który jest zależny od innego stanu. Czy właściwe jest utworzenie Hooka, który obserwuje A i ustawia B wewnątrz Hooka useEffect?

Ty po prostu opisany klasyczny przypadek użycia dla useReducer:

useReducer jest zwykle korzystniejszy od useState, gdy masz złożony stan logika, która zawiera wiele pod-wartości lub gdy następny stan zależy od poprzedniego. ( react docs )

Podczas ustawiania zmiennej state zależy od bieżącej wartości innego stanu , Możesz spróbować zastąpić je obie zmienną useReducer. [...] Kiedy znajdziesz siebie pisząc setSomething(something => ...), to dobry moment, aby rozważyć użycie reduktora. (Dan Abramov, Overreacted blog )

let MyComponent = () => {
  let [state, dispatch] = useReducer(reducer, { a: 1, b: 2 });

  useEffect(() => {
    console.log("Some effect with B");
  }, [state.b]);

  return (
    <div>
      <p>A: {state.a}, B: {state.b}</p>
      <button onClick={() => dispatch({ type: "SET_A", payload: 5 })}>
        Set A to 5 and Check B
      </button>
      <button onClick={() => dispatch({ type: "INCREMENT_B" })}>
        Increment B
      </button>
    </div>
  );
};

// B depends on A. If B >= A, then reset B to 1.
function reducer(state, { type, payload }) {
  const someCondition = state.b >= state.a;

  if (type === "SET_A")
    return someCondition ? { a: payload, b: 1 } : { ...state, a: payload };
  else if (type === "INCREMENT_B") return { ...state, b: state.b + 1 };
  return state;
}

ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect } = React</script>

▶ 3. Czy efekty będą kaskadowe w taki sposób, że po kliknięciu przycisku pierwszy efekt uruchomi się, powodując zmianę b, powodując uruchomienie drugiego efektu, przed następnym renderowaniem?

useEffect zawsze uruchamia Po Render jest zatwierdzany i stosowane są zmiany DOM. Pierwszy efekt jest wywołany, zmienia b i powoduje ponowne renderowanie. Po zakończeniu renderowania, Drugi efekt zostanie uruchomiony z powodu zmian b.

let MyComponent = props => {
  console.log("render");
  let [a, setA] = useState(1);
  let [b, setB] = useState(2);

  let isFirstRender = useRef(true);

  useEffect(() => {
    console.log("useEffect a, value:", a);
    if (isFirstRender.current) isFirstRender.current = false;
    else setB(3);
    return () => {
      console.log("unmount useEffect a, value:", a);
    };
  }, [a]);
  useEffect(() => {
    console.log("useEffect b, value:", b);
    return () => {
      console.log("unmount useEffect b, value:", b);
    };
  }, [b]);

  return (
    <div>
      <p>a: {a}, b: {b}</p>
      <button
        onClick={() => {
          console.log("Clicked!");
          setA(5);
        }}
      >
        click me
      </button>
    </div>
  );
};

ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

▶ 4. Czy są jakieś minusy wydajności do strukturyzowania kodu w ten sposób?

Tak. Poprzez owinięcie zmiany stanu b w oddzielny useEffect dla a, przeglądarka ma dodatkową fazę layout/paint - efekty te są potencjalnie widoczne dla użytkownika. Jeśli nie ma sposobu, aby dać useReducer spróbować, możesz zmienić b Stan razem z a bezpośrednio:

let MyComponent = () => {
  console.log("render");
  let [a, setA] = useState(1);
  let [b, setB] = useState(2);

  useEffect(() => {
    console.log("useEffect b, value:", b);
    return () => {
      console.log("unmount useEffect b, value:", b);
    };
  }, [b]);

  const handleClick = () => {
    console.log("Clicked!");
    setA(5);
    b >= 5 ? setB(1) : setB(b + 1);
  };

  return (
    <div>
      <p>
        a: {a}, b: {b}
      </p>
      <button onClick={handleClick}>click me</button>
    </div>
  );
};

ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
 12
Author: ford04,
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-03-26 12:58:08