React : Rendering vs Re-Rendering & React.memo + useCallback

[ React 에서 페이지가 렌더링 되는 시점 ]

① 최초 페이지가 처음 열릴때 , JSX 안의 상태(변수) , 함수 그리고 컴포넌트 등이 처음 Rendering 이 된다
② 상태(변수)가 1개라도 변경된다면, 현재 전체 페이지의 함수 및 변수등이 모두 Re-Rendering 된다
    (useStae , useReducer  로 설정된 여러개의 변수가 있을때, 그중 1개라도 값이 변경될 경우 해당)
③ 부모 컴포넌트에서 Re-Rendering 이 일어난다면 자식 컴포넌트들도 모두 다시 Re-Rendring 된다
④ props 가 변경되면(함수재성성 또는 값) 모두 다시 Re-Rendering  한다.
    (props로 전달되는 변수들 또는 함수가 변경 될 경우(함수재생성 – 함수안의 변수의 변경으로 함수가 다시 만들어질경우)
 forceUpate 함수가 실행될때 Re-Rendering 이 일어난다
   (클래스 컴포넌트방식으로 코딩 할 경우 해당됨)


[ Re-Rendering 범위 ]

App 안에 모든 코드들이 렌더링 & 리렌더링됨

function App()
{
     //  App 안에 있는 모든 것들이 Re-Rendering 의 대상
     – useState (useReducer) 상태들

     – function userFunction () {      } // 사용자 함수의 재생성
   
   return (

     – 자식 컴포넌트들 & 컨트롤들
        <div>
               <UserComponent1 value={val1}  onClick={clickMyFunction} data={Info}></UserComponent1>
               <UserComponent2 value={val1}  onClick={clickMyFunction} data={Info}></UserComponent2>
               <button onClick={clickBtn}>클릭</button>
         </div>
     )
}

 

< App.js >

import React from ‘react’
import {useState} from ‘react’

import SubComp1 from ‘./components/subComponent1’;
import SubComp2 from ‘./components/subComponent2’;
import SubComp3 from ‘./components/subComponent3’;

function App()
{
     console.log(“★ App render…………………………”)

     const [val1 , setVal1] = useState(”);
     const [val2 , setVal2] = useState(”)
     const [val3 , setVal3] = useState(”)

     const onChangeVal1 = (e) => { setVal1(e.target.value) }
     const onChangeVal2 = (e) => { setVal2(e.target.value) }
     const onChangeVal3 = (e) => { setVal3(e.target.value) }

     return(
        <div>
            <SubComp1 value={val1} onChangeEvent={onChangeVal1}></SubComp1>
            <SubComp2 value={val2} onChangeEvent={onChangeVal2}></SubComp2>
            <SubComp3 value={val3} onChangeEvent={onChangeVal3}></SubComp3>
        </div>
    )
}
export default App;

[
SubComponent1.js ]

import React from ‘react’
function SubComponent1(props)
{
    console.log(“★ Render SubComponent 1 …………”)   
    return <div>SubComponent1 [val1] : <input type=”text” onChange={props.onChangeEvent}></input>  {props.value}</div>
}
export default SubComponent1;

[ SubComponent2.js ]
import React from ‘react’
function SubComponent1({value , onChangeEvent})
{
    console.log(“★ Render SubComponent 2 …………”)   
    return <div>SubComponent2 [val2] : <input type=”text” onChange={onChangeEvent}></input>  {value}</div>
}
export default SubComponent3;

[ SubComponent3.js ]
import React from ‘react’
function SubComponent1({value , onChangeEvent})
{
    console.log(“★ Render SubComponent 3 …………”)   
    return <div>SubComponent3 [val3] : &lt;input type=”text” onChange={onChangeEvent}></input>  {value}</div>
}
export default SubComponent3;
 
초기 렌더링  –  최초 페이지 로딩하면서 전체 렌더링 됨
console.log(“★ App render…………………………”)   // 최초 렌더링
console.log(“★ Render SubComponent 1 …………”)   // 최초 렌더링
console.log(“★ Render SubComponent 2 …………”)   // 최초 렌더링
console.log(“★ Render SubComponent 3 …………”)   // 최초 렌더링

페이지가 첫 로딩되면서 모두 렌더링되었다.. 이제 아래 예시를 통해  상태변수를 변경하여 리-렌더링 일으켜보자

* 리렌더링 
– 상태(변수)가 1개라도 변경된다면, 현재 페이지의 함수 및 변수등이 모두 Re-Rendering 된다

* <Subcomponent1  컴포넌트의  onChangeEvent 이벤트를 발생해서 setVal1(e.target.value)  상태 변경  
(상태(변수)가 1개라도 변경된다면, 현재 페이지의 함수 및 상태(관련없는 다른상태 모두 포함)등이 Re-Rendering 된다)
const onChangeVal1 = (e) => { setVal1(e.target.value) }
<SubComp1 value={val1} onChangeEvent={onChangeVal1}></SubComp1> 

[출력로그]

console.log(“★ App render…………………………”)          //  렌더링 발생 – 정상
console.log(“★ Render SubComponent 1 …………”)    //  렌더링 발생 – 정상
console.log(“★ Render SubComponent 2 …………”)    // 렌더링 발생 – 불필요함 
console.log(“★ Render SubComponent 3 …………”)    // 렌더링 발생 – 불필요함 

[ React.memo ]  컴포넌트 memoization 하기

——————————————————————————————————————————————————————
React.memo 함수는 컴포넌트를 memoizaion 하여 부모에서 호출시 props 가 변경되지 않으면 새로 렌더링 하지 않는다.
( ★★★ props 가 변경되는 사항은 props 로 전달되는 상태 변수의 값의 변경 또는 함수의 재생성 등)

→  SubComponent1.js  ,SubComponent2.js  , SubComponent3.js 컴포넌트 export 시 함수 React.memo 적용

export default React.memo(SubComponent1);
export default React.memo(SubComponent2);
export default React.memo(SubComponent3);

 React.memo 함수로 memoization 했으니, 다시 테스트 ,,,,
 (SubComponent 1 만 리렌더링 되어야 원하는 결과이다.)

[출력로그]

console.log(“★ App render…………………………”)          // 렌더링 발생 – 정상
console.log(“★ Render SubComponent 1 …………”)    // 렌더링 발생 – 정상
console.log(“★ Render SubComponent 2 …………”)    // 렌더링 발생 – 불필요함 
console.log(“★ Render SubComponent 3 …………”)    // 렌더링 발생 – 불필요함 


오잉?  분명히 위에서 ,  아래 2개 컴포넌트도 React.memo 함수를 적용했는데 왜? 다시 리-렌더링 되었다 왜 일까?

export default React.memo(SubComponent2);
export default React.memo(SubComponent3);


——————————————————————————————————————————————————————
다시, 살펴보면 App.js 에서 상태 변수가 setVal1 를 통해 val1 상태가 변하면서App() 전체가 리렌더링 되고 App() 이 가지고 있던 → 함수 및 자식 컴포넌트인 SubComponent1,SubComponent2,SubComponent3 모두 리-렌더링 되었다.

여기서 SubComponent1,SubComponent2,SubComponent3 에 React.memo 를 통해 ,
각 컴포넌트가 “자신만의 props” 가 변경될대때만 재렌더링 되도록 설정 하였다.

SubComp1 을 통해 setVal1(e.target.value)  를 하여  Subcomponent1이 사용하는 val1 이 변경되었으니
SubComponent1 의 props 인 val1 이 변경되었으므로, ★ Render SubComponent 1 ……. 호출되는 것은 정상이다.
( Props 확인 <SubComp1 value={val1} onChangeEvent={onChangeVal1}></SubComp1> )

console.log(“★ App render…………………………”)          // 렌더링 발생 – 정상
console.log(“★ Render SubComponent 1 …………”)    // 렌더링 발생 – 정상

하지만 , setVal1(e.target.value) 와 관계없는,
★ Render SubComponent 2 …….. , ★ Render SubComponent 3 ……..   까지 호출되어 렌더링이 되었다.
console.log(“★ Render SubComponent 2 …………”)    // 렌더링 발생 – 불필요함 
console.log(“★ Render SubComponent 3 …………”)    // 렌더링 발생 – 불필요함 

★ Render SubComponent 2 …….. , ★ Render SubComponent 3  렌더링된 이유는?
val1 이 변경되면서 App() 컴포넌트를 다시 실행하고 실행하는 과정에서, “컴포넌트 및 함수가 모두 새로 생성 “
되었기때문에이다.
(각 컴포넌트에 넘겨질 함수들의 코드는 그대로 이지만 , App() 컴포넌트가 다시 실행되면서  함수들이로 모두 재생성되어 다른 참조를 가지고있다.)

그래서 컴포넌트의 props가 변경되었다고 인식함  ★★★

const onChangeVal1 = (e) => { setVal1(e.target.value) }
const onChangeVal2 = (e) => { setVal2(e.target.value) }
const onChangeVal3 = (e) => { setVal3(e.target.value) }
결과적으로 onChangeVal1 함수를 통해 setVal1val1 상태를 변경하였기 때문에 ,

아래 자식 컴포넌트의 변경된 props를 살펴보면 ,

<SubComp1 value={val1} onChangeEvent={onChangeVal1}></SubComp1> // val1 변경 O ,  onChangeVal1 변경 O
<SubComp2 value={val2} onChangeEvent={onChangeVal2}></SubComp2> // val2 변경 XonChangeVal2 변경 O
<SubComp3 value={val3} onChangeEvent={onChangeVal3}></SubComp3> // val2 변경 XonChangeVal3 변경 O

결국 리렌더링 조건의 ②,③,④ 조건에 모두 만족하여 리-렌더링이 발생하였다.
★ Render SubComponent 1 ……..  : val1 이 변경 , onChangeVal1 함수가 재생성 되면서 props 가 변경되어 리-렌더링
★ Render SubComponent 2 …….. , ★ Render SubComponent 3 : val2 와 val3는 변경되지 않았지만,
onChangeVal2 , onChangeVal3  함수가 재생성 되면서  props 가 변경되어 리-레더링된 것이다.


< 아래 리-렌더링 조건을 다시 한번 살펴보자>
②상태(변수)가 1개라도 변경된다면, 현재 페이지의 함수 및 변수등이 모두 Re-Rendering 된다
   (useStae , useReducer  로 설정된 여러개의 변수가 있을때, 그중 1개라도 값이 변경될 경우 해당)
③ 부모 컴포넌트에서 Re-Rendering 이 일어난다면 자식 컴포넌트들도 모두 다시 Re-Rendring 된다
④ props 또는 props의 전달되는 속성 ,값 등이 변경되면, 모두 다시 Re-Rendering  한다.
  (props 로 전달되는 변수들 또는 함수가 변경 될 경우(함수재생성 – 함수안의 변수의 변경으로 함수가 다시 만들어질경우)


[ useCallback ] – 함수 memoization 하기

App() 컴포넌트 안에서 상태가 변경되더라도 , ★ Render SubComponent 2 …….. , ★ Render SubComponent 3  와 관련없는 상태라면 리-렌더링 되지 않게 하려면, useCallback 을 이용하여 함수자체를 memoization 해야함

 const 함수명 = useCallback( ()=> {

} , [의존성배열1, 의존성배열2,,,,,,] )
const function = () => {

}

const function = useCallback ( () => {

} , [ ] )

함수 전체를 useCallback 으로 감싸고 , [의존성배열] 에  이 함수가 리-렌더링시 새롭게 생성 하기위해 의존할 상태변수들을
나열해주면,  나열한 상태 변수들 중 한 개 라도 값이 변하면 , 리렌더링시 함수를 재생성 하게된다.

※ 의존성 배열을, 빈 배열로 [ ] 설정 할 경우, 최초 로딩시 렌더링되고 이후로는 절대 리-렌더링 되지 않는다.

의존성 배열을 지정 해야 하는 이유는,   함수가 메모리에 저장 될 때 상태 값까지 그대로 저장되므로,
변경되는 상태 값을 사용 할 수가 없다.

const [ number , setNumber] = useState(10);

const myfunction = useCallback((){
     let mySum = number + 200;

     console.log(mySum)
}, [ ]);
setNumber(200);

<button onClick={myfunction}> 합계 </butotn>

1) number 값을 기본  10으로 설정하고,

2) myfunction 을 memoizaion 하고, 최초 렌더링시만 함수생성을 위해 의존성 배열은 [ ] 로 입력

