터미널에 다음 명령어 입력

yarn add @fortawesome/fontawesome-free

기존에 HTML에서 쓰던 것처럼(<i class="fas 뭐시기 등등")  하고 싶은 경우는 index.js에서 다음과 같이 작성해준다.

import '@fortawesome/fontawesome-free/js/all.js';

그리고 폰트어썸에서 쓰고 싶은 거 카피해서 그대로 쓰면 됨. 단 class속성은 JSX문법답게 className으로 바꿔서 쓸 것

 

아니면 리액트답게 쓰는 방법?으로 컴포넌트 형식으로 폰트어썸을 사용할 수도 있음. 사용하고자 하는 jsx파일에서

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

위 코드를 입력해준 후,

const Component = (props) => {
  return <FontAwesomeIcon icon={faHome} />
};

이런 식으로 쓰면 된다.

 

패키지 매니저를 yarn이 아니라 npm등을 쓸 경우는 yarn add 대신 npm install등으로 대체하면 됨

PC방 알바하던 중이었다. 잠깐 시간이 비어서 카운터 컴퓨터로 유튜브를 켰는데 알고리즘으로 침착맨의 "가장 쉽게 질리는 음식 16강"이란 제목의 영상이 떴다. 영상을 보다가 "나도 한 번 16강 웹사이트를 만들어볼까?"라는 생각으로 시작하게 됐다.


어떤 식으로 할 건지 구상해봤다.

우선 처음 시작하면 16개의 아이템들이 랜덤하게 섞이고, 2개씩 사용자 화면에 출력된다. 사용자가 선택한 음식들은 별도의 배열에 담기고, 16개의 아이템에 대해 모두 셀렉이 끝나면 8강을 진행. 이후 4강 진행, 결승.

 

난이도가 있는 프로젝트는 아니라고 생각되지만, 강의 실습이 아니라 처음으로 스스로 하는 프로젝트가 될 것 같다. 작년 멋사에서 Django로 플젝할 때와는 다른 설렘이 생기는 듯. 역시 강의들으며 실습보단 스스로 뭔가 만드는 게 더 재밌는 건가 싶다 ㅋㅋ

 

일단 이 플젝은 내가 리액트로 처음 하는 개인 플젝이므로, 이 플젝으로 얻고 싶은 소소한 목표는 다음과 같다.

 

1. 기본적인 컴포넌트 다루는 법 익히기 

: 강의 실습이 아니라 내가 스스로 만들면서 하는 거니까 컴포넌트의 개념이 좀 더 와닿지 않을까 생각한다.

2. json파일 다루기

: 대부분의 웹사이트는 json배열 등을 가공하고 출력하고 뭐 그런다고 한다. 강의에서는 API로 데이터를 받아오기 전에 mock데이터(샘플로 쓸 데이터를 mock데이터라고 부른다고 하더라)를 만들어서 쓰던데, 나도 이 플젝을 통해 json파일 등을 다루는 데 좀 익숙해지면 좋겠다. 작년에 Django로 해커톤할때도 json을 다루는 일이 종종 있었기 때문..

3. API로 데이터를 받아오는 것 이해하기

: 사실 js를 공부하고 리액트를 시작한게 아니라 리액트와 js를 병행하면서 그 때 그 때 모르는 걸 찾아보는 식으로 하기 때문에 fetch, async, await, premise? 이런 거 잘 모른다. 이 플젝을 하면서 이런 게 뭔지 이해하자. 일단 처음엔 나도 mock.json만들고 다루다가 좀 가닥이 잡히면 API로 데이터 받아와보는 식으로 해야겠다.

 


# 일단 mock.json만들자!

일단 샘플 데이터들이 있는 mock.json을 만들기로 했다. 근데 시작부터 나름 큰 난관에 봉착.

 

"도대체 어떤 16강 월드컵을 만들지?"

 

