VUE3.0을 빠르게 시작하는 방법: 시작하기
이 튜토리얼에서는 React에서 Memoization을 구현하는 방법을 알아봅니다. 메모화는 함수 호출 결과를 캐시하고 다시 필요할 때 캐시된 결과를 반환함으로써 성능을 향상시킵니다.
우리는 다음 내용을 다룰 것입니다:
이 글에서는 여러분이 React의 클래스와 함수 구성 요소에 대한 기본적인 이해가 있다고 가정합니다.
이러한 주제를 확인하려면 공식 React 문서 구성 요소 및 소품
https://reactjs.org/docs/comComponents-and-props.html
을 확인하세요.
React의 Memoization에 대한 세부 사항을 논의하기 전에 먼저 React가 가상 DOM을 사용하여 UI를 렌더링하는 방법을 살펴보겠습니다.
일반 DOM은 기본적으로 트리 형태로 유지되는 노드 집합을 포함합니다. DOM의 각 노드는 UI 요소를 나타냅니다. 애플리케이션에서 상태 변경이 발생할 때마다 해당 UI 요소와 모든 하위 요소에 해당하는 노드가 DOM 트리에서 업데이트되고 UI 다시 그리기가 트리거됩니다.
효율적인 DOM 트리 알고리즘의 도움으로 노드 업데이트가 더 빨라지지만 다시 그리기는 느리고 DOM에 UI 요소 수가 많으면 성능에 영향을 미칠 수 있습니다. 따라서 React에는 가상 DOM이 도입되었습니다.
이는 실제 DOM의 가상 표현입니다. 이제 애플리케이션 상태에 변화가 있을 때마다 React는 실제 DOM을 직접 업데이트하지 않고 새로운 가상 DOM을 생성합니다. 그런 다음 React는 이 새로운 가상 DOM을 이전에 생성된 가상 DOM과 비교하고 차이점을 찾은 다음(번역자 주: 업데이트해야 하는 노드를 찾습니다) 다시 그립니다.
이러한 차이점을 기반으로 가상 DOM은 실제 DOM을 보다 효율적으로 업데이트할 수 있습니다. 이는 가상 DOM이 단순히 UI 요소와 모든 하위 요소를 업데이트하는 것이 아니라 실제 DOM에서 필요하고 최소한의 변경 사항만 효과적으로 업데이트하기 때문에 성능을 향상시킵니다.
이전 섹션에서는 React가 가상 DOM을 사용하여 DOM 업데이트 작업을 효율적으로 수행하여 성능을 향상시키는 방법을 살펴보았습니다. 이 섹션에서는 성능을 더욱 향상시키기 위해 Memoization을 사용해야 하는 이유를 설명하는 예를 소개합니다.
count
라는 변수를 증가시키는 버튼이 포함된 상위 클래스를 생성하겠습니다. 또한 상위 구성 요소는 하위 구성 요소를 호출하고 매개 변수를 전달합니다. 또한 render
메서드에 console.log()
문을 추가했습니다.
//Parent.js 클래스 부모는 React.Component를 확장합니다. 생성자(소품) { 슈퍼(소품); this.state = { 개수: 0 }; } 핸들클릭 = () => { this.setState((prevState) => { return { 개수: prevState.count + 1 }; }); }; 렌더링() { console.log("상위 렌더링"); 반품 ( <div className="앱"> <button onClick={this.handleClick}>증분</button> <h2>{this.state.count}</h2> <아이 이름={"joe"} /> </div> ); } } 기본 상위 항목 내보내기;
이 예제의 전체 코드는 CodeSandbox에서 볼 수 있습니다.
상위 구성 요소가 전달한 매개 변수를 받아들이고 이를 UI에 표시하는 Child
클래스를 만듭니다.
//Child.js 클래스 Child는 React.Component를 확장합니다. 렌더링() { console.log("하위 렌더링"); 반품 ( <div> <h2>{this.props.name}</h2> </div> ); } } 기본 하위 내보내기;
상위 구성 요소에서 버튼을 클릭할 때마다 count
값이 변경됩니다. 상태가 변경되었으므로 상위 구성 요소의 render
메서드가 실행됩니다.
하위 구성 요소에 전달된 매개 변수는 상위 구성 요소가 다시 렌더링될 때마다 변경되지 않으므로 하위 구성 요소를 다시 렌더링해서는 안 됩니다. 그러나 위 코드를 실행하고 count
계속 증가시키면 다음과 같은 출력을 얻습니다
. 하위 렌더링 상위 렌더링 하위 렌더링 상위 렌더링 하위 렌더링
이 샌드박스에서 위의 예를 시험해보고 콘솔 출력을 볼 수 있습니다.
출력에서 상위 구성 요소가 다시 렌더링되면 하위 구성 요소에 전달된 매개 변수가 변경되지 않은 경우에도 하위 구성 요소가 다시 렌더링되는 것을 볼 수 있습니다. 이로 인해 하위 구성 요소의 가상 DOM이 이전 가상 DOM과 비교 검사를 수행하게 됩니다. 하위 구성 요소에는 변경 사항이 없고 다시 렌더링 시 모든 props는 변경되지 않으므로 실제 DOM은 업데이트되지 않습니다.
실제 DOM이 불필요하게 업데이트되지 않는다는 점은 확실히 성능상의 이점이지만, 하위 컴포넌트에 실제 변경 사항이 없는 경우에도 새로운 가상 DOM이 생성되고 차이점 검사가 수행되는 것을 볼 수 있습니다. 작은 React 구성 요소의 경우 이 성능 비용은 미미하지만 대형 구성 요소의 경우 성능에 미치는 영향이 클 수 있습니다. 가상 DOM의 이러한 재렌더링 및 차등 확인을 방지하기 위해 Memoization을 사용합니다.
React 애플리케이션의 맥락에서 메모화는 상위 컴포넌트가 다시 렌더링될 때마다 하위 컴포넌트가 props가 변경될 때만 다시 렌더링하는 수단입니다. 하위 구성 요소가 의존하는 props에 변경 사항이 없으면 render 메서드를 실행하지 않고 캐시된 결과를 반환합니다. render 메소드가 실행되지 않기 때문에 가상 DOM 생성 및 차등 확인이 없으므로 성능이 향상됩니다.
이제 불필요한 다시 렌더링을 방지하기 위해 클래스 및 함수 구성 요소에 메모를 구현하는 방법을 살펴보겠습니다.
클래스 컴포넌트에서 메모 기능을 구현하기 위해 React.PureComponent를 사용합니다. React.PureComponent
state
와 props
얕은 비교를 수행하고 소품이나 상태가 변경되는 경우에만 React 구성 요소를 다시 렌더링하는 shouldComponentUpdate()를 구현합니다.
하위 구성 요소를 다음과 같은 코드로 변경합니다.
//Child.js class Child는 React.PureComponent를 확장합니다. { // 여기서는 React.Component를 React.PureComponent로 변경합니다. 렌더링() { console.log("하위 렌더링"); 반품 ( <div> <h2>{this.props.name}</h2> </div> ); } } 내보내기 기본 하위;
이 예제의 전체 코드가 이 샌드박스에 표시됩니다.
상위 구성 요소는 변경되지 않고 그대로 유지됩니다. 이제 상위 구성 요소의 count
늘리면 콘솔의 출력은 다음과 같습니다.
상위 렌더링 하위 렌더링 상위 렌더링 상위 렌더링
첫 번째 렌더링에서는 상위 구성 요소와 하위 구성 요소 모두의 render
메서드를 호출합니다.
count
늘린 후 다시 렌더링할 때마다 상위 구성 요소의 render
함수만 호출됩니다. 하위 구성요소는 다시 렌더링되지 않습니다.
Function 컴포넌트에 Memoization을 구현하기 위해 React.memo()를 사용하겠습니다. React.memo()
불필요한 재렌더링을 피하기 위해 PureComponent
와 유사한 작업을 수행하는 고차 컴포넌트(HOC)입니다.
함수 구성 요소에 대한 코드는 다음과 같습니다.
//Child.js 내보내기 함수 Child(props) { console.log("하위 렌더링"); 반품 ( <div> <h2>{props.name}</h2> </div> ); } import default React.memo(Child); // 여기서는 Memoization을 구현하기 위해 하위 컴포넌트에 HOC를 추가하고
아래와 같이 상위 컴포넌트를 함수 컴포넌트로 변환합니다.
//Parent.js 기본 함수 내보내기 Parent() { const [count, setCount] = useState(0); const handlerClick = () => { setCount(개수 + 1); }; console.log("상위 렌더링"); 반품 ( <div> <button onClick={handleClick}>증분</button> <h2>{개수}</h2> <아이 이름={"joe"} /> </div> ); }
이 예제의 전체 코드는 이 샌드박스에서 볼 수 있습니다.
이제 상위 구성 요소의 count
늘리면 다음이 콘솔에 출력됩니다.
상위 렌더링 하위 렌더링 상위 렌더링 상위 렌더링 부모 렌더링React.memo()
위의 예에서 자식 컴포넌트에 React.memo()
HOC를 사용할 때 부모 컴포넌트가 다시 렌더링되더라도 자식 컴포넌트는 다시 렌더링되지 않는 것을 볼 수 있습니다.
그러나 주의해야 할 작은 점은 하위 구성 요소에 함수를 매개 변수로 전달하면 하위 구성 요소는 React.memo()
사용한 후에도 다시 렌더링된다는 것입니다. 이에 대한 예를 살펴보겠습니다.
아래와 같이 상위 구성 요소를 변경하겠습니다. 여기서는 핸들러 함수를 추가하고 이를 하위 구성 요소에 매개 변수로 전달합니다.
//Parent.js 기본 함수 내보내기 Parent() { const [count, setCount] = useState(0); const handlerClick = () => { setCount(개수 + 1); }; const 핸들러 = () => { console.log("handler"); // 여기의 핸들러 함수는 하위 구성 요소에 전달됩니다.}; console.log("상위 렌더링"); 반품 ( <div className="앱"> <button onClick={handleClick}>증분</button> <h2>{개수}</h2> <자식 이름={"joe"} childFunc={handler} /> </div> ); }
하위 구성 요소 코드는 그대로 유지됩니다. 하위 구성 요소의 상위 구성 요소에서 전달된 함수는 사용하지 않습니다.
//Child.js 내보내기 함수 Child(props) { console.log("하위 렌더링"); 반품 ( <div> <h2>{props.name}</h2> </div> ); } import default React.memo(Child);
이제 상위 구성 요소의 count
늘리면 전달된 매개 변수에 변경 사항이 없더라도 동시에 하위 구성 요소를 다시 렌더링하고 다시 렌더링합니다.
그렇다면 하위 구성 요소가 다시 렌더링되는 원인은 무엇입니까? 대답은 상위 구성 요소가 다시 렌더링될 때마다 새로운 handler
함수가 생성되어 하위 구성 요소에 전달된다는 것입니다. 이제 handle
함수는 다시 렌더링될 때마다 다시 생성되므로 하위 구성 요소는 props의 얕은 비교를 수행할 때 handler
참조가 변경되었음을 발견하고 하위 구성 요소를 다시 렌더링합니다.
다음으로 이 문제를 해결하는 방법을 다루겠습니다.
useCallback()
사용하위 구성 요소 재렌더링의 주요 문제는 handler
함수가 다시 생성되어 하위 구성 요소에 전달된 참조가 변경된다는 것입니다. 따라서 이러한 중복을 방지할 수 있는 방법이 필요합니다. handler
함수가 다시 생성되지 않으면 handler
함수에 대한 참조가 변경되지 않으므로 하위 구성 요소가 다시 렌더링되지 않습니다.
상위 구성 요소가 렌더링될 때마다 함수를 다시 생성할 필요가 없도록 하기 위해 useCallback()이라는 React Hook을 사용합니다. Hook은 React 16에서 도입되었습니다. Hooks에 대해 더 자세히 알아보려면 React의 공식 Hooks 문서를 확인하거나 `React Hooks: How to Get Started & Build Your Own'을 확인하세요.
useCallback()
후크는 콜백 함수와 종속성 목록이라는 두 가지 매개변수를 사용합니다.
다음은 useCallback()
의 예입니다
.
const handlerClick = useCallback(() => { //뭔가를 해라 }, [x,y]);
여기서는 handleClick()
함수에 useCallback()
추가합니다. 두 번째 매개변수 [x, y]
는 빈 배열, 단일 종속성 또는 종속성 목록일 수 있습니다. handleClick()
함수는 두 번째 매개변수에 언급된 종속성이 변경될 때마다 다시 생성됩니다.
useCallback()
에 언급된 종속성이 변경되지 않은 경우 첫 번째 인수로 언급된 콜백 함수의 메모화된 버전이 반환됩니다. 하위 구성 요소에 전달된 핸들러에서 useCallback()
후크를 사용하도록 상위 구성 요소를 변경합니다.
//Parent.js 기본 함수 내보내기 Parent() { const [count, setCount] = useState(0); const handlerClick = () => { setCount(개수 + 1); }; const handler = useCallback(() => { // 핸들러 함수에는 useCallback()을 사용합니다. console.log("처리기"); }, []); console.log("상위 렌더링"); 반품 ( <div className="앱"> <button onClick={handleClick}>증분</button> <h2>{개수}</h2> <자식 이름={"joe"} childFunc={handler} /> </div> ); }
하위 구성 요소 코드는 그대로 유지됩니다.
이 예제의 전체 코드는 이 샌드박스에 있습니다.
위 코드의 상위 구성 요소에서 count
늘리면 다음 출력을 볼 수 있습니다.
상위 렌더링 하위 렌더링 상위 렌더링 상위 렌더링 상위 렌더링
상위 구성 요소의 handler
에 대해 useCallback()
후크를 사용하기 때문에 상위 구성 요소가 다시 렌더링될 때마다 handler
함수가 다시 생성되지 않으며 handler
의 Memoization 버전이 하위 구성 요소에 전달됩니다. 하위 구성 요소는 얕은 비교를 수행하고 handler
함수에 대한 참조가 변경되지 않았음을 확인하므로 render
메서드를 호출하지 않습니다.
Memoization은 상태나 소품이 변경되지 않은 경우 구성 요소를 불필요하게 다시 렌더링하는 것을 방지하여 React 애플리케이션의 성능을 향상시키는 좋은 수단이라는
모든 구성 요소에 Memoization을 추가하는 것을 고려할 수 있지만 이것이 반드시 고성능 React 구성 요소를 구축하는 방법은 아닙니다. 메모화는 구성요소가 다음과 같은 경우에만 사용해야 합니다.
이 튜토리얼에서 우리는 다음을 이해했습니다.
React.memo()
, 클래스 컴포넌트는 React.PureComponent
통해 React에서 Memoization을 구현합니다.React.memo()
사용한 후에도 하위 컴포넌트가 재설정되는 것을 보여줍니다.useCallback()
React Memoization에 대한 소개가 도움이 되기를 바랍니다.
원본 주소: https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/
원저자: Nida Khan