구성 요소의 useState에서 상태 업데이트 프로그램을 여러 번 호출하면 여러 번 다시 렌더링됩니다.


122

처음으로 React hooks를 시도하고 있는데 데이터를 얻고 두 개의 다른 상태 변수 (데이터 및 로딩 플래그)를 업데이트 할 때 두 호출 모두에도 불구하고 내 구성 요소 (데이터 테이블)가 두 번 렌더링된다는 사실을 깨달을 때까지 모두 괜찮아 보였습니다. 상태 업데이트 프로그램에 동일한 기능이 발생합니다. 다음은 내 구성 요소에 두 변수를 모두 반환하는 내 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 };
};

일반 클래스 구성 요소에서는 복잡한 개체가 될 수있는 상태를 업데이트하기 위해 단일 호출을 할 수 있지만 "후크 방식"은 상태를 더 작은 단위로 분할하는 것 같습니다. 별도로 업데이트 될 때 렌더링됩니다. 이것을 완화하는 방법에 대한 아이디어가 있습니까?


의존하는 상태가 있다면 아마도 사용해야 할 것입니다useReducer
thinklinux

와! 나는 방금 이것을 발견했고 반응 렌더링이 어떻게 작동하는지에 대한 이해를 완전히 깨뜨 렸습니다. 이 방식으로 작업하는 이점을 이해할 수 없습니다. 비동기 콜백의 동작이 일반 이벤트 처리기의 동작과 다르다는 것은 다소 임의적 인 것 같습니다. BTW, 내 테스트에서 모든 setState 호출이 처리 될 때까지 조정 (즉, 실제 DOM의 업데이트)이 발생하지 않는 것으로 보이므로 중간 렌더링 호출은 어쨌든 낭비됩니다.
Andy

답변:


122

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>


1
나는 그것이 이상적이지 않다는 데 동의하지만 여전히 합리적인 방법입니다. 병합을 직접 수행하고 싶지 않은 경우 병합을 수행하는 사용자 지정 후크를 작성할 수 있습니다. 더 명확하게 마지막 문장을 업데이트했습니다. React의 작동 방식에 대해 잘 알고 있습니까? 그렇지 않은 경우, 이해하는 데 도움이되는 몇 가지 링크가 있습니다 ( reactjs.org/docs/reconciliation.html , blog.vjeux.com/2013/javascript/react-performance.html) .
Yangshun Tay

2
@jonhobbs useMergeState상태를 자동으로 병합 할 수 있도록 후크 를 만드는 대체 답변을 추가했습니다 .
Yangshun Tay

1
다시 한 번 감사드립니다. React 사용에 익숙해지고 있지만 내부에 대해 많은 것을 배울 수 있습니다. 내가 읽어 줄게.
jonhobbs

2
어떤 상황에서 이것이 사실 일 수 있는지 궁금 setState
하십니까

1
Nice .. 상태를 결합하거나 별도의 페이로드 상태를 사용하고 다른 상태를 페이로드 객체에서 읽도록하는 것에 대해 생각했습니다. 그래도 아주 좋은 게시물. 10/10 다시 읽을 것입니다
Anthony Moon Beam Toorie

63

이것은 또한 useReducer!를 사용하는 또 다른 해결책이 있습니다 . 먼저 새로운 setState.

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

우리는 단순히 좋은 오래된 클래스처럼 사용할 수있는 후 this.setState만하지 this!

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

새 버전에서 알 수 있듯이 setState(이전에했던 것과 마찬가지로 this.setState) 모든 상태를 함께 업데이트 할 필요가 없습니다! 예를 들어 다음과 같이 상태 중 하나를 변경할 수 있습니다 (다른 상태는 변경하지 않습니다!).

setState({loading: false})

멋지다, 하?!

이제 모든 조각을 모아 보겠습니다.

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
}

Expected 0 arguments, but got 1.ts(2554)그런 식으로 useReducer를 사용하려고 시도하면 오류가 발생합니다. 더 정확하게 setState. 어떻게 고쳐 요? 파일은 js이지만 여전히 ts 검사를 사용합니다.
ZenVentzi