킹받는 롤 챔피언 16강 만들까? 이런 거 고민하다가 카페에서 하던 중이라 그런지 갑자기 가장 먹고 싶은 디저트 16강으로 정했다.ㅋㅋㅋㅋㅋ

 

나름 샘플 만드는 과정도 재밌었다. 디저트를 뭘로 하지?부터 이미지는 다운받아서 로컬에 넣을까 하다가 그냥 구글에 검색해서 이미지 주소 복사해서 쓰기로 했다. 근데 이거 저작권에 문제 생기나..? 문제 생기면 프로젝트 폐기 이런건가..

 

mock.json은 다음과 같이 만들어졌다.

배열 형태고 각 원소?들은 id, name, imgUrl속성?을 갖도록. 이제 이 놈을 App.js에서 다음과 같이 임포트했다

import mock from "../mock.json";

리액트는 import를 활용해 모듈이나 파일을 추가하듯이 이미지를 추가할 수 있다.  리액트에서 로컬에 있는 이미지 등을 다룰 땐 저렇게 임포트해서 경로를 쓰는게 더 낫다 카더라. 아니면 오류가 난다꼬..json도 저렇게 import하면 mock이란 이름으로 쓸 수 있다.

 

이제 이 놈들을 시험 삼아서 화면에 띄워보기로 했다. 배열의 map메소드를 활용하면 배열의 각 아이템들에 대한 가공?이 가능하고, 이 map메소드 안에서 JSX요소를 리턴하면 마치 여러 개의 JSX요소를 추가한 것처럼 동작한다. App에서 다음과 같이 map메소드를 쓰면서 DessertItem 컴포넌트를 만들도록? 했다.

function App(){
    return(
        <div>
            {mock.map((dessert) => {
                return(
                    <li key={dessert.id}>
                        <DessertItem item={dessert}/>
                    </li>
                )
            })}
        </div>
    )
}

아! 그리고 리액트에서 배열을 다룰 때는 저렇게 원소들에 key속성을 넣어줘야 하고, 속성값으로는 각 요소를 구분지을 수 있는 값을 넣어야 한다.  공식 문서에 따르면 key는 리액트가 어떤 항목을 추가, 변경, 또는 삭제할지 식별하는 것을 돕는다고 함. 따라서 배열의 인덱스를 key로 쓰는 멍청한 짓은 하면 안 됨. 배열 요소들의 순서가 바뀔 수도 있으니까 인덱스는 고유한 값이 아니기 때문!

 

암튼 DessertItem는 다음과 같이 작성해 디저트 사진과 이름을 볼 수 있도록 했다.

function DessertItem({item}){
    return(
        <div>
            <h3>{item.name}</h3>
            <img src={item.imgUrl} alt="디저트 사진" />
        </div>
    )
}

이제 출력된 모습!

 

별 거 안했지만 그래도 혼자 해봤다는 게 조금 뿌듯..뿌듯할 레벨은 아니지만 ㅎㅎ..

암튼 이제 시작이다! 잘 만들어보자

 

리액트는 state와 props를 통해 데이터마다 다른 화면을 보여줄 수 있었다. 이번엔 state가 바뀔 때마다 리액트가 렌더링하는 방식을 살펴보겠다.

 

우선 결론적으로 말하자면, state가 바뀔 때마다 그 state를 갖고 있는 컴포넌트 자체를 새로 렌더링한다. 즉 그 컴포넌트를 통째로 화면에  다시그린다는 것. 이 때 쉽게 생각할 수 있는 문제점은 "아니 그럼 컴포넌트를 통째로 다시 그린다면 컴포넌트 내에서 안 바뀌는 부분도 다시 그린다는 건데 이건 좀 낭비 아냐?"이다.

 

