티스토리 뷰

리액트에서 컴포넌트는 JSX를 리턴하는 함수이다. 

function Component(props) {
// jsx를 계산하는 과정

return JSX;
}
render 란 간단히 말해서 `브라우저에 UI를 페인트하는 것`이다.

Q. 그렇다면 리액트에서 render는 무엇일까? 

  • 컴포넌트를 호출하는 것
  • JSX를 리턴하는 것
  • JSX를 브라우저에 페인트하는 것이다.  

Q. 리액트에서 re-render는 무엇일까? 

  • 컴포넌트 렌더링처럼 컴포넌트를 호출(call)하는 것 => 다시 함수 호출
  • JSX를 리턴하는 것 
  • 이전 JSX와 현재 JSX를 비교해서 다른 점(difference)을 페인트하는 것

Q. re-render 발생 시점

  1. state가 변했을 때 => state가 setState()로 값이 변경되면 이후에 컴포넌트가 재호출된다.
  2. 컴포넌트가 재호출되는 시점에서 state는 최신값을 가지고 있다 그렇게 되면 state에 연관된 로직의 연산도 최신값을 기준으로 계산할 수 있다. 
  3. 컴포넌트가 재호출 (Component re-call) => 즉, 이런 순서를 활용해서 불필요한 state를 줄이는 것이 가능하다. 

👩‍🦰 불필요한 state를 없애는 예시 코드 

// 리액트 컴포넌트 내부 
const [form, setForm] = useState({
	email: "",
    password: "",
});

// isEmailValid는 form에 의존하고 있으므로 state로 따로 뺄 필요없고
// state가 변할때마다 컴포넌트가 재호출되기 때문에 항상 최신값을 유지할 수 있다. 
const isEmailValid = form.email.includes("@");


const isDisabled = !(isValid.isEmail && isValid.isPassword);
  • 외에도  useEffect(deps의 의존값)을 같이 사용하면서 불필요한 리렌더링을 트리거하지는 않는 지 유의해야 한다. 
  • 예시로, onChange 함수에서 form state를 setState로 변경하여 리렌더링을 일으켰는데 동시에 useEffect내부에서도 isValid 등의 값을 의존값으로 넣어놨다면 렌더링이 form의 내용이 바뀔때마다 2번씩 발생하게 되기 때문이다. 

Q. 리액트에서 state를 사용하는 이유?

  • UI와 상태(state)를 연동시키기 위함
  • UI란 어떠한 데이터를 보기 편한 형태로 표현한 것 => 리액트는 UI와 연동되어야 하며, 변할 여지가 있는 데이터들을 state라는 형태로 사용할 수 있게 했다. 

위와 같은 이유로 리액트에서 리렌더링이 발생하는 시점은 state가 변했을 때 => 특정 컴포넌트의 state가 변한다면, 해당 컴포넌트와 해당 컴포넌트의 하위에 있는 모든 컴포넌트가 리렌더링 발생

 

Q. 하위 컴포넌트 리렌더링 방지는 어떻게?

  • state가 변한 상위 컴포넌트의 경우 당연히 UI의 변화가 있겠지만 하위 컴포넌트의 경우는 props가 변하지 않았다면 해당 컴포넌트의 UI가 변화하지 않았을 수도 있다. 
  • 이런 경우에는 굳이 새로운 컴포넌트 함수를 호출할 필요 없이 이전에 저장된 결과를 그대로 사용하는 것이 효율적이다. => React.memo 를 활용하자

React.memo:기존 컴포넌트의 UI를 재활용할 지 판단하는 방법

React.memo는 HOC(Higher Order Component, 고차 컴포넌트)이다.

HOC란 컴포넌트를 인자로 받아서, 컴포넌트를 리턴하는 컴포넌트이다.(Wrapper 컴포넌트같은 느낌)

function HOC(Component) {
  /* do something */
return <Component />
}
  • 이전 props와 다음 렌더링 때 사용될 props를 비교하여 차이가 있을 경우에만 리렌더링을 수행한다. 차이가 없다면 이전값을 재사용한다. 이를 통해 불필요한 컴포넌트 리렌더링을 방지할 수 있다. 
  • [props를 비교하는 방식] 기본적으로 shallow compare 하여 판단한다. shallow한 기본 비교 로직을 사용하지 않고 직접 비교하는 로직을 작성하고 싶을 경우 React.memo의 두번째 인자로 커스텀 비교 함수를 넘겨주면 된다. 
  • 두번째 인자로 전달된 함수는 함수의 기본 인자로 이전 props와 새로운 props가 순서대로 전달된다. 이 함수의 반환값이 true일 경우, 이전 결과를 재사용하고 false일 경우, 리렌더링을 수행한다. 
function Component1(props){

const areEqual = (prevProps, nextProps) => {
//비교 로직 return true || false;
}

return JSX;
}

//두번째인자로 커스텀 비교함수 전달
export default React.memo(Component1, areEqual)
주의할 점: 상위 컴포넌트로부터 전달받은 props역시 '객체' 형태(참조형 데이터타입)로 전달받는다. 이 말은 즉, props 객체들은 매 렌더링마다 새롭게 생성된다는 소리이다. 따라서 props 객체 자체를 비교하는 것은 의미가 없다. 
shallow compare을 하는 리액트는 props 객체 안의 각 property들을 Object.is(===) 연산자를 통해서 비교한다. 이 중 하나라도 false가 나온다면 props가 변경되었다고 판단하고 리렌더링을 수행한다.
자바스크립트 데이터 타입에 대해 이해하지 못한다면 memo를 잘못활용하는 상황이 발생한다. 

 

자바스크립트는 기본적으로 비교연산자를 수행할 대 해당 데이터의 메모리 주소를 통해서 일치 여부를 판단한다.

  • 원시형 타입의 경우는 데이터가 매번 새롭게 교체되면서 메모리 주소도 변경되기 때문에 비교가 쉽다. 
  • 참조형 타입의 경우는 내용물이 어떻게 바뀌었는지에 상관없이 해당 객체를 가리키는 메모리 주소는 동일하기 때문에 내용이 변했는지 판단하기 어렵다. 또한, 내용물이 완벽히 일치하더라도 각 객체를 가리키는 메모리 주소가 다르기때문에 두 객체가 동일하지 않다는 결과가 나온다. 내용물을 판단하려면 두 객체 안의 모든 property들을 순회하며 일일이 비교해주어야 한다. deps가 깊어질수록 연산을 수행하기 위한 복잡도는 더 늘어난다. (함수는 기본적으로 호출될 때마다 새로운 참조값을 가진다.)
  • 리액트에서의 shallow compare이란 참조형 데이터 타입(객체 배열 등..)의 업데이트를 비교할 때, 내부 값의 변경 여부가 아닌 객체나 배열의 참조만을 비교하는 것을 말한다.
댓글