3월1일(수) React: Components & Rendering, Styling, 반복되는 컴포넌트 처리, 컴포넌트 분리하기(독립 컴포넌트)

Mindfulness·2023년 3월 1일
post-thumbnail

오늘은 나머지 React 기초주차 강의 학습 내용을 정리해보겠다.

Components부터 Rendering, Styling, 반복되는 컴포넌트 처리, 컴포넌트 분리하기(독립 컴포넌트)가 있겠다.

12. Component & Rendering

1. Component

컴포넌트는 리액트의 핵심 빌딩 블록 중 하나. 즉 리액트에서 개발할 모든 애플리케이션은 컴포넌트라는 조각으로 구성된다. 컴포넌트는 UI 구축 작업을 훨씬 쉽게 만들어준다. UI 요소를 표현하는 최소한의 단위이며 화면의 특정 부분이 어떻게 생길지 정하는 선언체이다. 말이 조금 어려운데, 이전 실습에서 경험 했듯이 컴포넌트를 생성하고 보여지고자 하는 UI 요소를 컴포넌트 내부에서 JSX를 통해 선언하면 이를 리액트가 화면에 그려주었다. 리액트 컴포넌트가 선언체라는 개념은 아주 중요하다.

그 이유는 리액트의 컴포넌트기반 개발 이전에는 브라우저에서 동적으로 변하는 UI를 표현하기 위해 직접 DOM 객체를 조작하는 명령형 프로그래밍 방식으로 구현했었다. 그렇다면 기존 명령형 프로그래밍과 리액트 컴포넌트의 선언적 프로그래밍은 어떻게 다를까?

명령형은 어떻게(How)를 중요시여겨서 프로그램의 제어의 흐름과 같은 방법을 제시하고 목표를 명시하지 않는 형태이다. 선언형은 무엇(What)을 중요시 여겨서 제어의 흐름보다는 원하는 목적을 중요시 여기는 형태이다.

-DOM (명령형 프로그래밍)
명령형으로 작성된 코드의 경우 Hello, World!를 출력하기 위해 컴퓨터가 수행하는 절차를 일일히 코드로 작성해주어야 한다.

// Hello, World! 화면에 출력하기
// 순수 javaScript 명령형 코드 
const root = document.getElementById('root'); 
const header = document.createElement('h1'); 
const headerContent = document.createTextNode(
	'Hello, World!'
);

header.appendChild(headerContent); 
root.appendChild(header);

-리액트 (선언형 프로그래밍)
React 코드의 경우 내가 UI을 선언하고 render 함수를 호출하면 React가 알아서 절차를 수행해 화면에 출력해준다. 즉, 화면에 어떻게 그려야할지는 React 내부에 잘 숨겨져 추상화되어 있다.

// React 코드 (선언적인)
const header = <h1>Hello World</h1>; // jsx
ReactDOM.render(header, document.getElementById('root'));

DOM을 직접 조작하여 명령형 프로그래밍 방식으로 작성하던 코드가 나쁘다는게 아니다. 카운터 예시와 같이 격리된 예제에서는 차라리 리액트와 같은 UI 라이브러리를 사용하지 않고 만드는게 더 빠르고 전체적인 번들 사이즈 측면에서도 더 효율적인 방법일수 있습다. 그치만 더 복잡한 UI 시스템에서는 관리하기가 기하급수적으로 어려워진다.

2. 렌더링이란?

React에서 rendering은 React 요소를 브라우저 DOM으로 변환하는 프로세스를 말한다. React는 가상 DOM(Virtual DOM)을 사용하여 브라우저 DOM의 효율적인 업데이트를 관리한다.

React 애플리케이션은 일반적으로 React 컴포넌트로 구성된다. 컴포넌트는 일종의 JavaScript 함수로, React 요소(HTML 태그와 비슷한 JSX 코드)를 반환한다. 이 요소는 브라우저에서 실제로 렌더링된다.

