React 갈고리를 사용하여 부울 상태 오른쪽 업데이트

최근 코드 검토 과정에서 이러한 구조를 발견했습니다.
const MyComponent = (props) => {
  const [isToggled, setIsToggled] = React.useState(false);
  const toggle = React.useCallback(() => setIsToggled(!isToggled));

  return ...;
};
부울 상태와 전환 방법을 만드는 것은 매우 흔히 볼 수 있는 용례이다.자선망은 기능적으로 100% 정확하다.하지만 성능 면에서는 더 좋을 수 있다.어떻게 개선하는지 봅시다.

그럼 왜?


첫 번째 일은 우선이다. useCallback 이 실현에서 아무것도 하지 않는다.의존 관계 수조를 두 번째 매개 변수로 전달하지 않는 한 useCallback 은 리셋에 대한 동일한 참조를 유지하는 것이 아니라 다음 리셋 성명과 같다.
const toggle = () => setIsToggled(!isToggled);
그 밖에 현재의 실현은 가난한 의존항 규칙을 위반했다. 함수에서 인용한 모든 값도 의존항 수조에 나타나야 한다.이것은 리셋 중의 값이 항상 최신이고 이와 관련된 어떠한 오류도 피하기 위해서이다.
우리 실천에서 이 두 가지가 어떻게 성능에 영향을 미치는지 봅시다.우선 간단한 RendersCounter 구성 요소를 만듭니다. 이 구성 요소는 단일한 onClick 도구를 사용합니다.그러면 어셈블리가 렌더링되는 횟수가 계산됩니다.
import React from 'react';

const RendersCounter = ({ onClick }) => {
  const rendersCountRef = React.useRef(0);
  rendersCountRef.current += 1;

  return (
    <div>
      <span>
        RendersCounter rendered <b>{rendersCountRef.current}</b> time(s)
      </span>
      <button style={{ marginLeft: '10px' }} onClick={onClick}>
        toggle
      </button>
    </div>
  )
};

export default React.memo(RendersCounter);
RendersCounterReact.memo으로 소포하십시오.우리가 진행할 최적화는 하위 구성 요소가 순수한 구성 요소일 때만 유효하다. 이것은 React.PureComponent의 실례이고 React.memo로 포장된 기능 구성 요소이거나 shouldComponentUpdate 또는 다른 방식으로 인용 등식 렌더링 최적화를 한다.서브어셈블리에 대해 이러한 작업을 수행하지 않은 경우 리셋을 수행하는 방식에 관계없이 상위 어셈블리를 다시 표시할 때마다 다시 표시됩니다.
이제 이 구성 요소를 사용해서 의존항을 useCallback 에게 전달하지 않으면 무슨 일이 일어날지 봅시다.나는 두 개의 독립된 상태 처리 프로그램을 만들 것이다. 하나는 볼 상태이고, 다른 하나는 무작위 수를 저장하는 데 쓰인다.
const BasicBooleanState = () => {
  const [isToggled, setIsToggled] = React.useState(false);
  const toggle = React.useCallback(() => setIsToggled(!isToggled));

  const [randomNumber, setRandomNumber] = React.useState(Math.random());
  const generateRandomNumber = React.useCallback(
    () => setRandomNumber(Math.random()),
    [],
  );

  return (
    <div>
      <div>
        Current random number is <b>{randomNumber}</b>
        <button style={{ marginLeft: '10px' }} onClick={generateRandomNumber}>
          regenerate
        </button>
      </div>
      <div>
        Boolean is set to <b>{String(isToggled)}</b>.
      </div>
      <RendersCounter onClick={toggle} />
    </div>
  );
}
RendersCounter 부울 상태가 변경되지 않더라도 다시 렌더링!

앞에서 말한 바와 같이 toggle의 현재useCallback는 일반적인 화살표 함수 성명에 해당한다.렌더링할 때마다 다시 만들어지기 때문에 RendersCounter 다른 onClick 도구를 참조하여 필요하지 않을 때 다시 렌더링할 수 있습니다.
Try it yourself

부족한 의존 항목 복구


React 문서 설명:

Every value referenced inside the function should also appear in the dependencies array


이 규칙을 따르지 않으면 콜백에 오래된 값이 나타날 수 있습니다.toggle 콜백에는 isToggledsetIsToggled 두 개의 외부 값이 사용됩니다.그것들을 useCallback의 의존 관계 그룹에 넣읍시다.
const BasicBooleanState = () => {
  const [isToggled, setIsToggled] = React.useState(false);

  // here we added [isToggled, setIsToggled] as a second parameter
  const toggle = React.useCallback(
    () => setIsToggled(!isToggled),
    [isToggled, setIsToggled],
  );

  const [randomNumber, setRandomNumber] = React.useState(Math.random());
  const generateRandomNumber = React.useCallback(
    () => setRandomNumber(Math.random()),
    [],
  );

  return (
    <div>
      <div>
        Current random number is <b>{randomNumber}</b>
        <button style={{ marginLeft: '10px' }} onClick={generateRandomNumber}>
          regenerate
        </button>
      </div>
      <div>
        Boolean is set to <b>{String(isToggled)}</b>.
      </div>
      <RendersCounter onClick={toggle} />
    </div>
  );
}
현재 RendersCounter 랜덤 수 변경 시 다시 렌더링되지 않습니다!우리는 리셋이 isToggled 또는 setIsToggled 변경될 때만 업데이트된다고 말했기 때문에 isToggled 변경이 없으면 인용에 있어서 같다.