이런 문제를 해결하기 위해, React에선 Virtual DOM(가상 돔)이란 것을 활용한다. 리액트 내에서 DOM Tree를 본따서 만든 것인데, 이를 통해 Element를 새로 렌더링할 때 실제 DOM Tree에 바로 반영하는 게 아니라 일단은 Virtual DOM에 적용한다. 이후 state 변경 전의 Virtual DOM과 변경 후의 Virtual DOM을 비교해, 실제로 바뀐 부분만 찾아낸 다음에 비로소 그 부분들만 실제 DOM Tree에 적용한다. 똑똑하게도 바로 화면을 바꾸는 게 아니라 화면을 바꿀 준비를 하고 어디어디가 바뀌는지 파악한 다음 그 때 바꾸는 것.

 

이런 방식으로 렌더링하는 것은 크게 2가지의 좋은 점이 있다.

 

1. 개발자가 직접 DOM을 신경쓸 필요가 없으니 단순하고 깔끔한 코드를 작성할 수 있다는 것. 무슨 데이터를 어떻게 보여줄 지만 신경쓰면 된다.

2. 변경사항들을 리액트가 적당히 모아 처리할 수 있다는 것. 브라우저의 한정적인 자원을 효율적으로 이용할 수 있다는 것이다.

 

참고로 이 Vitrual DOM은 리액트에만 있는 개념은 아니다. 그냥 여기선 Virtual DOM을 사용하면 효율적인 화면 처리가 가능하다는 것만 알아두면 될 것 같다. 

 

 

state?

: 리액트에서 데이터를 다룰 때 사용하는 것으로, props가 부모 컴포넌트에서 자식 컴포넌트로 값을 전달할 때 사용하는 것이었다면 state는 컴포넌트 자기 자신이 갖는 값을 의미한다. props의 주인은 부모이지만 state의 주인은 나! 인 느낌.

 

이 state의 값이 바뀔 때마다 얘를 갖고 있는 컴포넌트들이 새로 렌더링되며, state는 다음과 같이 만들 수 있다.

import { useState } from 'React';

function Component(){
	const [num, setNum] = useState(1);
    
	return(
    	<>
        </>
	)
}

export default Component

Component란 컴포넌트에서 num이란 이름의 state를 만든 모습이다. 보통 이렇게 Destrucrturing 문법으로 작성한다. 초기엔 useState를 통해 초깃값을 할당하는데 파라미터로 준 값이 초깃값이 된다. setNum은 setter함수라고 부르며 보통 state이름 앞에 set을 붙이고 camel case로 명명한다. state를 변경하는 것은 기존에 하던 것처럼 num = 5 이런 식으로 하면 안되고, 이 setter함수를 이용해야 한다. 예를 들어 setNum(5)를 해야 num이란 state가 5로 바뀐다. 

 

 

참조형 state?

js를 공부하다보면 참조형 변수라는 걸 만나게 된다. 기본형 변수는 변수에 값을 그대로 할당하는 반면 참조형 변수는 값의 주소를 할당한다는 점(즉 참조한다)에 차이가 있다. 참조형의 예로는 객체, 배열 등이 있다.

 

예를 들어 let a = 1; 을 하면 a라는 변수를 위한 공간이 메모리의 100번지에 마련되고, 100번지에 해당하는 공간에 1이란 값이 들어간다. 즉 값이 그대로 할당되는 것. 그러나 let b = [1, 2, 3]을 하면 b라는 변수를 위한 공간이 메모리의 101번지에 마련되지만 이 101번지에 해당하는 공간에 [1, 2, 3]이 아니라 메모리 어딘가에 만들어둔 [1, 2, 3]의 주소가 할당되는 것이다.

 

즉 js에선

let b = [1, 2, 3];
let c = b;
c[2] = 5;

console.log(b);
console.log(c);

 

출력되는 두 결과가 [1, 2, 5]로 같다. 

 

암튼, 리액트도 js를 기반으로 만들어진 놈이니 state를 다룰 때 state를 배열같은 걸로 해준다면, 참조형을 다루는 것이므로 예기지 못한 상황에 직면할 수 있다.  

 

다음과 같은 state를 만들었는데, 배열을 사용하는 놈이라고 하자.

const [list, setList] = useState([]);

