JD의 블로그

Learning React - 5장 본문

Web/프론트엔드

Learning React - 5장

GDong 2021. 10. 8. 03:35

JSX를 사용하는 리액트

createElement 함수는 리액트의 동작을 살펴보는 데 좋은 방법이지만 리액트 개발자들이 실제로 해야 하는 일과는 거리가 있다. 우리는 복잡하고 읽기 어려운 자바스크립트 구문 트리를 이리저리 전달하면서 재미있다고 하지 않는다. 리액트를 활용해 효과적으로 일하려면 JSX라는 다른 요소가 필요하다.

 

JSX는 자바스크립트의 JS와 XML의 X를 합친 말이다. JSX는 자바스크립트 코드 안에서 바로 태그 기반의 구문을 써서 리액트 엘리먼트를 정의할 수 있게 해주는 자바스크립트 확장이다. JSX는 단지 복잡한 createElement 보다 편하게 리액트 엘리먼트를 만들 수 있게 해주는 다른 방법이다.

 

1. JSX로 리액트 엘리먼트 정의하기

페이스북의 리액트 팀은 리액트를 내놓으면서 JSX도 함께 내놨다. JSX는 속성이 붙은 복잡한 DOM 트리를 작성할 수 있는 간편한 문법을 제공한다. JSX에서는 태그를 사용해 엘리먼트의 타입을 지정한다. 태그의 속성은 프로퍼티를 표현한다. 여는 태그와 닫는 태그 사이에 엘리먼트의 자식을 넣는다. 

 

JSX 엘리먼트에 다른 JSX 엘리먼트를 자식으로 추가할 수 있다. JSX로 정의한 ul 엘리먼트가 있다면 JSX 태그를 사용해 만든 자식 리스트 원소들을 ul 엘리먼트에 추가할 수 있다. 결과는 HTML과 비슷해보인다. 

 

1.1 JSX 팁

내포된 컴포넌트

JSX에서는 다른 컴포넌트의 자식으로 컴포넌트를 추가할 수 있다. 

<IngredientsList>
	<Ingredient />
    <Ingredient />
    <Ingredient />
</IngredientsList>

className

자바스크립트에서 class가 예약어이므로 class 속성 대신 className을 사용한다.

<h1 className="fancy">구운 연어</h1>

자바스크립트 식

중괄호로 자바스크립트 식을 감싸면 중괄호 안의 식을 평가해서 결과값을 사용해야 한다는 뜻이다.

<h1>{title}</h1>

문자열이 아닌 다른 타입의 값도 자바스크립트 식 안에 넣어야 한다. 

<input type="checkbox" defaultChecked={false} />

평가

중괄호 안에 들어간 자바스크립트 코드는 그 값을 평가받는다. 이는 덧셈이나 문자열 이어붙임 등의 여러 연산이 일어날 수 있다는 뜻이다.

또 자바스크립트 식 안에 함수 호출 구문이 있다면 그 함수가 호출된다는 뜻이기도 하다.

<h1>{"Hello" + title}</h1>

<h1>{title.toLowerCase().replace}</h1>

1.2 배열을 JSX로 매핑하기

JSX는 자바스크립트이므로 자바스크립트 함수 안에서 JSX를 직접 사용할 수 있다. 예를 들어 배열을 JSX 엘리먼트로 변환할 수 있다.

<ul>
	{props.ingredients.map((ingredient, i) => (
    	<li key={i}>{ingredient}</li>
    ))}
</ul>

2. 바벨

자바스크립트는 인터프리터 언어라서 브라우저가 코드 텍스트를 해석하기 때문에 컴파일할 필요가 없다. 하지만 모든 브라우저가 최신 자바스크립트 문법을 지원하지는 않는다. 우리는 JSX와 함께 최신 자바스크립트 기능을 활용하고 싶기 때문에 우리가 작성한 멋진 소스코드를 브라우저가 해석할 수 있는 코드로 변환해줄 수단이 필요하다. 이런 변환 과정을 컴파일링이라고 부르며 바벨이 그런 일을 해준다.

 

바벨을 사용하는 방법이 많이 있다. 가장 간단한 방법은 바벨 CDN 링크를 직접 HTML에 포함시키는 것이다. 이렇게 하면 타입이 "text/babel"인 script 블록을 바벨이 컴파일해준다. 바벨은 클라이언트가 script안의 코드를 실행하기 전에 컴파일을 수행한다. 

 

3. JSX로 작성한 조리법

JSX는 코드에서 리액트 엘리먼트를 멋지고 깔끔하게 기술할 수 있게 해주며 JSX로 작성한 코드는 작성자 스스로 볼 때도 이해하기 쉽고 리액트 커뮤니티의 다른 엔지니어들이 볼 때도 거의 즉시 이해할 수 있을 정도로 읽기 좋다. 

 

