[TIL] 2022/03/10

yongkini ·2022년 3월 9일
0

Today I Learned

목록 보기
119/173

Today I Learned

React에서 가장 먼저 실행되는 파일은 Index.js이며, 그안의 ReactDOM.render() 메서드는 최상위 컴포넌트에 대해서만 실행된다

: 어제도 배웠지만, index.js는 웹앱이 처음 실행되는 시점 그리고 새로고침되는 시점에서만 activate된다. 그리고 그안의 ReactDOM.render()가 항상 궁금했는데, 보통 그안의 첫번째 매개변수로 <App /> 가 오게된다. 이 때, 나는 원래 이 메서드로 App 컴포넌트(최상위 컴포넌트)에 포함되는 하위 컴포넌트들까지 모두 렌더링하는줄 알았지만, 그것이 아니라 단지 App 컴포넌트만 렌더링하며, 다른 하위 컴포넌트들은 ReactDOM.render의 지시로(?) 렌더링되는 것은 아니라고 한다.

리액트 컴포넌트는 단순한 '함수'이다.

: 실제로 js 파일을 만들고, 함수를 만들어서 그 안에 jsx를 리턴하도록 해놓으면, 결과적으로 그것이 하나의 리액트 컴포넌트 단위를 이루게 된다. 그리고 이러한 함수, 그리고 이 함수가 리턴하는 jsx 그리고 이러한 jsx가 VirtualDOM으로 마지막으로 실제 DOM으로 바뀌는 과정 자체는 'react'가 해준다(자동 변환). 사실 이걸 몰랐던건 아니지만, 본래 상단에

import React from 'react';

구문이 있어야만 함수가 자동으로 변환되는줄 알았으나 WebPack(번들러)이 번들링하는 과정 속에서 자동으로 이에 대해서 변형을 하는 거였다.

반드시 styled-components, css-modules를 사용해야하는 것은 아니다

: 당연한 말이지만, 어떻게보면 여태까지 저 둘중 하나를 거의 '필수적으로' 사용해왔기에 저 둘을 반드시 써야 css를 쓸 수 있는줄 알았다. 그러나, 단지

import "./ExpenseItem.css";

function ExpenseItem() {
  return (
    <div className="expense-item">
      <div>March 28th 2021</div>
      <div className="expense-item__description">
        <h2>Car Insurance</h2>
        <div className="expense-item__price">$294.67</div>
      </div>
    </div>
  );
}

export default ExpenseItem;

이런식으로 import하기만 해도 css가 적용되는 것을 알 수 있었다. 이 또한, React가 이에 대해 파싱하는 과정에서 자동으로 해주는 것 혹은 webpack의 역할인 거였다.

여러 데이터를 리렌더링 해야하는 상황을 피하자

패션 쇼핑몰 사이트를 만든다 했을 때 infinite scroll을 이용해서 무한한 데이터를 하나의 배열로 된 state로 관리한다고 해보자. 이 때, 300개의 item 중에 한개의 의류 데이터에 좋아요 버튼이 activate 됐고, 이에 따라 해당 item의 id 값을 찾아서(300개의 데이터가 담긴 배열에서) like를 true로 바꿔준다(setState로). 그러면, 해당 배열에서 값을 쓰는 모든 item 들이 결국 리렌더링 된다. 즉, 좋아요 버튼이 눌린 item은 한개인데 그 한개의 좋아요 업데이트를 위해 300개의 데이터가 리렌더링 되는 것이다. 이를 피하려면 item 별로 세분화해서 즉, 컴포넌트를 세분화해서 생각하는 습관을 들여야한다. 예를 들어, 좋아요 버튼에 대한 로직은 의류 아이템 컴포넌트 내에 각각 좋아요 버튼 컴포넌트를 또 세분화해서 둔 다음에 그 버튼이 눌리면 해당 컴포넌트만 리렌더링되도록 설계하는 것이다.

컴포넌트를 세분화하는 이유