React는 상태(state)나 속성(props)이 변경될 때마다 가상 DOM을 업데이트하고 변경된 부분만 실제 DOM에 반영하여 불필요한 리렌더링을 최소화한다. 이러한 방식으로 React는 성능을 최적화하고 빠른 애플리케이션을 만들 수 있도록 지원한다.
따라서, rendering은 React 요소를 브라우저 DOM으로 변환하고, 이를 효율적으로 업데이트하여 React 애플리케이션을 렌더링하는 과정이다. 즉, 컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미한다.

더욱 확실히 이해하기 위해 예시를 한 번 들어보자.
HTTP 통신을 배울 때 많이 보는 주방장과 웨이터 예시를 들어보자. 이제부터 컴포넌트를 주방에서 요리를 준비하는 주방장이라고 생각해보자. 그리고 리액트는 손님으로부터 주문받아 주방에 전달하고 완성된 요리를 손님에게 서빙하는 웨이터로 가정해보겠다. 그리고 손님이 주문하고 주방장이 만드는것이 요리가 아닌 UI라고 하겠다.

  • UI - 음식
  • 컴포넌트 - 음식을 만드는 주방장
  • 리액트 - 웨이터

자 그럼 렌더링이 일어나는 프로세스를 아래와 같이 설명할 수 있겠다.

  1. 렌더링 일으키는 것은 (triggering) - UI를 주문하고 주방으로 전달하는 것.
  2. 렌더링 한다는 것은 (rendering) - 주방에서 컴포넌트가 UI를 만들고 준비하는 것.
  3. 렌더링 결과는 실제 DOM에 커밋한다는 것은 (commit) - 리액트가 준비된 UI를 손님 테이블에 올려놓는 것.

2-1. 렌더링 트리거
앞선 자료에서 ‘렌더링이 발생하는 경우’를 다뤘었는데,

  1. 첫 리액트 앱을 실행 했을 때
  2. 현재 리액트 내부에 어떤 상태(state)에 변경이 발생했을 때.
    -컴포넌트 내부 state가 변경 되었을 때
    -컴포넌트에 새로운 props가 들어올 때,
    -상위 부모 컴포넌트에서 위에 두 이유로 렌더링이 발생했을 때

리액트 앱이 실행되고 첫 렌더링이 일어나면 리액트는 컴포넌트의 루트에서 시작하여 아래쪽으로 쭉 훑으며 컴포넌트가 반환하는 JSX 결과물을 DOM 요소에 반영한다.

2-2. 리렌더링
첫 렌더링은 자동으로 일어난 것이었다. 리액트 앱이 실행되면 리액트는 전체 컴포넌트를 렌더링하고 결과물을 DOM에 반영해 브라우저상에 보여준다. 첫 렌더링을 끝난 이후에 추가로 렌더링을 트리거 하려면 상태를 변경해주면 된다. 지금까지 setState 함수만 공부해보았는데 몇 가지가 더 있다고 한다. 후에 배우면 이 부분도 기록해 보겠다.

컴포넌트 상태에 변화가 생기면 리렌더링이 발생한다. 이때 여러 상태가 변경됐다면 리액트는 이를 큐 자료구조에 넣어 순서를 관리한다.

  1. 주방 예시를 다시 들어보면 리렌더링은 음식점 손님이 첫 주문 이후에 갈증이 생겨 추가로 음료를 주문하거나 처음 받은 음식이 마음에 들지 않아 새로운 메뉴를 주문하는 것과 같다.
  2. 새로운 UI주문(리렌더링)이 일어나면 리액트가 변경된 내용을 주방에 있는 요리사인 컴포넌트에 전달하고 컴포넌트는 새로운 변경된 주문을 토대로 새로운 요리(UI)를 만든다.
  3. 새롭게 만들어진 요리(렌러딩 결과)는 리액트에 의해 다시 손님 테이블에 올려진다 (DOM에 반영 - commit phase).

    2-3. 브라우저 렌더링
    브라우저의 렌더링과 리액트의 렌더링은 엄연히 다른 독립적인 프로세스다. 렌더링이 완료되고 React가 DOM을 업데이트한 후 브라우저는 화면을 그린다. 이 프로세스를 "브라우저 렌더링"이라고 하지만 혼동을 피하기 위해 "페인팅"이라고도 한다.

