생활코딩 밤공부 React편을 듣고 정리한 내용입니다.

강의에 덧붙여 제가 이해한 내용을 같이 첨부하였습니다.

2편의 목차는 다음과 같이 구분하였습니다.

1. 컴포넌트란?
2. props란?
리액트는 사용자 정의 태그(컴포넌트)를 만드는 기술이다.

저번 시간에 리액트를 다음과 같이 정의했다. 컴포넌트를 만들든, 컴포넌트를 가지고 UI를 만들든 그게 그거다.

그러나 컴포넌트에 대해서는 완벽히 이해를 하지 못하고 넘어갔다.

이번 시간에는 IT계의 명언인 '백문이 불여일타'를 생각하며, 실제 작동하는 컴포넌트를 만들어 보면서 컴포넌트에 대한 이해를 해보자.

 

1. 컴포넌트란?

 

>app.js

function App() {
  return (
    <div>
      <h1>Counter</h1>
      <button>+</button> 0
    </div>
  );
}

골격이지만, 간단한 카운터를 하나 만들어 주었다.

카운터임을 알려주기 위해 h1태그로 카운터임을 알려 주었고,

button 태그로 더하기 버튼을 만들어주고,

그 뒤에는 하드코딩이지만 숫자를 집어넣어 주었다.

그런데 내가 카운터를 만약 1억개 만들고 싶다면, 저 코드블럭에 있는 내용들을 몽땅 다 copy해서 1억개를 paste해야 한다.

만약 이 카운터가 1억줄 짜리라면 전체 코드 줄수는 10,000,000,000,000,000줄로, 공간을 엄청나게 낭비하는 비효율적인 코드가 탄생할 것이다. 

또, 이 1억개의 카운터를 만들었는데 문제가 발생한다면?

1억개의 카운터를 몽땅 손봐줘야 할 것이다.

아마 개발자를 지향하는 사람들은 나처럼 '사회의 귀찮음을 해결하기 위해' 개발자가 되기로 결심한 사람이 많을 것이므로,

조금 더 똑똑하게 코드를 만들어 보자.

어떻게?

바로 이 카운터를 '사용자 정의 태그'로 만들어 주는 것이다.

Counter라는 이름의 사용자 정의 함수를 만들어 보자.

 

> app.js

function Counter(){
  return <div>
    <h1>Counter</h1>
    <button>+</button> 0 
  </div>
}

여기서 <div> </div>의 wrapping tag를 빼면 오류가 발생한다.

왜 에러가 발생할까?

리액트에서는 컴포넌트를 만드는 함수를 정의할 때에는

`반드시 단 하나의 단일 태그만을 리턴`해야 한다.

 

다시 돌아가서, 이제 app.js에서 <Counter></Counter>를 입력하면, 우리가 만들었던 카운터가 그대로 나오게 된다.

이제 이 카운터를 1억개 넣어도 코드가 1억줄만 나오게 되어 매우 간결(?) 하며, 오류 수정을 하기도 쉽다.

또, 컴포넌트의 이름과 내용을 매칭시키면서 이 태그가 어떤 태그인지 다른 사람이 확인하기도 좋다.

 

정리
1. 리액트는 `사용자 정의 태그`를 만드는 기술이다.
2. 사용자 정의 태그를 '컴포넌트'라고 한다.
3. 컴포넌트는 함수다.
4. 함수의 리턴값이 컴포넌트의 UI값이 된다.
5. 컴포넌트의 이름은 반드시 `대문자`여야 한다.
6. 리턴값은 반드시 '하나의 태그'로 시작해야 한다.

2. props란?

우리가 만든 컴포넌트를 조금만 더 바꿔 보자.

카운터를 1억개 쓰는데 모조리 다 똑같은 카운터라면, 존재의 의미가 있을까?

그래서 이제 <Counter title="xxx 카운터"></Counter>에 있는 title대로 렌더링을 하도록 바꿔보자.

일단 다음과 같이 컴포넌트를 바꾸고, 콘솔에 찍어보자.

function Counter(props){
	console.log(props)
    return (
    	<div>
        	<h1>카운터</h1>
            <button>+</button>0
        </div>
    )
}

function App(){
	return (
    	<div>
        	<Counter title ="xxx 카운터"></Counter>
        </div>
    )
}

콘솔에 다음과 같이 {title : 'xxx 카운터'}가 찍히는 것을 확인할 수 있다.

따라서 '카운터'라는 글자를 {props.title}로 변경하면 재렌더링이 된다.

{} 안에 점을 찍으면 '~의' 라는 뜻을 가진다.

