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

처음으로 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 };
};

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



답변

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>


답변

이것은 또한 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
}


답변

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

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


답변

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

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

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


답변

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

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

Dan Abramov가 추천 한

보기


답변

복제하기 위해 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.


답변

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