13.실습 - 카운터앱

14.Styling

컴포넌트 스타일링

이번 강의에서는 CSS, 즉 꾸미기에 대해서 학습해보자.

새로운 프로젝트 생성하기
터미널에 “yarn create react-app 프로젝트이름” 로 새로운 프로젝트를 하나 더 생성해보자.

컴포넌트 구현해보기

아래 그림을 보고 똑같이 구현해보자.

(힌트 키워드)
padding
display : flex
alignItems
justifyContent
gap
width, height
border
borderRadius

(구현 예시)

import React from "react";

const App = () => {
  const style = {
    padding: "100px",
    display: "flex",
    gap: "12px",
  };

  const squareStyle = {
    width: "100px",
    height: "100px",
    border: "1px solid green",
    borderRadius: "10px",
		display: "flex",
    alignItems: "center",
    justifyContent: "center",
  };

  return (
    <div style={style}>
      <div style={squareStyle}>감자</div>
      <div style={squareStyle}>고구마</div>
      <div style={squareStyle}>오이</div>
      <div style={squareStyle}>가지</div>
      <div style={squareStyle}>옥수수</div>
    </div>
  );
};

export default App;

컴포넌트 파일에서 CSS 코드 분리하기

지금까지, 한 파일 내에 style을 작성했었는데, 이부분을 CSS 파일로 따로 작성해보자.
JSX는 사실 HTML과 굉장히 비슷하기에 방법이 크게 다르지 않다. 단, class → className 을 사용한다.

import React from "react";

function App() {
  const style = {
    padding: "100px",
    display: "flex",
    gap: "12px",
  };

  const users = [
    { id: 1, age: 30, name: "송중기" },
    { id: 2, age: 24, name: "송강" },
    { id: 3, age: 21, name: "김유정" },
    { id: 4, age: 29, name: "구교환" },
  ];

  return (
    <div style={style}>
      {users.map((user) => {
        return <Square user={user} key={user.id} />;
      })}
    </div>
  );
}

export default App;

먼저, 컴포넌트 파일에서 className 을 넣어준다. 그리고 이 컴포넌트에서 적용할 CSS 파일을 import 한다. 경로 틀리지 않도록 잘 확인!!

// src/App.js
import React from "react";
import Square from "./components/Square.js";
import "./App.css"; // 🔥 반드시 App.css 파일을 import 해줘야 한다.

function App() {
  const users = [
    { id: 1, age: 30, name: "송중기" },
    { id: 2, age: 24, name: "송강" },
    { id: 3, age: 21, name: "김유정" },
    { id: 4, age: 29, name: "구교환" },
  ];

  return (
    <div className="app-style">
      {users.map((user) => {
        return <Square user={user} key={user.id} />;
      })}
    </div>
  );
}

export default App;

기존 style 변수에 넣어줬던 스타일 프로퍼티들을 별도의 CSS 파일로 옮겨보자.
기존에는 style을 자바스크립트 객체로 작성했었기 때문에 CSS 프로퍼티의 값을 따옴표(””)로 감싸주었지만 CSS 문법에서는 따옴표를 다 지워야한다.

<!--src/App.css-->
.app-style {
  padding: 100px;
  display: flex;
  gap: 12px;
}

15. 반복되는 컴포넌트 처리하기

map 함수

map 함수가 중요하다는 말은 수 없이 들었다. 이런 map 함수를 이용하여 반복되는 컴포넌트를 표현해보자.

import React from "react";

const App = () => {
  const style = {
    padding: "100px",
    display: "flex",
    gap: "12px",
  };

  const squareStyle = {
    width: "100px",
    height: "100px",
    border: "1px solid green",
    borderRadius: "10px",
		display: "flex",
    alignItems: "center",
    justifyContent: "center",
  };

  return (
    <div style={style}>
      <div style={squareStyle}>감자</div>
      <div style={squareStyle}>고구마</div>
      <div style={squareStyle}>오이</div>
      <div style={squareStyle}>가지</div>
      <div style={squareStyle}>옥수수</div>
    </div>
  );
};

export default App;

