Подтвердить что ты не робот

Как сравнить oldValues и newValues на React Hooks useEffect?

Допустим, у меня есть 3 входа: скорость, sendAmount и receiveAmount. Я поставил эти 3 входа на использование различных параметров. Правила таковы:

  • Если sendAmount изменился, я вычисляю receiveAmount = sendAmount * rate
  • Если receiveAmount изменился, я рассчитываю sendAmount = receiveAmount/rate
  • Если скорость изменилась, я рассчитываю receiveAmount = sendAmount * rate когда sendAmount > 0 или я вычисляю sendAmount = receiveAmount/rate когда receiveAmount > 0

Вот коды и окно https://codesandbox.io/s/pkl6vn7x6j, чтобы продемонстрировать проблему.

Есть ли способ сравнить oldValues и newValues как в componentDidUpdate вместо создания 3 обработчиков для этого случая?

Спасибо


Вот мое окончательное решение с использованием usePrevious https://codesandbox.io/s/30n01w2r06

В этом случае я не могу использовать несколько useEffect потому что каждое изменение приводит к одному и тому же сетевому вызову. Вот почему я также использую changeCount чтобы отслеживать изменения тоже. Этот changeCount также полезен для отслеживания изменений только локально, поэтому я могу предотвратить ненужный сетевой вызов из-за изменений с сервера.

4b9b3361

Ответ 1

Вы можете написать собственный хук, чтобы предоставить вам предыдущий реквизит, используя useRef

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

а затем использовать его в 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])
}

Однако его понятнее и, вероятно, лучше и яснее читать и понимать, если вы используете два useEffect отдельно для каждого идентификатора изменения, который вы хотите обработать отдельно.

Ответ 2

Вариант 1 - использовать крючок

Сравнение текущего значения с предыдущим значением является распространенным шаблоном и оправдывает его собственный пользовательский хук, который скрывает детали реализации.

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

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

const Component = (props) => {
  ...
  const hasVal1Changed = useCompare(val1)
  const hasVal2Changed = useCompare(val2);
  useEffect(() => {
    if (hasVal1Changed ) {
      console.log("val1 has changed");
    }
    if (hasVal2Changed ) {
      console.log("val2 has changed");
    }
  });

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

Demo

Вариант 2 - запустить useEffect при изменении значения

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

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

Demo

Ответ 3

Incase кто-нибудь ищет версию использования TypeScriptPrevious:

import { useEffect, useRef } from "react";

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

Ответ 4

Поскольку состояние не тесно связано с экземпляром компонента в функциональных компонентах, предыдущее состояние не может быть достигнуто в useEffect без предварительного его сохранения, например, с помощью useRef. Это также означает, что обновление состояния возможно было неправильно реализовано в неправильном месте, поскольку предыдущее состояние доступно внутри setState обновления setState.

Это хороший вариант использования для useReducer который предоставляет Redux-подобное хранилище и позволяет реализовать соответствующий шаблон. Обновления состояний выполняются явно, поэтому нет необходимости выяснять, какое свойство состояния обновляется; это уже ясно из отправленных действий.

Вот пример того, как это может выглядеть:

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
});
...

Ответ 5

Для действительно простого сравнения реквизитов вы можете использовать useEffect и useState, чтобы легко проверить, обновился ли реквизит.

const myComponent = ({ prop }) => {
  const [propHasChanged, setPropHasChanged] = useState(false)
  useEffect(() => {
    setHasPropChanged(true)
  }, [prop])

  if(propHasChanged){
    ~ do stuff here ~
  }
}

useEffect проверяет, изменился ли реквизит, обновляет состояние, чтобы сказать, что оно изменилось, и позволяет вашему условному коду выполняться.