이 때 다음 코드를 실행한 결과는 무엇이 될까?

list.push(5)
setList(list)

당연히 이 코드를 작성한 사람이 원한 것은 list에 5를 추가하는 것인데, setter함수로만 list를 조작할 수 있으니 push를 해주고 setList를 통해 list를 다시 설정해주는 것. 그러나 이렇게 하면 list state에는 아무런 변화가 없다..!

list는 배열이니까 주소를 갖고 있는데, push하던 뭘하던 주소엔 변화가 없으니까 리액트는 상태가 바뀌었다고 판단하지 않는 것.

 

그래서 참조형 변수를 state로 활용할 때 state를 변경하고 싶다면 새로운 참조형 변수를 만들어야 한다. 위와 같은 상황에서 가장 쉬운 방법은 Spread문법을 활용하는 것.

setList([...list, 5]);

 

 

※ spread문법

펼치다 라는 뜻을 갖는 문법.

let a = [1, 2, 3, 4];가 있다고 하자. a는 배열이다. 그럼 a를 펼치면 뭐가 되는가? 1, 2, 3, 4가 된다.

...은 그런 '펼치는' 역할을 해준다.

list라는 배열이 있고 [1, 2, 3, 4]일 때, 여기에 5를 push한 새 배열을 만들고 싶다. 그러면 [...list, 5]를 해주면 된다. list를 펼친 1, 2, 3, 4가 그대로 ...list에 들어간다고 생각하면 됨.

 

객체에서도 spread문법을 활용가능하다. 다음처럼. 얘도 똑같~~이 펼치다 라는 뜻에 주목해서 보면 됨.

const obj1 = {
    name : "JSH",
    age : 24
}

const obj2 = {
   ...obj1
   gender : "man"
}

// obj2는 name, age, gender프로퍼티를 가지며 value는 각각 "JSH", 24, "man"

사실 JSX는 실제로 실행될 때 javascript코드로 바뀌어서 실행된다. 그러면 당연히 JSX에 javascript코드를 넣을 수도 있다.

다음과 같은 코드가 있다고 하자.

import ReactDOM from 'react-dom';

ReactDOM.render(
  <>
    <h1>안녕 리액트!</h1>
  </>,
  document.getElementById('root')
);

여기서 리액트란 단어를 name이라는 javascript 변수로 바꾸려고 한다. 그러면

import ReactDOM from 'react-dom';

const name = "리액트";

ReactDOM.render(
  <>
    <h1>안녕 {name}!</h1>
  </>,
  document.getElementById('root')
);

이렇게 중괄호를 이용해 name이란 javascript변수를 JSX 문법에 사용가능하다. Django에서 템플릿 변수를 사용하는 것과 비슷.

 

사실 이 중괄호엔 변수 뿐만 아니라 javascript로 쓰인 표현식은 모두 사용가능하다. 예를 들어, 문자열의 문자들을 모두 대문자로 만드는 toUpperCase()메소드도 중괄호 안에서 사용가능하다.

import ReactDOM from 'react-dom';

const name = "reactplease";

ReactDOM.render(
  <>
    <h1>안녕 {name.toUpperCase()}!</h1>
  </>,
  document.getElementById('root')
);

React에선 JSX문법을 사용하므로, 이벤트 핸들러를 등록할 때 addEventListener보단 요소의 속성값으로 이벤트 핸들러를 등록할 때가 많다. 이렇게 특정 태그의 속성값으로도 중괄호를 활용해 javascript를 사용가능하다.

import ReactDOM from 'react-dom';

const name = "reactplease";
function func(){
  alert("s");
}

ReactDOM.render(
  <>
    <h1>안녕 {name.toUpperCase()}!</h1>
    <button onClick={func}>버튼</button>
  </>,
  document.getElementById('root')
);

참고로 중괄호안에서는 javascript 표현식만 사용가능하므로, if문, for문, 함수선언등과 같은 것은 할 수 없다.

+ Recent posts