이렇게 하면 하드코딩이지? 코드가 여러 번 중복이 되잖아. “감자", “고구마" 와 같은 이름들 직접 입력을 해줘야 하고, 데이터로서 관리가 되고 있지 않다. 그리고 만약에 “당근” 이라는 항목이 추가되면 화면을 추가로 개발해줘야 하잖아. 이걸 map으로 구현해보자. (map은 자바스크립트 메서드)

먼저, map() 을 사용하기 위해 채소들의 이름을 배열로 만들어보자.

// src/App.js

function App(){
	// .. 중략
	const vegetables = ["감자", "고구마", "오이", "가지", "옥수수"];

	return <div></div>
}

// .. 중략

그리고 JSX 부분에서 아래와 같이 작성한다.

import React from "react";

  const vegetables = ["감자", "고구마", "오이", "가지", "옥수수"];

  return (
    <div className="app-style">
      {vegetables.map((vegetableName) => {
        return (
          <div className="square-style" key={vegetableName}>
            {vegetableName}
          </div>
        );
      })}
    </div>
  );
};

export default App;

JSX 부분에서 map() 즉, 자바스크립트 코드를 작성할 것 이기때문에 { } 로 먼저 감싸고 시작한다.
JSX에서 map() 은 배열의 모든 요소를 순회한다. 그래서 클라이언트에서는 배열 형태의 데이터를 활용해서 화면을 그려주는 경우가 많고, 이때 배열의 값들로 동적으로 컴포넌트를 만들수 있다.
map을 사용하니 중복된 코드가 사라지고 1개의 컴포넌트를 이용하면서 그 안에서 <'div>{vegetableName}<'/div> 가 순차적으로 보여지고 있다.

조금 더 복잡한 데이터 다뤄보기

(1) 객체가 담긴 배열 다뤄보기

조금 더 복합한 구조의 데이터를 다뤄보자. 주어진 정보는 다음과 같다. user 라는 정보이며 배열안에 object literal 형태의 데이터가 있다.

const users = [
    { id: 1, age: 30, name: "송중기" },
    { id: 2, age: 24, name: "송강" },
    { id: 3, age: 21, name: "김유정" },
    { id: 4, age: 29, name: "구교환" },
  ];

이것도 역시 map()을 사용해보자. User 컴포넌트를 생성하고 스타일 코드를 적용하자. App 컴포넌트에서는 user.map() 을 통해 user의 정보를 순회하고 각각의 user 정보를 User 컴포넌트로 주입해주자.

import React from 'react';
import './App.css'; // 🔥 반드시 App.css 파일을 import 해줘야 한다.

//  User 컴포넌트를 분리해서 구현
function User(props) {
  return (
    <div>{props.user.age}살 - {props.user.name}</div>
  );
}

const App = () => {
  const users = [
    { id: 1, age: 30, name: '송중기' },
    { id: 2, age: 24, name: '송강' },
    { id: 3, age: 21, name: '김유정' },
    { id: 4, age: 29, name: '구교환' },
  ];
  return (
    <div className="app-container">
      {users.map((user) => {
        return <User user={user} key={user.id} />;
      })}
    </div>
  );
};

export default App;

User 컴포넌트에 props로 들어오는 user의 정보는 { id: 1, age: 30, name: "송중기" } 라는 정보가 들어오는 것. map() 의 기능으로 반복되는 컴포넌트를 간단하게 화면에 표시할 수 있다.

(2) state 복습

리액트에서 동적으로 변화는 데이터는 state라는 상태 값으로 관리한다. 기존 정적 배열 데이터로 관리했던 유저 리스트를 useState를 활용해 변화가 일어나면 리랜더링을 초래하는 ‘상태 값’으로 만들어보자.

//기존 users 배열
const users = [
    { id: 1, age: 30, name: '송중기' },
    { id: 2, age: 24, name: '송강' },
    { id: 3, age: 21, name: '김유정' },
    { id: 4, age: 29, name: '구교환' },
  ];