이렇게 props에 여러 속성들을 줄 수도 있다. 이번엔 카운팅 초기값을 한 번 줘보자.

function Counter(props){
  console.log(props.title)
  return(
    <div>
      <h1>{props.title}</h1>
      <button>+</button>{props.initValue}
    </div>
  )
}

export default function App() {
  return (
    <div>
      <Counter title = "불면증 카운터", initValue = {10}></Counter>
    </div>
  );
}

initValue = "10"으로 해 준 사람들도 있을 수 있겠으나, 이렇게 하면 안된다.

"10"으로 할 시 리액트에서는 이 안의 문자를 스트링으로 인식하기 때문에,

중괄호 안에 숫자를 넣어서 타입을 number로 설정해 주어야 한다.

이제 우리는 깨달았다.

컴포넌트의 속성(title, initValue)을 적으면, 컴포넌트 함수(Counter)의 첫 번째 파라미터로(Counter()) 객체가 주입되는데, 그 객체의 프로퍼티 값(props)으로 속성의 값(title, initValue)이 전달된다는 것을.

 

그런데 갑자기 궁금한 점, 콘솔에는 왜 값이 2번씩 찍힐까?

> index.js

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

 

React.StrictMode는 애플리케이션 내의 잠재적인 문제를 알아내기 위한 도구로, 이걸 켜면 2번씩 실행된다. UI를 재렌더링 하지 않는다. 그러나 어짜피 build하면 한번만 실행된다.

 

이왕 index.js 파일을 열어본 김에, 그 위의 rootElement를 보면, html에서 id가 root인 태그 안에 우리의 html이 들어간다는 내용을 확인할 수 있다. 각자 콘솔창을 열어서 id=root인 태그가 있는지 확인 해 보자.

 

그런데, 우리가 만든 root 이외의 영역은 우리 파일 중 어디에 있을까?

 

> public > index.html

<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>

이 root 안에 우리가 만든 어플리케이션이 들어가도록 create-react-app이 구현을 해 놓은 것이다.

 

지금까지 한 내용들을 정리해보자.

1. props는 입력값이다.
2. props에 중괄호를 사용하면 js의 데이터 타입으로 주입되고, 따옴표를 사용하면 문자열로 평가된다.
3. 컴포넌트(함수)의 리턴값에서 중괄호를 사용하면, 변수로 간주된다. (=> {props.title})

지금까지 우리가 만든건 그냥 틀을 만든 것 뿐이고, 이제 실제로 버튼이 동작하게 만들어보자.

<button onClick = {function(){console.log(1)}}>+</button>

버튼을 누르게 되면 콘솔에 1이 찍히는 익명함수를 만들어 주었다. 작동도 잘 한다.

그런데 보기가 너무 불편하므로, 함수를 바깥으로 빼보자.

function up(){
	console.log(1);
}
<button onClick = {up}>+</button>{props.initValue}

 

이제는, up 함수가 실행될때마다 초기값을 하나씩 올려보자.

function up(){
	props.initValue = props.initValue + 1;
}
<button onClick = {up}>+</button>{props.initValue}

과연 정상작동이 될까? 결과는 런타임 에러를 뱉는다.

리액트에서, 컴포넌트 안에서는 컴포넌트 바깥의 값을 바꾸면 안되기 때문이다.

그러면, 다음과 같이 바꾸어 보자.

function Counter(props){
	let count = props.initValue;
    function up(){
    	console.log(1);
        count = count + 1;
    }
    return ( ... )
}

이 코드는 실행될까?

에러는 나지 않지만, 컴포넌트가 re-rendering 되지는 않는다.

사실 리액트에서는 함수가 다시 실행되지도 않았는데 변수인 count값이 변한다고 그냥 리렌더링을 발생시킬 이유가 없다.

 

그래서, 이제 지역변수 count를 '상태'로 승진시켜야 한다.

import { useState } from 'react';
function Counter(props){
  let countState = useState(props.initValue);
  console.log(countState)
  function up(){
   //count = count + 1;
  }
  return (...)
}

이 useState라는 친구로 10을 기본값으로 갖는, '상태'를 만들어 주었다. 이름도 상태에 맞게 countState로 바꿔주었다.

추가로, use~ 이런 함수들을 훅(Hook)이라고 한다.

그런데 이 useState의 형태는 어떻게 생겼을까? count의 로직을 주석 처리하고, countState를 콘솔에 찍으면 다음과 같이 나오게 된다.

콘솔에 찍어보니, 다음 3가지를 알 수 있다.

  1. '상태'는 '배열'이다.
  2. '상태'의 첫 번째 원소는 '상태의 값'이다.
  3. '상태'의 두 번째 원소는 '상태의 값을 바꿀 때 호출하는 함수'다.

