Today I Learned
- React 입문 강의 수강
- My Todo List 만들기 과제 수행
React 입문 강의
명령형 프로그래밍과 선언형 프로그래밍
명령형 프로그래밍 (DOM)
// 순수 javaScript 명령형 코드
const root = document.getElementById('root');
const header = document.createElement('h1');
const headerContent = document.createTextNode(
'Hello, World!'
);
header.appendChild(headerContent);
root.appendChild(header);
명령형은 어떻게(How)를 중요시 여겨서 프로그램의 제어의 흐름과 같은 방법을 제시하고 목표를 명시하지 않는 형태이다. 명령형으로 작성된 코드의 경우 Hello, World!를 출력하기 위해 컴퓨터가 수행하는 절차를 일일이 코드로 작성해주어야 한다.
선언형 프로그래밍 (React)
// React 코드 (선언적인)
const header = <h1>Hello World</h1>; // jsx
ReactDOM.render(header, document.getElementById('root'));
선언형은 무엇(What)을 중요시 여겨서 제어의 흐름보다는 원하는 목적을 중요시 여기는 형태이다. React 코드의 경우 내가 UI을 선언하고 render 함수를 호출하면 React가 알아서 절차를 수행해 화면에 출력해준다. 즉, 화면에 어떻게 그려야할지는 React 내부에 잘 숨겨져 추상화되어 있는 것이다.
렌더링이란?
리액트에서 렌더링이란, 컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미한다.
- 렌더링 일으키는 것은 (triggering)- UI를 주문하고 주방으로 전달하는 것
- 렌더링한다는 것은 (rendering)- 주방에서 컴포넌트가 UI를 만들고 준비하는 것
- 렌더링 결과는 실제 DOM에 커밋한다는 것은 (commit) - 리액트가 준비된 UI를 손님 테이블에 올려놓는 것
렌더링이 발생하는 경우 (triggering)
- 첫 리액트 앱을 실행했을 때
- 현재 리액트 내부에 어떤 상태(state)에 변경이 발생했을 때 (리렌더링)
- 컴포넌트 내부 state가 변경되었을 때
- 컴포넌트에 새로운 props가 들어올 때
- 상위 부모 컴포넌트에서 위에 두 가지 이유로 렌더링이 발생했을 때
익명 함수 이용해서 counter 만들기
import React, { useState } from "react";
function App() {
// 숫자의 초기 값을 0으로 설정함
const [num, setNum] = useState(0);
return (
<div>{num}
<button onClick={() => {setNum(num + 1)}}>+ 1</button>
<button onClick={() => {setNum(num - 1)}}>- 1</button>
</div>
);
};
export default App;
한 번만 사용하는 함수이기 때문에 익명 함수로 기능을 구현할 수 있다.
컴포넌트에 css 적용하기
인라인으로 style 적용하는 방법
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>
);
};
export default App;
JSX에서 css 코드를 작성할 때는 주의할 점이 있다. css 속성 명을 '-' 없이 camelCase로 작성해야 한다.
ex) align-items -> alignItems
또한 속성 값은 항상 따옴표로 감싸고 뒤에 콤마를 찍는다.
css 파일을 분리해서 import 하는 경우
// src/App.js
import React from "react";
import Square from "./components/Square.js";
import "./App.css"; // 🔥 반드시 App.css 파일을 import 해줘야 합니다.
function App() {
return (
<div className="app-style">
테스트
</div>
);
}
export default App;
import한 css 파일은 기존과 동일하게 클래스에 따라 작성하면 된다.
map( ) 이용해서 중복된 요소 제거하기
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;
div를 다섯 개 만들어서 과일 이름을 수정하는 방식 말고 map 메서드를 이용해서 구현해봤다. 일단 JSX 안에 자바스크립트 코드를 작성할 것이기 때문에 { }로 감싸고, map 메서드가 모든 배열을 순회하며 각 배열의 값을 동적으로 컴포넌트에 추가해주고 있다. 아마 map( ) 안에 화살표 함수가 있고, 인자에 vegetableName을 담아서 그 값을 하나씩 div 안에 넣어주는 방식인 거 같다.
객체가 담긴 배열 다뤄보기 (14강 자료 참고)
객체가 담긴 배열을 이용해서 추가/삭제 기능까지 구현하기
import React, { useState } from 'react';
import './App.css';
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}>{children}</button>;
}
function User(props) {
return(
<div className="fruit">
<div>{props.user.age}살 - {props.user.name}</div>
<CustomButton color="red" onClick={() => props.handleDelete(props.user.id)}>삭제하기</CustomButton>
</div>
);
}
function 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('')
const addUserHandler = () => {
const newUser = {
id: users.length + 1,
age: age,
name: name,
};
setUsers([...users, newUser]);
};
const deleteUserHandler = (id) => {
// 만약에 삭제하려는 게시물 id가 3번이면 3번이 아닌(3번을 제외한) 애들만 남겨서 newUserList를 만들어라!
const newUserList = users.filter((user) => user.id !== id);
setUsers(newUserList);
}
return (
<div>
<div className="container">
{users.map((user) => {
return <User user={user} key={user.id} handleDelete={deleteUserHandler}/>
})}
</div>
<input value={name} placeholder="이름을 입력해주세요"
onChange={(event) => setName(event.target.value)} />
<input value={age} placeholder="나이를 입력해주세요"
onChange={(event) => setAge(event.target.value)} />
<CustomButton color="blue" onClick={addUserHandler}> 추가하기</CustomButton>
</div>
);
}
export default App;
user 삭제하기 - filter 사용
const deleteUserHandler = (id) => {
const newUserList = users.filter((user) => user.id !== id);
setUsers(newUserList);
};
내가 이해가 되지 않았던 것은 filter 메서드의 조건 부분이었다. 왜 유저의 아이디가 지금 내가 설정한 아이디와 같지 않으면 그것만 filter해서 새로운 유저 리스트를 만들어라는 것인지 이해가 되지 않았다. 그런데 팀원 분의 설명을 듣고 나니 이해를 할 수 있었다. 만약에 삭제하려는 게시물 id가 3번이면 3번이 아닌(3번을 제외한) 애들만 남겨서 newUserList를 만들어라!라고 이해하면 된다.
나이가 20세 이상인 경우만 렌더링 하기
{users.map((user) => {
if (user.age > 20) {
return <User user={user} key={user.id} handleDelete={deleteUserHandler}/>
} else {
return null;
}
})}
map( ) 안에서 조건문을 작성해주면 된다.
map( )을 사용하여 배열을 렌더링할 때 key 값을 넣어야 하는 이유
<div>
{users.map((user) => {
return <User user={user} key={user.id} />;
})}
</div>
key가 필요한 이유는 React에서 컴포넌트 배열을 렌더링 했을 때 각각의 원소에서 변동이 있는지 알아내려고 사용하기 때문이다. 만약 key가 없다면 React는 가상돔을 비교하는 과정에서 배열을 순차적으로 비교하면서 변화를 감지하려 한다. 하지만 key가 있으면 이 값을 이용해서 어떤 변화가 일어났는지 더 빠르게 알아낼 수 있게 된다. 즉, key값을 넣어줘야 React의 성능이 더 최적화된다는 것이다.
컴포넌트 분리하기
만약에 Button 컴포넌트를 Button.js 파일에 분리한다고 하면 Button 컴포넌트를 옮긴 후에 하단에 export default Button;을 작성한다. 그리고 App.js로 돌아와서 상단에 import Button from './components/Button.js';를 추가한다.
exprort와 export default의 차이
export default
1. 일반적으로 해당 모듈엔 하나의 개체(변수, 클래스, 함수 등)만 있다는 의미로 받아들여진다.
2. 따라서 해당 모듈의 전체 개체를 export 한다는 의미를 갖는다.
3. 원하는 이름으로 import가 가능하다.(ex. import ThisIsSample from "경로")
export
1. 복수의 개체가 있는 라이브러리 형태의 모듈에서 가져오기 할 때 사용된다.
2. 특정 개체만 가져오는 게 가능하다.
3. 원하는 이름으로 import가 불가능하다.
출처 https://quark21.tistory.com/314
절대경로와 상대경로
1. 절대경로
- 어떠한 웹페이지나 파일이 가지고 있는 고유한 경로를 말한다.
- 예를 들어 http://www.google.com, C:\users\document\untitled.jpg 등을 모두 절대 경로라고 한다.
2. 상대경로
- 파일이 위치한 곳을 기준으로 경로를 파악한다.
- 참고 ./ : 현재 위치
- ../ : 현재 위치의 상단 폴더
- / : 루트 (가장 최상단의 디렉토리)
Vercel로 배포하기
- 레포지토리를 생성하고 Vercel에서 로그인 후 레포지토리만 선택하면 자동으로 배포가 된다.
- 수정사항을 반영하고자 할 때는 레포지토리에 commit, push 하면 자동으로 재배포를 시작한다.
React 입문 과제-My Todo List 만들기
CSS로 구조 잡기
일단 CSS로 기본적인 구조를 잡았다. 이렇게 내가 직접 코드를 작성해야 전체적인 구조를 파악할 수 있다. 일단 하나의 App.js 파일에 컴포넌트를 Header, Form, List로 나눠서 모두 App 컴포넌트에 넣었다. 컴포넌트를 js 파일까지 분리하는 것은 마지막에 수정할 예정이다.
function App () {
return (
<div className="wrap">
<Header />
<Form />
<List />
</div>
);
};
function App () {
const [todo, setTodo] = useState([
{id: 1, title: '운동하기', content: '테스트1'},
{id: 2, title: '마라탕 먹기', content: '테스트2'},
{id: 1, title: '잘 쉬고 공부하기', content: '테스트3'},
]);
return (
<div className="wrap">
<Header />
<Form />
{todo.map((list) => {
return <List list={list} key={list.id} />
})}
</div>
);
};
일단 임시로 배열을 만들어서 Form 컴포넌트에 map 메서드를 돌려봤는데 투두리스트 카드만 3번씩 반복되고 내가 배열에 넣은 제목과 내용이 뜨지도 않는다.
<div className="list_text">
<h2 className="todo-title">{props.list.title}</h2>
<p className="todo-content">{props.list.content}.</p>
</div>
App 컴포넌트에서 List 컴포넌트에 전달한 props 테이터로 투두리스트 배열의 제목과 내용을 불러오는 데는 성공했다. 근데 working, done이라고 적힌 카드 전체를 불러오기 때문에 List 안에 컴포넌트를 하나 더 만들어주기로 했다.
이 과정에서 문제가 생겨서 웹페이지에 아예 렌더링이 되지 않았고 튜터님께 도움을 요청해서 List 안에 컴포넌트를 하나 더 만드는 방법 말고 App 컴포넌트에서 해결하는 방법을 제안해주셨다.
function App () {
(생략)
return (
<div className="wrap">
<Header />
<Form title={title} content={content} addContent={addContent} addHandler={addHandler}/>
<div className="list">
<h2>Working.. 🔥</h2>
<div className="list-container">
{todo.map((list) => {
return <Todo deleteHandler={deleteHandler} list={list} key={list.id} />
})}
</div>
</div>
<div className="list">
<h2>Done..! 🎉</h2>
<div className="list-container">
{todo.map((list) => {
return <Todo deleteHandler={deleteHandler} list={list} key={list.id} />
})}
</div>
</div>
</div>
);
};
이제 여기서 추가 기능을 구현했고 삭제 기능은 코드는 작성했지만 작동은 하지 않는 상태고 리스트 완료 체크하면 done으로 넘어가는 것, done에 버튼을 완료 -> 취소로 변경하는 것 +) css 다듬기까지 해야 한다. 내일 오후에 제출인데 아직도 많이 남았네..핫
일단 오늘은 여기까지 하고 마무리한다. 피곤해 😤
재미있었던(?) 에러
.container {
display: flex;
padding: 100px;
flex-direction: row;
gap: 12px;
};
.fruit {
padding: 20px;
border: 1px solid green;
border-radius: 10px;
height: 100px;
width: 100px;
display: flex;
align-items: center;
justify-content: center;
};
팀원 분의 문제를 같이 해결하면서 알게 된 사실. 이렇게 각 class 중괄호마다 세미콜론을 붙이면 container만 css가 적용되고 fruit은 css가 적용되지 않는다! 내 생각에는 css 파일에서 중괄호 뒤에 세미콜론을 찍으면 거기서 css 파일이 끝난다고 생각하는 거 같다. 정말 문장 부호 하나 때문에도 에러가 발생하는 어지러운 코딩의 세계😵
회고
오늘은 스파르타 리액트 입문 강의를 다 듣고 본격적으로 투두리스트 구현하는 과제를 시작했다. 초반에 컴포넌트 때문에 막히는 부분이 있어서 튜터님께 질문하러 갔는데 갑자기 내가 물어본 것 외에도 이것도 저것도 가르쳐주셔서 과제 진행 상황이 생각보다 빨라졌다ㅋㅋ 아예 튜터님과 함께(사실 로봇처럼 따라 침 ㅎㅎ) 코드를 작성해보는 건 흔한 기회가 아니니까 재미있었다! 역시 강의 들으면서 이론만 공부할 때 보다 실습하면서 여기저기 문제 해결하는 과정이 훨씬 재밌다. 또 욕심이 생겨서 늦게까지 남아서 코드를 수정했다. 과제하면서 멀게만 느껴졌던 리액트와 가까워지는 기분이 들기도 한다~