//useState를 이용한 상태값 만들기
const [users, setUsers] = useState([
    { id: 1, age: 30, name: '송중기' },
    { id: 2, age: 24, name: '송강' },
    { id: 3, age: 21, name: '김유정' },
    { id: 4, age: 29, name: '구교환' },
  ]);

(3) 추가/삭제를 위한 준비

user를 추가하고, 삭제해보자.

(추가,삭제위한 준비)

import React from 'react';
import './App.css';

//  User 컴포넌트를 분리해서 구현
function User(props) {
  return (
    <div>{props.user.age}살 - {props.user.name}</div>
  );
}

const App = () => {
  const [users, setUsers] = useState([
    { id: 1, age: 30, name: '송중기' },
    { id: 2, age: 24, name: '송강' },
    { id: 3, age: 21, name: '김유정' },
    { id: 4, age: 29, name: '구교환' },
  ]);
  const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
	const [age, setAge] = useState('');

  return (
    <div className="app-container">
      <input value={name}
        placeholder="이름을 입력해주세요"
			// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
        onChange={(e) => setName(e.target.value)} 
      />
			<input value={age}
        placeholder="나이를 입력해주세요"
			// 인풋 이벤트로 들어온 입력 값을 age의 값으로 업데이트
        onChange={(e) => setName(e.target.value)} 
      />
      {users.map((user) => {
        return <User user={user} key={user.id} />;
      })}
    </div>
  );
};

export default App;

(4) user 추가

import React from 'react';
import './App.css'; 

//  User 컴포넌트를 분리해서 구현
function User(props) {
  return (
    <div className="user-card">
      <div>{props.user.age}살 - </div>
      <div>{props.user.name}</div>
    </div>
  );
}

const App = () => {
  const [users, setUsers] = useState([
    { id: 1, age: 30, name: '송중기' },
    { id: 2, age: 24, name: '송강' },
    { id: 3, age: 21, name: '김유정' },
    { id: 4, age: 29, name: '구교환' },
  ]);
  const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
  const addUserHandler = () => {
    const newUser = {
      id: users.length + 1,
      age: age,
      name: name,
    };

    setUsers([...users, newUser]);
  };
  return (
    <div className="app-container">
      <input
        value={name}
			// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
        onChange={(e) => setName(e.target.value)} 
      />
      {users.map((user) => {
        return <User user={user} key={user.id} />;
      })}
      <button onClick={addUserHandler}>추가하기</button>
    </div>
  );
};

export default App;

(5) user 삭제

import React from 'react';
import './App.css'; 

//  User 컴포넌트를 분리해서 구현
function User(props) {
  return (
    <div className="user-card">
      <div>{props.user.age}살 - </div>
      <div>{props.user.name}</div>
      <button onClick={() => props.handleDelete(props.user.id)}>
        삭제하기
      </button>
    </div>
  );
}

const App = () => {
  const [users, setUsers] = useState([
    { id: 1, age: 30, name: '송중기' },
    { id: 2, age: 24, name: '송강' },
    { id: 3, age: 21, name: '김유정' },
    { id: 4, age: 29, name: '구교환' },
  ]);
  const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
  const addUserHandler = () => {
    const newUser = {
      id: users.length + 1,
      age: 30,
      name: name,
    };

    setUsers([...users, newUser]);
  };
	const deleteUserHandler = (id) => {
    const newUserList = users.filter((user) => user.id !== id);
    setUsers(newUserList);
  };
  return (
    <div className="app-container">
      <input
        placeholder="이름을 입력해주세요"
        value={name}
			// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
        onChange={(e) => setName(e.target.value)} 
      />
      {users.map((user) => {
        return <User user={user} key={user.id} handleDelete={deleteUserHandler}/>;
      })}
      <button onClick={addUserHandler}>추가하기</button>
    </div>
  );
};

export default App;

(6) 컴포넌트 분리

import React from 'react';
import './App.css';

//1. 버튼 컴포넌트 생성
function CustomButton(props) {
  return <button onClick={props.onClick}>{props.children}</button>;
}