그래서 첫 번째 원소는 '읽을 때', 두 번째 원소는 '바꿀 때' 사용한다는 것을 알아차릴 수 있다.

그래서 우리는 countState의 첫 번째 원소를 count로 정의하고, 두 번째 원소를 count를 바꿀 수 있는 setCount 함수로 정의한다. 

이해하기 힘든 사람이 있을 수도 있어, 전체 코드와 흐름을 다시 올려본다.

import {useState} from 'react';

function Counter(props){
 let countState = useState(props.initValue);
 let count = useState(0);
 let setCount = useState(1);
 function up(){
  setCount(count + 1);
 }
 return (
  <div>
   <h1>{props.title}</h1>
   <button onClick = {up}>+</button>{count}
  </div>
 )
}

function App(){
 return (
  <div>
   <Counter title = "불면증 카운터", initValue = {10}></Counter>
  </div>
 )
}

흐름을 글로 정리하자면 다음과 같다.

count를 return값에 배치하고, 이 count를 바꿀 수 있는 setCount라는 함수를 up함수 안에 배치한다.

우리가

1. 버튼을 클릭하면,

2. up함수가 실행되면서,

3. setCount가 count의 값을 바꾸고,

4.리액트는 전체를 바꾸지 않고 이 count 부분만 재렌더링한다.

 

만약 setCount(count+1)을 count = count + 1로 치환하게 되면 count부분이 재렌더링 되지 않는다.

리액트는 setCount라는 함수를 호출하고, 이 함수 안의 값이 이전과 달라졌을 때만 재렌더링 하기 때문이다.

만약 setCount(count + 1)을 setCount(11)으로 바꿔주면, 값이 불변하기 때문에 절대 재렌더링 되지 않는다.

그리고 이 불필요한 렌더링 방지는 리액트의 성능 향상에 큰 영향을 끼친다.

 

지금까지 한 것들을 복습해보자.

1. 리액트의 코드는 js가 아니라 jsx이다.
- jsx는 Js+태그를 쉽게 코드에 포함시킬 수 있도록 고안된 js 확장 언어다. onclick -> onClick, onchange -> onChange
2. 이벤트의 값은 함수다.
3. props의 값은 read only(어명), State의 값은 쓰기 가능(지방 방송)
4. state는 배열이다.
- 첫번째 원소는 값이다. 읽을 때 쓰고, 수정하면 안된다.
- 두번째 원소는 함수다. 수정할 때 호출한다.
5. 파라미터가 변경되었을 때만 컴포넌트가 다시 실행된다.
6. props, state가 바뀌면 컴포넌트가 다시 렌더링 된다.

이제 props란 무엇인지, state란 무엇인지, 그리고 왜 쓰이는지에 대해 알게 되었다.

승리의 박수 한번 치고, 다음으로 넘어가보자.

 

그리고 개인적으로, 스프링과 리액트 둘을 공부하면서, 프로그래밍의 몇 가지 공통점을 알게 되었다.

  1. 어느 언어를 택하든, 백엔드든 프론트엔드를 하든 '유지보수가 간편한 코드'와 '남이 알아볼 수 있는 코드'가 정말 중요하다는 것.
    1. '좋은 코드는 무엇인가'에 대한 답은 명확하지 않아도, '나쁜 코드는 무엇인가'에 대한 답은 명확하다.
    2. 지금 작동하는 코드가 어떤 동작을 하느냐를 메소드/컴포넌트 이름만 보고서도 알수 있는 것이 가장 좋다. 그래서 ~케이스라는 규칙이 등장하고, 변수 명명 규칙등이 생겼다.
    3. '객체'의 유무 차이는 2D와 3D의 차이다. 그만큼 객체로 인해 많은 것을 얻을 수 있고, 또 많은 것을 조심해야 한다.
  2. '중복되는 코드'를 기피하는 방법을 찾으려 많은 생각을 하자.
  3. 변수가 영향을 미치는 범위를 잘 생각해서 코드를 작성하자.

 

생활코딩 밤공부 React편을 듣고 정리한 내용입니다.

강의에 덧붙여 제가 이해한 내용을 같이 첨부하였습니다.

1편의 목차는 다음과 같이 구분하였습니다.

1. 개발의 흐름
2. 리액트란?
3. 리액트 세팅

1. 개발의 흐름

오르고 올라도 끝없이 올라야 하는 산처럼

개발 공부도 공부하고 공부해도 계속 새로운 것이 등장하기 마련이다.