사실 컴포넌트를 세분화하지 않아도, 결국 리렌더링이 발생했을 때 리액트 내부의 휴리스틱 알고리즘으로 이전과 달라진 부분만 리렌더링하도록(real DOM에 적용하도록) 세팅돼있기 때문에 꼭 모든 컴포넌트를 잘게잘게 쪼개야하는지 의문이 들 수 있다. 즉, react로 개발을 하더라도 하나의 컴포넌트로 전체 페이지를 제작해도, 결국 리렌더링은 변화된 부분만 될텐데 무슨 상관일까하는 생각이 들 수 있다. 하지만, 리렌더링 이슈 이외에도 리액트 컴포넌트는 앞서 말했듯이 함수로 돼있고, state 업데이트 등이 일어나면 함수를 재실행하게 되는데, 이 때, 해당 함수 안에 여러 로직이 있게 되면 그만큼의 반복적인 로직 실행이 이뤄지고(세분화를 하지 않았다면 큰 단위의 로직이 반복될 것), virtualDOM 두개를 비교하는 데에 있어서도 휴리스틱 알고리즘이라고 하더라도 세분화된 것을 비교하는 것과 큰 단위를 비교하는 것은 다른 얘기이다. 이에 더하여 우리가 흔히 알고 있는 재사용성과 코드를 분할하여 개발함에 따라 디버깅 등 단위적으로 생각할 수 있다는 장점이 있다.

Wrapper Component

import "./Card.css";

function Card(props) {
  const classes = "card " + props.className;
  return <div className={classes}>{props.children}</div>;
}

export default Card;

위와 같은 Wrapper Component를 만들어놓고

.card {
  border-radius: 12px;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
}

아래와 같은 css 파일과 연결시켜 놓는다고 생각해보자. 그러면 우리는 해당 Wrapper 컴포넌트로 둘러 쌓인 컴포넌트에 위와 같은 css 속성을 줄 수 있고 동시에

import ExpenseItem from "./ExpenseItem";
import "./ExpenseItems.css";
import Card from "./Card";

function ExpenseItems({ expenses }) {
  return (
    <Card className="expenses">
      {expenses.map((expense) => (
        <ExpenseItem
          key={expense.id}
          title={expense.title}
          amount={expense.amount}
          date={expense.date}
        />
      ))}
    </Card>
  );
}

export default ExpenseItems;

위와 같이 className을 props로 줘서 Card에 있는 default 속성 외에도 각각의 커스텀 속성을 적용해줄 수 있다. 예를 들어,

.expenses {
  padding: 1rem;
  background-color: rgb(31, 31, 31);
  margin: 2rem auto;
  width: 50rem;
  max-width: 95%;
  border-radius: 12px;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
}

위의 Card.css의 속성들과 위의 expenses의 속성들을 같이 적용할 수 있는 것이다. 이는 컴포넌트 기반으로 개발을 하여 컴포지션을 형성하는 리액트에서 간간히 어쩌면 자주 써먹을 방법이니 기억해두자(props.children을 활용하는 방법)

어제 질문들

그러면, useState, props 변화 등을 통해 특정 컴포넌트가 리렌더링이 되는 상황이 발생했을 때, ReactDOM은 어떤식으로 DOM API를 호출하며, 그 부분만 리렌더링하도록 하는걸까?

: useState 등으로 state가 변화했을 때, 일단 함수 컴포넌트를 예로 들면, 해당 컴포넌트가 재실행된다(즉, 함수가 재실행된다)

import "./ExpenseItem.css";
import React, { useState } from 'react';

function ExpenseItem() {
  const [title, setTitle] = useState("");
  return (
    <div className="expense-item">
      <div>March 28th 2021</div>
      <div className="expense-item__description">
        <h2>{title}</h2>
        <button className="expense-item__price" onClick={() => setTitle("제목 바꾸기")}>click</button>
      </div>
    </div>
  );
}

export default ExpenseItem;

예를 들어, 위와 같은 컨테이너에서 click 버튼을 클릭했을 때, setTitle이 activate되면서 state가 변경될테고, 그러면 function이 재실행된다(컴포넌트는 하나의 함수니까). 그러면, return 문 이하의 virtualDOM을 리렌더링하게 되는데, 이 때, 리액트 내부적으로 이전의 virtualDOM과 새로운 virtualDOM의 차이를 찾아내서 '그 차이가 나는 부분만' 업데이트 한다. 여기서 '차이가 나는 부분만' 이라는 말이 뭔가하면 예를 들어,
위의 화면처럼 click을 하면 title state가 속한 h2 태그 부분만 리렌더링을 한다는 것이다. 이는 리액트가 virtualDOM끼리 비교를해서 차이가 나는 부분만 찾아서 dom update를 하는 것으로 파악할 수 있다.

Next Questions

  • Hooks 에 대해서 공부해보기(useCallback 이러한 것들)
  • function이 하나의 컴포넌트라면 그 바깥에 정의한 변수들은 어떻게 되는가?

references

profile
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

0개의 댓글