//  User 컴포넌트를 분리해서 구현
function User(props) {
  return (
    <div className="user-card">
      <div>{props.user.age}살 - </div>
      <div>{props.user.name}</div>
			//2. 버튼을 컴포넌트로 바꾸기
      <CustomButton onClick={() => props.handleDelete(props.user.id)}>
        삭제하기
      </CustomButton>
    </div>
  );
}

const App = () => {
  const [users, setUsers] = useState([
    { id: 1, age: 30, name: '송중기' },
    { id: 2, age: 24, name: '송강' },
    { id: 3, age: 21, name: '김유정' },
    { id: 4, age: 29, name: '구교환' },
  ]);
  const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
  const addUserHandler = () => {
    const newUser = {
      id: users.length + 1,
      age: 30,
      name: name,
    };

    setUsers([...users, newUser]);
  };
	const deleteUserHandler = (id) => {
    const newUserList = users.filter((user) => user.id !== id);
    setUsers(newUserList);
  };
  return (
    <div className="app-container">
      <input
        placeholder="이름을 입력해주세요"
        value={name}
			// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
        onChange={(e) => setName(e.target.value)} 
      />
      {users.map((user) => {
        return <User user={user} key={user.id} handleDelete={deleteUserHandler}/>;
      })}
			//3. 버튼을 컴포넌트로 바꾸기
      <CustomButton onClick={addUserHandler}>추가하기</CustomButton>
    </div>
  );
};

export default App;

(7) 버튼 색상 변경하기

추가하기 버튼은 초록색, 삭제하기 버튼은 빨강색으로 표현해보자.
버튼 컴포넌트를 호출할 때 색상에 대한 정보를 props로 전달해볼까?

//  Button 컴포넌트 부분만
function CustomButton(props) {
const {color, onClick, children} = props

  if (color)
    return (
      <button
        style={{ background: color, color: "white" }}
        onClick={onClick}
      >
        {children}
      </button>
    );

  return <button onClick={onClick}>{props.children}</button>;
}
<Button color="red" onClick={() => props.handleDelete(props.user.id)}>삭제하기</Button>
<Button color="green" onClick={handleAdd}>추가하기</Button>

key

혹시 브라우저 콘솔에서 에러가 뜬다면?
map을 사용해서 화면을 구현했을 때 이런 에러

리액트에서 map을 사용하여 컴포넌트를 반복 렌더링 할 때는 반드시 컴포넌트에 key를 넣어줘야 한다.

key가 필요한 이유는 React에서 컴포넌트 배열을 렌더링했을 때 각각의 원소에서 변동이 있는지 알아내려고 사용하기 때문. 만약 key가 없다면 React는 가상돔을 비교하는 과정에서 배열을 순차적으로 비교하면서 변화를 감지하려 한다. 하지만 key가 있으면 이 값을 이용해서 어떤 변화가 일어났는지 더 빠르게 알아낼 수 있다. 즉, key값을 넣어줘야 React의 성능이 더 최적화 된다. 라는 의미이다.

key는 아래와 같이 넣어주면 된다. props 처럼 생겼지만, props로 설정하지 않아도 key를 넣을 수 있다.

<div style={style}>
      {users.map((user) => {
        return <User user={user} key={user.id} />;
      })}
    </div>

그리고 간혹 map((value, index)=>{}) 처럼, map에서 지원해주는 index를 사용해서 key를 넣는 경우가 있다. 이것은 좋지 않은 방식이니 지양해야 한다.

Null vs Undefined

마지막으로 Null과 Undefined에 대해 알아보자.
undefined와 null에 대해서 : 둘 다 없음을 의미 (같지는않음)

(1) undefined
1. 사용자 지정
2. 자바스크립트 엔진에서 자동 부여

//2-1) 변수에 값이 지정되지 않은 경우, 
//데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
var a;
console.log(a);
//2-2) .이나 []로 접근하려 할 때, 해당 데이터가 존재하지 않는 경우
var obj = { a: 1 };
console.log(obj.a);
console.log(obj.b);
// console.log(b);
//2-3) return 문이 없거나 호출되지 않는 함수의 실행 결과
var func = function() { };
var c = func();
console.log(c);
  1. 비어있는 요소와 undefined에 대해
