Устаревшее состояние с настраиваемым перехватчиком реакции - PullRequest
1 голос
/ 28 мая 2020

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App />,
  rootElement
)

function square(n, timeout = 1000) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(n * n), timeout);
  });
}

function App() {
  const [number, setNumber] = React.useState(0);
  const [loading, result, error, reload] = useAsync(
    () => square(number, 1000),
    [number]
  );
  console.log(number, result);
  return (
    <div className="App">
      <div>
        Decrement{" "}
        <button
          disabled={loading}
          type="button"
          onClick={e => setNumber(number => setNumber(number - 1))}
        >
          -
        </button>
      </div>
      <div>Number: {number}</div>
      <div>Its square: {result} {loading && <span className="fa fa-spinner"></span>}</div>
      <div>
        Increment
        <button
          type="button"
          disabled={loading}
          onClick={e => setNumber(number => setNumber(number + 1))}
        >
          +
        </button>
      </div>
    </div>
  );
}



function useAsync(func, dependencyArray = []) {
  const [state, setState] = React.useState({
    loading: false,
    result: null,
    error: null,
    mounted: true
  });

  const reload = () => {
    function call() {
        setState(state => ({
          ...state,
          loading: true,
          error: null,
          result: null
        }));
        func()
        .then(res=>{
          if (!state.mounted) return;
          setState(state => ({
            ...state,
            result: res,
            loading: false
          }));
        })
        .catch(err=>{
          setState(state => ({
          ...state,
          loading: false,
          result: null,
          error
        }));
        })        
    }
    call();
  };

  React.useEffect(() => {
    reload();
    return () =>
      setState({
        ...state,
        loading: false,
        result: null,
        error: null
      });
  }, dependencyArray);

  React.useEffect(() => {
    setState(state => ({ ...state, mounted: true }));
    return () => setState(state => ({ ...state, mounted: false }));
  }, []);

  return [state.loading, state.result, state.error, reload, setState];
}
.App {
  font-family: sans-serif;
  text-align: center;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-direction: column;
}

button {
  width: 100px;
  height: 40px;
  border-radius: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">

Я использую настраиваемый перехватчик реакции для вызова API и установки состояния в моем компоненте реакции. hook принимает функцию, которая возвращает массив обещаний и зависимостей. Крюк возвращает [loading, result, error, reload, setState]. Я надеюсь, что результат будет сброшен, как только изменится зависимость хука, но состояние остается устаревшим для одного рендера, который нарушает logi c в компоненте. Действия по воспроизведению. I. Предположим, что число 2, тогда его квадрат равен 4. II. Откройте консоль. III. с шагом 2, число станет 3, через одну секунду квадрат будет 9. IV. В консоли вы увидите что-то вроде: 2 4 , 3 4 , 3 null , 3 9 V. 3 4 здесь содержит ошибки.

Ответы [ 2 ]

1 голос
/ 28 мая 2020

Вы видите null в консоли, потому что при изменении числа вы сбрасываете состояние внутри вашего настраиваемого хука, чтобы иметь result = null в функции перезагрузки, а также в функции очистки useEffect

const reload = () => {
    async function call() {
      try {
        setState(state => ({
          ...state,
          loading: true,
          error: null,
          result: null
        }));
        const res = await func();
        if (!state.mounted) return;
        setState(state => ({
          ...state,
          result: res,
          loading: false
        }));
      } catch (error) {
        if (!state.mounted) return;
        setState(state => ({
          ...state,
          loading: false,
          result: null,
          error
        }));
      }
    }
    call();
  };

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

Однако вы можете удалить setState в функции очистки useEffect, что не требуется

const reload = () => {
    async function call() {
      try {
        setState(state => ({
          ...state,
          loading: true,
        }));
        const res = await func();
        if (!state.mounted) return;
        setState(state => ({
          ...state,
          result: res,
          loading: false
        }));
      } catch (error) {
        if (!state.mounted) return;
        setState(state => ({
          ...state,
          loading: false,
          result: null,
          error
        }));
      }
    }
    call();
  };

  React.useEffect(() => {
    reload();
  }, dependencyArray);
0 голосов
/ 28 мая 2020

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

Если ваша основная задача - убедиться, что пользователь не видит неправильный результат, вы можете заставить переданную функцию вернуть массив с исходным аргументом и ответом, а затем убедитесь, что текущее значение из number соответствует ему:

function square(n, timeout = 1000) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve([n, n * n]), timeout);
  });
}

export default function App() {
  const [number, setNumber] = useState(0);
  const [loading, argAndResult, error, reload] = useAsync(
    () => square(number, 1000),
    [number]
  );
  if (argAndResult && argAndResult[0] === number) 
      { result = argAndResult[1]; }
  else { result = null; }

Не идеально, но будет работать в определенных случаях использования.

...