3) number 를 200 으로 변경

4) 합계를 실행하면 myfunction 호출되면서  200 + 200 이 아니라, 저장해 놓은 함수가 호출되면서 10 + 200 이 되어 210 출력

[ useCallback 으로 함수 재생성 ]

재생성되는 함수의 리렌더링을 막기위해서는 useCallback 을 통해 함수를 memoization 한다.

const onChangeVal1 = useCallback ( (e) => { setVal1(e.target.value) } , [ ] )
const onChangeVal2 = useCallback ( (e) => { setVal2(e.target.value) } , [ ] )
const onChangeVal3 = useCallback ( (e) => { setVal3(e.target.value) } , [ ] )

의존성 배열에 [ ] 빈 배열로 설정하면 최초 렌더링시만 함수를 생성하며, 그뒤로는 리-렌더링 하지않음


[결과]
console.log(“★ App render…………………………”)          // 정상  렌더링
console.log(“★ Render SubComponent 1 …………”)    // 정상  렌더링
console.log(“★ Render SubComponent 2 …………”)    // 리-렌더링 발생안함 X
console.log(“★ Render SubComponent 3 …………”)    // 리-렌더링 발생안함 X

<SubComp1 value={val1} onChangeEvent={onChangeVal1}></SubComp1> // val1 변경 O ,  onChangeVal1 변경 X
<SubComp2 value={val2} onChangeEvent={onChangeVal2}></SubComp2> // val2 변경 XonChangeVal2 변경 X
<SubComp3 value={val3} onChangeEvent={onChangeVal3}></SubComp3> // val2 변경 XonChangeVal3 변경 X


onChangeVal1, onChangeVal2 ,onChangeVal3 모두 저장된 함수가 사용되며,  재생성 되지 않았기때문에, 변경이없다.

★ 참고로 아래 예제처럼 함수를 memoization 할때, 의존성 배열에 나열된 상태변수가,  함수안에서 사용 될때만 나열하고
그렇치 않은 경우 [ ] 빈 배열을 통해 최초 렌더링시만 생성되게 하자.

const myFunction = useCallback( () => {
      let sum = val1 + 200;
      console.log(sum);
} , [val1]);

 

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다