Chcę zaczekać na zastosowanie aktualizacji stanu z zaplecza, jeśli pewna animacja jest obecnie uruchomiona. Ta animacja mogłaby działać wiele razy w zależności od scenariusza gry. Używam React-Native z hookami i firestore.

Planowałem stworzyć tablicę, która będzie przechowywać obiekty przychodzącej migawki oraz funkcję, która użyje tych danych do aktualizacji stanu. Po zakończeniu animacji ustawiłoby to, że animacja była uruchomiona, na false i usunęło pierwszy element tablicy. Napisałbym również useEffect, który usuwałby pierwszy element z tablicy, gdyby długość tablicy się zmieniła.

Zamierzałem zaimplementować tę funkcję, sprawdzając, czy ta animacja jest uruchomiona, czy też istnieje element w tablicy przyszłych aktualizacji, gdy pojawi się najnowsza migawka. Gdyby ten warunek był prawdziwy, dodałbym migawkę i funkcję aktualizacji do mojej tablicy, w przeciwnym razie natychmiast zastosowałbym aktualizację stanu. Muszę uzyskać dostęp do tego elementu stanu we wszystkich trzech moich odbiornikach Firestore.

Jednak w onSnapshot, jeśli spróbuję uzyskać dostęp do mojego stanu, poda mi stan początkowy od momentu renderowania funkcji. Jedynym wyjątkiem jest to, że mogę uzyskać dostęp do stanu, jeśli użyję funkcji do ustawienia stanu, w tym przypadku setPlayerIsBetting i uzyskam dostęp do poprzedniego stanu poprzez funkcję przekazaną jako wywołanie zwrotne do setPlayerIsBetting.

Przychodzi mi do głowy kilka możliwych rozwiązań, ale wszystkie poza pierwszym, z którym mam kłopoty w implementacji, sprawiają wrażenie hakerskich.

  1. Czy otrzymam aktualizacje stanu w przyszłości, jeśli zmodyfikuję właściwość useEffect dla migawek, aby nie działały tylko po zamontowaniu składnika? Krótko próbowałem tego, ale wydaje się, że psuje migawki. Czy ktoś wiedziałby, jak to zaimplementować?

  2. Uzyskaj dostęp do stanu poprzez wywołanie setPlayerIsBetting we wszystkich 3 odbiornikach i po prostu ustaw setPlayerIsBetting na poprzedni stan w 99% przypadków, gdy nie powinien być aktualizowany. Czy nawet wyrenderuje się ponownie, jeśli nic nie zostanie zmienione? Czy może to spowodować inne problemy?

  3. Przez cały cykl życia komponentu dodawaj migawki i funkcje aktualizacji do kolejki zamiast tylko wtedy, gdy animacja jest uruchomiona. To może nie być optymalne dla wydajności, prawda? Nie musiałbym się tym martwić, ponieważ mój początkowy plan polegał na wprowadzeniu kilku aktualizacji stanu po uruchomieniu animacji, ponieważ i tak potrzebowałem czasu, aby poczekać na animację.

  4. Mógłbym dodać stan, którego potrzebuję, wszędzie na zapleczu, aby pojawił się wraz z migawką.

  5. Jakaś metoda, która usuwa, a następnie dodaje słuchaczy. To zły pomysł.

  6. Czy Redux lub jakieś narzędzie do zarządzania stanem może rozwiązać ten problem? Wdrożenie go w tym jednym przypadku wymagałoby dużo pracy, ale może moje aplikacje w miejscu, w którym i tak byłoby to przydatne?

Oto mój odpowiedni kod:

const Game = ({ route }) => {
  const [playerIsBetting, setPlayerIsBetting] = useState({
    isBetting: false,
    display: false,
    step: Infinity,
    minimumValue: -1000000,
    maximumValue: -5000,
  });
  const [updatesAfterAnimations, setUpdatesAfterAnimations] = useState([]); 
  // updatesAfterAnimations is currently always empty because I can't access the updated playerIsBetting state easily

  const chipsAnimationRunningOrItemsInQueue = (snapshot, updateFunction) => {
    console.log(
      "in chipsAnimationRunningOrItemsInQueue playerIsBetting is: ",
      playerIsBetting
    ); // always logs the initial state since its called from the snapshots. 
// So it doesn't know when runChipsAnimation is added to the state and becomes true. 
// So playerIsBetting.runChipsAnimation is undefined
    const addToQueue =
      playerIsBetting.runChipsAnimation || updatesAfterAnimations.length;
    if (addToQueue) {
      setUpdatesAfterAnimations((prevState) => {
        const nextState = cloneDeep(prevState);
        nextState.push({ snapshot, updateFunction });
        return nextState;
      });
      console.log("chipsAnimationRunningOrItemsInQueue returns true!");
      return true;
    }
    console.log("chipsAnimationRunningOrItemsInQueue returns false!");
    return false;
  };

  // listener 1
  useEffect(() => {
    const db = firebase.firestore();
    const tableId = route.params.tableId;
    const unsubscribeFromPlayerCards = db
      .collection("tables")
      .doc(tableId)
      .collection("players")
      .doc(player.uniqueId)
      .collection("playerCards")
      .doc(player.uniqueId)
      .onSnapshot(
        function (cardsSnapshot) {
          if (!chipsAnimationRunningOrItemsInQueue(cardsSnapshot, updatePlayerCards)) {
            updatePlayerCards(cardsSnapshot);
          }
        },
        function (err) {
          // console.log('error is: ', err);
        }
      );
    return unsubscribeFromPlayerCards;
  }, []);
};

// listener 2
useEffect(() => {
  const tableId = route.params.tableId;
  const db = firebase.firestore();
  const unsubscribeFromPlayers = db
    .collection("tables")
    .doc(tableId)
    .collection("players")
    .onSnapshot(
      function (playersSnapshot) {
        console.log("in playerSnapshot playerIsBetting is: ", playerIsBetting); // also logs the initial state 
        console.log("in playerSnapshot playerIsBetting.runChipsAnimation is: "playerIsBetting.runChipsAnimation); // logs undefined
        if (!chipsAnimationRunningOrItemsInQueue(playersSnapshot, updatePlayers)) {
          updatePlayers(playersSnapshot);
        }
      },
      (err) => {
        console.log("error is: ", err);
      }
    );
  return unsubscribeFromPlayers;
}, []);

// listener 3
useEffect(() => {
  const db = firebase.firestore();
  const tableId = route.params.tableId;
  // console.log('tableId is: ', tableId);
  const unsubscribeFromTable = db
    .collection("tables")
    .doc(tableId)
    .onSnapshot(
      (tableSnapshot) => {
        if (!chipsAnimationRunningOrItemsInQueue(tableSnapshot, updateTable)) {
          updateTable(tableSnapshot);
        }
      },
      (err) => {
        throw new err();
      }
    );
  return unsubscribeFromTable;
}, []);
1
Gwater17 29 sierpień 2020, 03:56

2 odpowiedzi

Najlepsza odpowiedź

Skończyło się na tym, że nie zdecydowałem się na żadne z zaproponowanych przeze mnie rozwiązań.

Zdałem sobie sprawę, że mogę uzyskać dostęp do aktualnego stanu za pomocą ref. Jak to zrobić, wyjaśniono tutaj: (https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559) A to jest odpowiedni przykład kodu z tego posta: (https://codesandbox.io/s / event-handler-use-ref-4hvxt? from-embed)

Rozwiązanie nr 1 mogło zadziałać, ale byłoby trudne, ponieważ musiałbym obejść funkcję czyszczenia działającą, gdy zmienia się stan animacji. (Dlaczego funkcja czyszczenia z „useEffect” wywoływane przy każdym renderowaniu?)

Mogę obejść ten problem, ponieważ funkcja czyszczenia nie wywołuje funkcji anulowania subskrypcji od nasłuchiwania i przechowuje funkcje anulowania subskrypcji w stanie i umieszcza je wszystkie w useEffect po zamontowaniu komponentu z drugim parametrem, który potwierdza, że wszystkie 3 funkcje anulowania subskrypcji zostały dodane określić.

Ale jeśli użytkownik wyłączył się, zanim te funkcje były w stanie, myślę, że mogą wystąpić wycieki pamięci.

0
Gwater17 30 sierpień 2020, 01:04

Wybrałbym rozwiązanie nr 1 : w hakach UseEffect() można umieścić flagę boolowską, aby odbiornik migawki był ustawiany tylko raz na zaczep. Następnie umieść właściwość stanu animacji w tablicy zależności useEffect, tak aby każdy punkt zaczepienia useEffect był wyzwalany, gdy zmieni się stan animacji, a następnie możesz uruchomić dowolną logikę z tego warunku.

0
apena 29 sierpień 2020, 01:44