/** 비어있는 요소와, undefined를 할당한 요소는 다르다. */
var arr1 = [];
arr1.length = 3;
console.log(arr1);

var arr2 = new Array(3);
console.log(arr2);

var arr3 = [undefined, undefined, undefined];
console.log(arr3);
var arr1 = [undefined, 1];
var arr2 = [];
arr2[1] = 1;

//forEach
arr1.forEach(function(v, i) { console.log(v, i); );
arr2.forEach(function(v, i) { console.log(v, i); );

//map
arr1.map(function(v, i) { return v + i });
arr2.map(function(v, i) { return v + i });

//filter
arr1.filter(function(v) { return !v; });
arr2.filter(function(v) { return !v; });

//reduce
arr1.reduce(function(p, c, i) { return p + c + i; }, '');
arr2.reduce(function(p, c, i) { return p + c + i; }, '');

//reduce란?
//https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
  1. 2가지 역할을 가진 undefined, 헷갈리고 위험하다!
    1. 지금 undefined로 나오는 이 변수가, 필요에 의해 할당한건지
      자바스크립트 엔진이 반환한건지 어떻게 알지? → 구분할 수 없다.
    2. ‘없다’를 명시적으로 표현할 때는 undefined를 사용하지 말자!

(2) null
1. 용도 : ‘없다’를 명시적으로 표현할 때
2. 주의 : typeof null

var n = null;
console.log(typeof n);

//동등연산자(equality operator)
console.log(n == undefined);
console.log(n == null);

//일치연산자(identity operator)
console.log(n === undefined);
console.log(n === null);

16. 컴포넌트 분리하기(독립 컴포넌트)

컴포넌트 분리해서 구현하기

우리는 지난 챕터에, <'User /> 컴포넌트, <'Button /> 컴포넌트의 관심사를 <'App /> 컴포넌트에서 분리하고, 컴포넌트의 역할을 명확히 해주었기 때문에 <'User /> 컴포넌트의 재사용성과 전체적인 가독성을 올릴 수 있었다.

그렇지만 현재 <'App /> 컴포넌트와 <'User /> 컴포넌트, <'Button /> 컴포넌트가 모두 App.js라는 파일 한 곳에 작성되어 있기 때문에 발생하는 몇 가지 문제들이 있다.

  1. App.js 파일의 역할이 명확하지 않다.
  2. 컴포넌트 분리를 통해 가독성을 높였지만, 두 컴포넌트의 사이즈가 커지거나 혹은 또 다른 컴포넌트를 작성하게 된다면 가독성은 금방 떨어지게 될 것이다.
  3. 현재 프로젝트 구조에서, User 컴포넌트, Button 컴포넌트가 어디에 작성되어 있는지 찾기가 힘들다. 특히 작성자가 아닌 다른 개발자가 App.js 파일을 보고 User 컴포넌트, Button 컴포넌트가 해당 파일에 작성되어 있다고 유추하기 쉽지 않아 보인다.

우리는 일반적으로, 계속 여러번 렌더링하여, 기능을 재사용하는 컴포넌트들은 따로 분리해서 사용한다. User 라는 이름의 컴포넌트로 분리해보자.

하나의 폴더 안에 모든 컴포넌트를 만들어서 관리하면 시간이 흐를수록 컴포넌트가 많아져서 원하는 컴포넌트를 찾기가 힘들어질 것이다. 그래서 연관된 컴포넌트끼리 폴더를 만들어서 관리하는 것이 컴포넌트를 찾기에 수월할 것이다.

<'Button /> 컴포넌트를 예시로 파일을 나누는 실습을 해보자.

1. src에 오른쪽 마우스를 눌러서 new folder로 components라는 폴더를 만든다.

  1. components 폴더에 Button.js 파일을 만든다.
    • 이후 새로운 컴포넌트 파일을 만들 때도 e.g) <컴포넌트>.js 형식으로 만들어주면 된다.
  2. App.js에서 작성한 Button 컴포넌트를 Button.js로 옮겨준다.
// 경로: src/components/Button.js

function Button(props) {
  switch (props.color) {
    case 'green': {
      return (
        <button
          style={{ background: 'green', color: 'white' }}
          onClick={props.onClick}
        >
          {props.children}
        </button>
      );
    }
    case 'red': {
      return (
        <button
          style={{ background: 'red', color: 'white' }}
          onClick={props.onClick}
        >
          {props.children}
        </button>
      );
    }
    default: {
      return <button onClick={props.onClick}>{props.children}</button>;
    }
  }
}

export default Button;

// 외부 모듈(파일)에서 Sqaure 컴포넌트를 사용할 수 있게 export(내보내기)해줘야 한다.
// 경로: src/App.js

import React from 'react';
import Button from './components/Button.js';

//  User 컴포넌트를 분리해서 구현
function User(props) {
  return (
    <div className="user-card">
      <div>{props.user.age}살 - </div>
      <div>{props.user.name}</div>
			//2. 버튼을 컴포넌트로 바꾸기
      <Button onClick={() => props.handleDelete(props.user.id)}>
        삭제하기
      </Button>
    </div>
  );
}

const App = () => {
  const [users, setUsers] = useState([
    { id: 1, age: 30, name: '송중기' },
    { id: 2, age: 24, name: '송강' },
    { id: 3, age: 21, name: '김유정' },
    { id: 4, age: 29, name: '구교환' },
  ]);
  const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
  const addUserHandler = () => {
    const newUser = {
      id: users.length + 1,
      age: 30,
      name: name,
    };

    setUsers([...users, newUser]);
  };
	const deleteUserHandler = (id) => {
    const newUserList = users.filter((user) => user.id !== id);
    setUsers(newUserList);
  };
  return (
    <div className="app-container">
      <input
        placeholder="이름을 입력해주세요"
        value={name}
			// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
        onChange={(e) => setName(e.target.value)} 
      />
      {users.map((user) => {
        return <User user={user} key={user.id} handleDelete={deleteUserHandler}/>;
      })}
			//3. 버튼을 컴포넌트로 바꾸기
      <Button onClick={addUserHandler}>추가하기</Button>
    </div>
  );
};

export default App;

코드를 작성하다가 어느 순간 코드가 복잡하다고 느껴진다면, 관심사의 분리가 필요한 순간인지 생각해보는 게 좋을 거 같다. 리액트에서도 마찬가지로 하나의 컴포넌트에서 모든 기능을 구현할 수 없기 때문에, 여러 개의 컴포넌트를 만들어서 조립해보자.

export와 export default 차이점

둘 다 모듈을 내보내고 불러오는 방법이다!
모듈은 크게 두 종류로 나뉜다.

  1. 복수의 함수가 있는 라이브러리 형태의 모듈
  2. 개체 하나만 선언되어있는 모듈

대개는 두 번째 방식으로 모듈을 만드는 걸 선호하기 때문에 함수, 클래스, 변수 등의 개체는 전용 모듈 안에 구현된다.

그런데 이렇게 모듈을 만들다 보면 자연스레 파일 개수가 많아질 수밖에 없다. 그렇더라도 모듈 이름을 잘 지어주고, 폴더에 파일을 잘 나눠 프로젝트를 구성하면 코드 탐색이 어렵지 않으므로 이는 전혀 문제가 되지 않는다.

모듈은 export default라는 특별한 문법을 지원한다. export default를 사용하면 '해당 모듈엔 개체가 하나만 있다’는 사실을 명확히 나타낼 수 있다.
내보내고자 하는 개체 앞에 export default를 붙여보자.

// App.js
function App() {
	return <div>My App</div>;
}

export default App;


// Sample.js
export function Sample1 () {
	//
}

export function Sample2 () {
	//
}

파일 하나엔 대개 export default가 하나만 있다.
이렇게 default를 붙여서 모듈을 내보내면 중괄호 {} 없이 모듈을 가져올 수 있습니다.
반면, 단순 export로 선언한 모듈은 중괄호 {}를 반드시 사용해야 한다.

// index.js
import App from "./App";
import { Sample1, Sample2 } from "./Sample";

...

[참고 : 모던 javascript 튜토리얼]

profile
Junior Frontend Developer

0개의 댓글