데이터는 자바스크립트 객체가 2개 포함된 배열이다. 각 객체에는 조리법의 이름, 필요한 재료 리스트, 그리고 조리 절차가 들어 있다.

 

이런 조리법을 가지고 2가지 컴포넌트가 들어 있는 UI를 만들 수 있다. Menu 컴포넌트는 조리법의 목록을 표시하고 Recipe 컴포넌트는 각 조리법의 UI를 표현한다. DOM에 렌더링해야하는 대상은 Menu 컴포넌트다. 데이터를 Menu 컴포넌트의 recipes 프로퍼티로 넘긴다.

 

// 데이터는 조리법 객체의 배열이다.
var data = [...];

// 조리법 하나를 표현하는 상태가 없는 함수 컴포넌트다
const Recipe = (props) => (
	...
);

// 조리법으로 이뤄진 메뉴를 표현하는 상태가 없는 함수 컴포넌트다
const Menu = (props) => (
	...
);

// ReactDOM.render를 호출해서 Menu를 현재의 DOM 안에 렌더링한다
ReactDOM.render(
	<Menu recipes={data} title="맛있는 조리법" />,
    document.getElementById("root")
);

 

Menu 컴포넌트 안에서 쓰인 리액트 엘리먼트는 JSX로 되어 있다. 모든 내용은 article 엘리먼트 안에 들어 있다.

function Menu(props) {
	return (
    	<article>
        	<header>
            	<h1>{props.title}</h1>
            </header>
        	<div className="recipes">
        	</div>
        </article>
    );
}

div.recipes 엘리먼트 안에는 각 조리법 컴포넌트가 들어간다.

<div className="recipes">
	{props.recipes.map((recipe, i) => (
    	<Recipe
        	key={i}
            name={recipe.name}
            ingredients={recipe.ingredients}
            steps={recipe.steps}
        />
      ))}
</div>

div.recipes 엘리먼트 안에 조리법을 나열하기 위해 중괄호를 사용해서 자식 노드 배열을 반환하는 자바스크립트 식을 추가한다. props.recipes 배열에 map을 적용해서 배열 안의 각 객체에 대한 컴포넌트를 만든다. 각 조리법에는 이름, 재료, 조리 절차가 들어있다. 

이런 데이터를 각 Recipe에 프로퍼티로 전달해야 한다. 또한 각 조리법 엘리먼트를 유일하게 식별하기 위해 key 프로퍼티를 추가해야한다.

 

JSX의 스프레드 연산자를 사용하면 코드를 더 개선할 수 있다.

{
	props.recipes.map((recipe, i) => <Recipe key={i} {...recipe} />);
}

이제 각 조리법을 처리하는 코드를 살펴보자.

function Recipe({ name, ingredients, steps}) {
	return (
    	<section id={name.toLowerCase().replace(/ /g, "-")}>
        	<h1>{name}</h1>
            <ul className="ingredients">
            	{ingredients.map((ingredient, i) => (
                	<li key={i}>{ingredient.name}</li>
                ))}
            </ul>
            <section className="instructions">
            	<h2>조리 절차</h2>
                {steps.map((step,i) => (
                	<p key={i}>{step}</p>
                ))}
            </section>
        </section>
    );
}

4. 리액트 프래그먼트

앞 절에서는 Menu 컴포넌트를 렌더링했다. 이 Menu 컴포넌트는 부모 컴포넌트로, 자식인 Recipe 컴포넌트를 렌더링한다. 리액트 프래그먼트를 사용해 두 형제 컴포넌트를 렌더링해주는 예제를 보자. 먼저 루트에 DOM을 렌더링해줄 Cat이라는 새 컴포넌트를 만들자.

function Cat({ name }) {
	return <h1>고양이 이름은 {name} 입니다. </h1>;
}

ReactDOM.render(<Cat name="나비" />, document.getElementById("root"));

이 코드는 예상대로 h1을 렌더링하지만, 내부에서 h1과 같은 수준에 p 태그를 추가하면 프래그먼트를 사용하라고 권장하는 메시지와 오류가 발생한다. 

 

리액트는 둘 이상의 인접한 형제 엘리먼트를 컴포넌트로 렌더링하지 않기 때문에 이들은 div와 같은 태그로 둘러싸야만 한다. 이로 인해 불필요한 태그가 만들어지고, 아무 목적도 없이 다른 태그를 감싸는 태그가 생겨버린다. 리액트 프래그먼트를 사용하면 새로운 태그를 실제로 만들지 않아도 이런 래퍼 동작을 흉내낼 수 있다.

function Cat({name}){
	return (
    	<React.Fragment>
        	<h1>고양이 이름은 {name} 입니다.</h1>
            <p>이 고양이는 멋져요. </p>
        </React.Fragement>
    );
}

프래그먼트 태그를 추가하면 경고가 사라지며 프래그먼트를 단축한 태그를 써서 코드를 더 깔끔하게 만들 수 있다.