정기적으로 내 위치를 체크하다 보면, 더 이상 공부를 좋아하는 사람이 없다.

산의 입구에서 산을 올려다보면 정상이 보이지 않는다. 막막하다.

그러나 산을 '만들러' 간다고 생각하면 막막하지 않다.

내가 채워넣는 만큼 산이 쌓여가는 것이기 때문이다.

 

2. 리액트란?

 

리액트 공식 홈페이지(react.dev)를 들어가면 다음과 같은 문구를 확인할 수 있다.

웹과 네이티브(모바일 어플리케이션)의 UI를 만들어 주는 라이브러리.

 

여기서 네이티브란 Android 또는 iOS 같은 어떤 구체적인 플랫폼만을 위해 만들어진 응용 프로그램을 네이티브 앱(Native App)이라고 한다. 그렇다면 리액트를 이용해서 웹과 모바일 앱을 만들 수 있나보다.

조금만 더 스크롤을 내리면, 다음과 같은 문구를 발견할 수 있다.

Create user interfaces from components : 컴포넌트(부품)을 이용하여 UI(User Interface)를 만들어 보세요.

 

컴포넌트란 무엇일까?

인터넷으로 찾아보면, 여러 개의 프로그램 함수들을 모아 하나의 특정한 기능을 수행할 수 있도록 구성한 작은 기능적 단위를 말한다고 한다.

 

더 쉽게

친한 동생인 pengoose에게 "컴포넌트가 뭐냐?"라고 물어보니까 다음과 같은 답을 해 주었다.

컴포넌트는 '객체'야.

이고잉님은 다음과 같이 컴포넌트를 정의했다.

 

컴포넌트란 '사용자 정의 태그'입니다.

무슨 소리인지는 더 공부하면서 이해해 가도록 하자.

 

결국 리액트 공식 문서에서 이야기하듯, 리액트는 '컴포넌트'라는 것을 이용하여 UI를 만들어가는 어플리케이션이기 때문에 '컴포넌트'가 가장 중요하다.

자바를 공부해 본 사람은 '객체'가 얼마나 중요한지 알 것이다. OOP(객체 지향 프로그래밍)가 자바의 핵심이듯, 객체(컴포넌트)가 리액트의 핵심이라면 아무래도 '컴포넌트'를 얼마나 잘 다루느냐가 리액트를 얼마나 잘 다루는지를 판별하는 척도가 될 것이라고 생각했다.

 

3. 리액트 세팅

보통 마라탕집에 가면 마라탕 메뉴를 고르는데 절반의 시간이 소요되고, 먹는데 나머지 절반을 소요한다.

과거의 프로그래밍도 똑같았다. 초기 리액트나 스프링에서 처음에 가장 고된 작업은 ' 초기 세팅'이었다.

나는 이제야 프로그래밍을 접하는 개발자 지망생이라 예전에 비해서는 많은 혜택을 받았지만,과거에 이고잉님이 처음 리액트를 배울 때는 초기 세팅에 일주일에서 한 달에 걸치는 시간을 소모했다고 한다.스프링 세팅에 많은 시간을 할애하는 개발자들을 보고 '스프링 부트'나 'start.spring.io'가 등장한 것을 보면 얼마나 개발자들이 고통받았을지를 짐작할 수 있다.리액트 또한 마찬가지로, 그래서 초기 세팅하는 시간을 절약해주기 위해 'create-react-app'이라는 것이 등장했다.create-react-app은 React를 간단하게 사용해 볼 수 있으며, 간단한 프로젝트를 만들 때 매우 간편한 도구이다.

 

설치는 간단하다. VSC 등 IDLE를 켜서 터미널을 열고(보통 맥이면 Zsh, 윈도우는 Bash를 추천한다.) 다음과 같은 명령어를 입력하면 된다.

npx create-react-app .

NPM vs NPX vs YARN

나는 부트캠프를 수료할 때 프론트엔드 동료들이 늘 'npm run dev'로 시작하는 것만 봐서, 처음에는 npx가 오타인 줄 알았다. 그러나 npm, npx, yarn 모두 다 다른 명령어이다.

  • npm : 자바스크립트 런타임 환경 Node.js의 기본 패키지 관리자(Node Package Manager)
  • npx : NPM에 속해있는 NPM 패키지의 실행 도구 (execute Npm Package Binaries)
    • Node.js로 만들어진 앱을 내 컴퓨터에 설치하고 실행까지 해주는 명령어가 npx이다.
    • npm과의 가장 큰 차이점은, npx는 패키지를 실행만 되게 해주면 OK기 때문에 가볍게 사용할 수 있다는 것이다.
    • 어떤 프로젝트를 다운받았을 때, NPM으로 실행하면 모든 패키지를 전부 다운받아 설치하고 실행하지만, NPX는 실행만 되면 되기 때문에 실행에 필요한 패키지만 다운받아 설치하고 실행한다.
  • yarn : 안전하고 안정적이며 재현 가능한 프로젝트
    • yarn은 2016년 페이스북에서 개발한 패키지 관리자로, 큰 틀에서는 NPM과 똑같은 역할을 한다.
    • npm 레지스트리와 호환하면서 NPM보다 훨씬 더 빠르고 안전하게 안정적인 패키지 설치를 할 수 있다.

 

