Многократные вызовы для обновления состояния из useState в компоненте вызывают многократные повторные рендеринг - PullRequest
0 голосов
/ 01 декабря 2018

Я пробую перехватывать React в первый раз, и все показалось хорошим, пока я не понял, что когда я получаю данные и обновляю две разные переменные состояния (данные и флаг загрузки), мой компонент (таблица данных) отображается дважды, дажехотя оба вызова средства обновления состояния происходят в одной и той же функции.Вот моя функция API, которая возвращает обе переменные моему компоненту.

const getData = url => {

    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(async () => {

        const test = await api.get('/people')

        if(test.ok){
            setLoading(false);
            setData(test.data.results);
        }

    }, []);

    return { data, loading };
};

В обычном компоненте класса вы бы сделали один вызов, чтобы обновить состояние, которое может быть сложным объектом, но "перехватывает путь"«Похоже, нужно разделить состояние на более мелкие блоки, побочным эффектом которых является несколько повторных визуализаций, когда они обновляются отдельно.Любые идеи, как смягчить это?

Ответы [ 5 ]

0 голосов
/ 14 июля 2019

Это будет делать:

const [state, setState] = useState({ username: '', password: ''});

// later
setState({
    ...state,
    username: 'John'
});
0 голосов
/ 10 июня 2019

У этого также есть другое решение, использующее useReducer!сначала мы определяем наш новый setState.

const [state, setState] = useReducer(
  (state, newState) => ({...state, ...newState}),
  {loading: true, data: null}
)

, после этого мы можем просто использовать его как старые добрые классы this.setState, только без this!

setState({loading: false, data: test.data.results})

Итак, все вместе мы имеем:

import {useReducer} from 'react'

const getData = url => {
  const [state, setState] = useReducer(
    (state, newState) => ({...state, ...newState}),
    {loading: true, data: null}
  )

  useEffect(async () => {
    const test = await api.get('/people')
    if(test.ok){
      setState({loading: false, data: test.data.results})
    }
  }, [])

  return state
}
0 голосов
/ 02 февраля 2019

Пакетное обновление в обработчиках реакции https://github.com/facebook/react/issues/14259

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

0 голосов
/ 11 марта 2019

Небольшое дополнение к ответу https://stackoverflow.com/a/53575023/121143

Круто!Для тех, кто планирует использовать этот хук, он мог бы быть написан немного более надежно для работы с функцией в качестве аргумента, например:

const useMergedState = initial => {
  const [state, setState] = React.useState(initial);
  const setMergedState = newState =>
    typeof newState == "function"
      ? setState(prevState => ({ ...prevState, ...newState(prevState) }))
      : setState(prevState => ({ ...prevState, ...newState }));
  return [state, setMergedState];
};

Обновление : оптимизированная версия,состояние не будет изменено, если входящее частичное состояние не было изменено.

const shallowPartialCompare = (obj, partialObj) =>
  Object.keys(partialObj).every(
    key =>
      obj.hasOwnProperty(key) &&
      obj[key] === partialObj[key]
  );

const useMergedState = initial => {
  const [state, setState] = React.useState(initial);
  const setMergedState = newIncomingState =>
    setState(prevState => {
      const newState =
        typeof newIncomingState == "function"
          ? newIncomingState(prevState)
          : newIncomingState;
      return shallowPartialCompare(prevState, newState)
        ? prevState
        : { ...prevState, ...newState };
    });
  return [state, setMergedState];
};
0 голосов
/ 02 декабря 2018

Вы можете объединить состояние loading и состояние data в один объект состояния, а затем сделать один вызов setState, и будет только один рендеринг.

Примечание: В отличие от setState в компонентах класса, setState, возвращаемое из useState, не объединяет объекты с существующим состоянием, оно полностью заменяет объект.Если вы хотите выполнить слияние, вам нужно прочитать предыдущее состояние и объединить его с новыми значениями самостоятельно.Обратитесь к документам .

. Я бы не стал слишком беспокоиться о том, чтобы вызывать рендеры, пока вы не решите, что у вас есть проблема с производительностью.Рендеринг (в контексте React) и фиксация обновлений виртуального DOM в реальном DOM - разные вопросы.Рендеринг здесь относится к генерации виртуальных DOM, а не к обновлению DOM браузера.React может пакетировать вызовы setState и обновлять DOM браузера, указав окончательное новое состояние.

const {useState, useEffect} = React;

function App() {
  const [userRequest, setUserRequest] = useState({
    loading: false,
    user: null,
  });

  useEffect(() => {
    // Note that this replaces the entire object and deletes user key!
    setUserRequest({ loading: true });
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUserRequest({
          loading: false,
          user: data.results[0],
        });
      });
  }, []);

  const { loading, user } = userRequest;

  return (
    <div>
      {loading && 'Loading...'}
      {user && user.name.first}
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

Альтернатива - написать собственный хук слияния состояний

const {useState, useEffect} = React;

function useMergeState(initialState) {
  const [state, setState] = useState(initialState);
  const setMergedState = newState => 
    setState(prevState => Object.assign({}, prevState, newState)
  );
  return [state, setMergedState];
}

function App() {
  const [userRequest, setUserRequest] = useMergeState({
    loading: false,
    user: null,
  });

  useEffect(() => {
    setUserRequest({ loading: true });
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUserRequest({
          loading: false,
          user: data.results[0],
        });
      });
  }, []);

  const { loading, user } = userRequest;

  return (
    <div>
      {loading && 'Loading...'}
      {user && user.name.first}
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
...