function Cat({name}) {
	return(
    	<>
        	<h1>고양이 이름은 {name} 입니다.</h1>
            <p>이 고양이는 멋져요. </p>
        </>
	);
}

 

5. 웹팩 소개

리액트를 프로덕션에 사용하려고 한다면 고려해야 할 것이 많다. JSX와 ESNext의 변환을 어떻게 처리해야 할까? 프로젝트의 의존 관계를 어떻게 관리해야 할까? 이미지와 CSS를 어떻게 최적화할까?

 

이런 질문을 해결해주는 여러 도구가 있다. 많이 사용 중인 웹팩이 주도적인 도구 중 하나이다. 

웹팩은 모듈 번들러로 알려져있다. 모듈 번들러는 여러 다른 파일들(자바스크립트, LESS, CSS, JSX, ESNext 등)을 받아서 한 파일로 묶어준다. 모듈을 하나로 묶어서 얻는 2가지 이익은 모듈성과 네트워크 성능이다. 

 

모듈성은 소스 코드를 작업하기 쉽게 여러 부분 또는 모듈로 나눠서 다룰 수 있게 해준다. 특히 팀 환경에서 모듈성이 중요하다.

의존 관계가 있는 여러 파일들을 묶은 번들을 브라우저가 한 번만 읽기 때문에 네트워크 성능이 좋아진다. 

 

컴파일 외에 웹팩이 처리할 수 있는 일은 다음과 같다.

  • 코드 분리 : 코드를 여러 덩어리로 나눠서 필요할 때 각각을 로딩할 수 있다. 때로 이를 롤업이나 레이어라고 부른다. 코드의 분리는 여러 다른 페이지나 디바이스에서 필요한 자원을 따로 나눠서 더 효율적으로 처리하기 위함이다.
  • 코드 축소 : 공백, 줄바꿈, 긴 변수 이름, 불필요한 코드 등을 없애서 파일 크기를 줄여준다.
  • 특징 켜고 끄기 : 코드의 기능을 테스트해야 하는 경우 코드를 각각의 환경에 맞춰 보내준다.
  • HMR : 소스 코드가 바뀌는지 감지해서 변경된 모듈만 즉시 갱신해준다.

 

웹팩과 같은 도구를 사용해 클라이언트 자바스크립트를 정적으로 빌드하면 대규모 웹 애플리케이션을 여러 팀원들이 함께 개발할 수 있다. 웹팩 모듈 번들러를 사용하면 다음과 같은 장점을 가진다.

  • 모듈성: 모듈 패턴을 사용해 모듈을 외부에 익스포트하고 나중에 그 모듈을 필요한 곳에 임포트해서 쓸 수 있으면 애플리케이션 소스 코드를 더 관리하기 쉬운 규모로 나눌 수 있다. 각 팀이 서로 다른 파일에 작업을 진행하고 프로덕션으로 보내기 전에 전체를 한 파일로 묶으면서 정적으로 오류를 검사할 수 있으므로 여러 팀이 쉽게 협업할 수 있다.
  • 조합 : 모듈을 사용하면 애플리케이션을 효율적으로 구축할 수 있는 작고 단순하며 재사용하기 쉬운 리액트 컴포넌트를 구축할 수 있다. 컴포넌트가 작으면 더 이해하거나 테스트하거나 재사용하기 쉽다. 또 컴포넌트가 작고 단순하다면 애플리케이션을 향상시키기 위해 그 컴포넌트 전체를 완전히 변경하기도 더 쉽다.
  • 속도 : 모든 애플리케이션 모듈과 의존 관계를 하나의 클라이언트 번들로 묶으면 여러 파일을 HTTP로 요청함에 따라 발생할 수 있는 시간 지연이 없어져서 애플리케이션 로딩 속도가 빨라진다. 애플리케이션을 모두 한 파일로 패키지하면 클라이언트에서 단 한 번만 HTTP 요청을 보내면 된다. 번들 안에 들어가는 코드를 축소하면 로딩 시간을 더 줄일 수 있다.
  • 일관성 : 웹팩이 JSX나 자바스크립트를 컴파일해주기 때문에 프로젝트에서 아직 표준화되지 않은 미래의 문법을 사용할 수 있다. 

웹팩은 import문을 발견할 때마다 파일시스템에서 해당 모듈을 찾아서 번들에 포함시켜준다. 의존 관계는 컴포넌트 파일이나 리액트와 같은 라이브러리 파일, 이미지 등 앱에게 필요한 요소를 뜻한다. 

'Web > 프론트엔드' 카테고리의 다른 글

Learning React - 6장  (0) 2021.10.11
Learning React - 4장  (0) 2021.10.06
Learning React - 3장  (0) 2021.10.06
Learning React - 2장  (0) 2021.10.04