맨 뒤에 찍은 점은 무엇인가?

온점을 찍지 않으면 새 디렉토리에 기본 이름으로 새 react 어플리케이션이 생성되고, 온점을 찍으면 현재 디렉토리에 새로운 react 어플리케이션을 생성한다는 뜻이다. 그리고 현재 디렉토리를 프로젝트의 루트로 사용한다.

쉽게 말해서, 리액트 개발환경을 '현재 폴더'에 만든다는 것이다.

따라만 가면, Compiled successfully라는 명령어가 뜨면서 설치가 성공한 것을 볼 수 있다.이제 새 터미널을 켜서 'npm run start'라는 명령어를 입력해 보자. 크롬에서 localhost:3000으로 우리의 웹페이지에 접속할 수 있게 된다.

 

Entry file

npm 명령어를 실행했을 때 가장 먼저 실행되는 파일을 엔트리 파일이라고 하고, 리액트에서는 app.js를 가리킨다.

> index.js

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

index.js 파일에 들어가면 다음과 같은 코드를 볼 수 있다.

여기서의 <App />가 이 화면을 렌더링함을 알 수 있다.또한 상단의

import App from './App';

 

를 보면, App.js에서 파일을 가져와서 이 화면에 렌더링한다는 것을 알 수 있다.

이제 App.js를 다 지우게 되면 깔끔한 빈 화면이 나오고, 이게 리액트 초기 세팅의 끝이다.

 

이제 '컴포넌트'란 무엇인지에 대해 알아보자.

 

그 전에, 몇 가지 추가 정보만 더 알아보고 가자.

 

1. Stackblitz.com

stackblitz에서는 무료로 리액트를 경험해볼 수 있는 환경을 제공한다. 리액트 뿐 아니라 Node 기반의 여러 어플리케이션을 체험해볼 수 있도록 제공하니, 내 컴퓨터에 vsc가 깔려있지 않다고 좌절하고 라이트하게 사용해보자.

 

2.공학자와 과학자

공학자는 '되면 일단 OK다'는 태도를 지니고,

과학자는 '되면 왜 되는지, 안되면 왜 안되는지'를 살펴본다.

보통 프로그래밍은 과학자와 공학자의 스탠스를 박쥐처럼 모두 취해야 한다.

각자의 장단점은 뚜렷하고 과학자처럼 왜 되는지, 왜 안되는지를 뜯어보는 자세는 매우 좋다.

그러나 때로는 한 발짝 더뎌지는것보다 한 걸음 더 나아가서 나중에 뒤를 돌아보는것도 좋다.

지금은 왜 그런지 몰랐던 문제들이, 나중에 가면 당연하게 느껴지거나 그때 이유를 알아보는 것도 좋으니 일단 나아가보자.

 

3. 배포

다양한 스크립트

내가 만든 프로젝트를 나만 가지고 어화둥둥 보는 것은 아무런 의미가 없다.만든 어플리케이션을 배포해볼 수 있다.package.json은 node.js 기반 프로젝트에서 굉장히 중요한 정책 파일로, 들어가보면 "scripts"에 위와 같은 메뉴들이 있다.우리가 아까 했던 npm run start에서 start는 저 react-scripts를 시작한다는 뜻이다.다음과 같은 명령어를 사용해보자.

npm run build

IDLE를 확인하면, build라는 디렉토리가 새로 생성이 되면서 안에 여러 파일들이 생성되는 것을 볼 수 있다.

이 build 파일로 모두가 볼 수 있는 홈페이지를 만들 수 있다.

윈도우의 절대경로로 폴더를 열어서 build 폴더를 vercel이나 netlify에 들어가서 deploy해보자.(netlify가 조금 더 간편)

 

이제 내가 만든 프로젝트를 모든 사람이 다 확인할 수 있다.

 

정말 많은 것을 배웠으니, 승리의 박수를 다같이 한번 치고 다음으로 넘어가보자.

+ Recent posts