굉장 형제, 감사합니다
Nisharg 샤

두 주가 합쳐 졌다는 것은 여전히 ​​같은 생각이라고 생각합니다. 그러나 어떤 경우에는 상태를 병합 할 수 없습니다.
user1888955

45

react-hooks에서 일괄 업데이트 https://github.com/facebook/react/issues/14259

React는 버튼 클릭이나 입력 변경과 같은 React 기반 이벤트 내에서 트리거되는 경우 현재 일괄 상태 업데이트를 수행합니다. 비동기 호출과 같이 React 이벤트 핸들러 외부에서 트리거되는 경우 일괄 업데이트가 수행되지 않습니다.


5
이것은 거의 대부분의 경우 아무 것도하지 않고도 문제를 해결하므로 매우 유용한 답변입니다. 감사!
davnicwil

7

이것은 다음을 수행합니다.

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

// later
setState({
    ...state,
    username: 'John'
});

4

타사 후크를 사용 중이고 상태를 하나의 객체로 병합하거나 사용할 수없는 useReducer경우 솔루션은 다음을 사용하는 것입니다.

ReactDOM.unstable_batchedUpdates(() => { ... })

Dan Abramov가 추천 한

보기


거의 1 년이 지난 후에도이 기능은 아직 완전히 문서화되지 않은 것으로 보이며 여전히 불안정한 것으로 표시됩니다
Andy

4

복제하기 위해 this.setState클래스 구성 요소에서 병합 동작을, 문서 반응 추천 의 기능 형태로 사용하는 useState오브젝트 확산 - 필요가 없습니다 를 들어 useReducer:

setState(prevState => {
  return {...prevState, loading, data};
});

이제 두 상태가 하나로 통합되어 렌더링주기가 절약됩니다.

이 하나의 상태 개체와 또 다른 장점은 다음 loadingdata이다 의존 상태. 상태가 합쳐지면 잘못된 상태 변경이 더 분명해집니다.

setState({ loading: true, data }); // ups... loading, but we already set data

당신은 더 나은 수있는 일관된 상태를 확인 하기) (1)에 의해 상태 - loading, success, error, 등 - 명시 적으로 사용하여 주 및 2)의 useReducer감속기에 캡슐화 상태 논리 :

const useData = () => {
  const [state, dispatch] = useReducer(reducer, /*...*/);

  useEffect(() => {
    api.get('/people').then(test => {
      if (test.ok) dispatch(["success", test.data.results]);
    });
  }, []);
};

const reducer = (state, [status, payload]) => {
  if (status === "success") return { ...state, data: payload, status };
  // keep state consistent, e.g. reset data, if loading
  else if (status === "loading") return { ...state, data: undefined, status };
  return state;
};

const App = () => {
  const { data, status } = useData();
  return status === "loading" ? <div> Loading... </div> : (
    // success, display data 
  )
}

추신 : 사용자 지정 후크 접두사use( useData대신 getData)로 지정 하십시오 . 또한에 전달 된 콜백은 일 useEffect수 없습니다 async.


1
찬성했습니다! 이것은 React API에서 찾은 가장 유용한 기능 중 하나입니다. 함수를 상태로 저장하려고 할 때 찾았습니다 (함수를 저장하는 것이 이상하다는 것을 알고 있지만 어떤 이유로 기억하지 못함) 예약 된 호출 서명으로 인해 예상대로 작동하지 않았습니다
elquimista

1

답변 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];
};

멋있는! 난 단지 포장 것 setMergedStateuseMemo(() => setMergedState, [])워드 프로세서 말하는대로, 때문에 setState재 - 렌더링 사이에 변경되지 않습니다 React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.는 setState를 기능에 재현되지 이런 식으로 재 렌더링합니다.
tonix

0

useEffect상태 변경을 감지하고 그에 따라 다른 상태 값을 업데이트하는 데 사용할 수도 있습니다.


자세히 설명해 주시겠습니까? 그는 이미 useEffect.
bluenote10 20.11.27
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.