그러나 RendersCounter 에서 부울 상태를 전환하면 다시 렌더링됩니다.isToggled에 변화가 생겼고 useCallback 의존항수조의 일부이기 때문에 의미가 있다.

Try it yourself

리셋 최적화


리셋 toggle 문제를 해결하기 위해서는 직접적인 의존 isToggled 을 피하는 방법이 필요하지만, 리셋에는 실제 값이 있습니다.다음은 useRef 지원 서비스입니다.우리는 인용을 한 번만 만들고 isToggled 변경할 때 그 값을 업데이트할 수 있습니다.그리고 우리는 의존항수 그룹의 인용으로 isToggled 대체하고 그 자체를 리셋합니다. 이렇게!
사용자 정의 갈고리를 만듭니다. 현재 볼 상태와 전환 방법을 되돌려줍니다. 이 방법은 볼 값을 변경하고 있으며 영원히 다시 만들어지지 않습니다.
// it might be a project-level reusable hook
const useToggle = (initialState) => {
  const [isToggled, setIsToggled] = React.useState(initialState);
  const isToggledRef = React.useRef(isToggled);

  // put [isToggledRef, setIsToggled] into the useCallback's dependencies array
  // these values never change so the calllback is not going to be ever re-created
  const toggle = React.useCallback(
    () => setIsToggled(!isToggledRef.current),
    [isToggledRef, setIsToggled],
  );

  // keep the value in isToggledRef actual
  // when isToggled changes, isToggledRef is updated accordingly
  React.useEffect(
    () => {
      isToggledRef.current = isToggled;
    },
    [isToggled],
  );

  return [isToggled, toggle];
}
우리는 isToggled 대신 isToggledRef 리셋을 만들기 위해 toggle 을 사용합니다.isToggledRefsetIsToggled 은 한 번만 생성됩니다. React는 변경되지 않고 인용에 렌더링을 통해 동일합니다.이것은 리셋 toggle 을 다시 만들 이유가 없다는 것을 의미한다.isToggledRef의 값이 최신 값인지 확인하기 위해서, 우리는 useEffect 의존항 수조의 단일 isToggled 의존항과 함께 사용할 것이다.isToggled가 변경된 경우에만 실행됩니다.
우리가 만든 갈고리를 사용할 때가 되었다.
const OptimizedBooleanState = () => {
  const [isToggled, toggle] = useToggle(false);

  const [randomNumber, setRandomNumber] = React.useState(Math.random());
  const generateRandomNumber = React.useCallback(
    () => setRandomNumber(Math.random()),
    [],
  );

  return (
    <div>
      <div>
        Current random number is <b>{randomNumber}</b>
        <button style={{ marginLeft: '10px' }} onClick={generateRandomNumber}>
          regenerate
        </button>
      </div>
      <div>
        Boolean is set to <b>{String(isToggled)}</b>.
      </div>
      <RendersCounter onClick={toggle} />
    </div>
  );
}
현재RenderCounter는 영원히 다시 렌더링되지 않습니다!

Try it yourself

현대화하다


설명에 설명된 대로 이 경우에는 REF를 사용할 필요가 없습니다.이상적인 행위는 기능 상태 업데이트 프로그램을 통해 실현될 수 있다.현재 상태를 첫 번째 매개 변수로 하는 함수를 전달해야 합니다. setIsToggled이것은 확실히 갈고리의 방식을 더욱 명확하게 한다.
setIsToggled(state => !state);
다음은 업데이트 버전useToggle 연결 모양입니다.
const useToggle = (initialState) => {
  const [isToggled, setIsToggled] = React.useState(initialState);

  // put [setIsToggled] into the useCallback's dependencies array
  // this value never changes so the callback is not going to be ever re-created
  const toggle = React.useCallback(
    () => setIsToggled(state => !state),
    [setIsToggled],
  );

  return [isToggled, toggle];
}
Try it yourself

결론


마지막으로 useCallback는 최적화에 관한 것이다.리셋을 순수한 화살표 함수로 표시하면 코드가 정상적으로 작동할 수 있기 때문에 최적화와 코드 간결성 사이의 균형을 찾아야 합니다.
React-Hooks API 기능이 강력합니다.그것은 당신이 명확한 성명 코드를 작성할 수 있도록 합니다.만약 적절하게 처리된다면, 그것은 또한 응용 프로그램의 성능을 향상시킬 수 있다.
읽어주셔서 감사합니다!

좋은 웹페이지 즐겨찾기