
리액트 : 사용자 인터페이스를 구축하기위한 라이브러리.
리액트를 왜 사용할까? HTML, CSS, JS로도 사용자 인터페이스를 구축하는것이 가능하다. 하지만 리액트를 사용하면 복잡한 웹사이트를 좀더 간편하게 웹 사이트를 구축할 수 있다. 쓸데없는 세부사항을 신경쓰지 않아도 되며 자신의 비즈니스 로직만 생각하면 된다. 어딘가에 무언가가 발생한다면 페이지를 업데이트 하는 복잡한 과정을 생각하지 않아도 되는것이다.
이러한 과정에 핵심이되는 개념이 컴포넌트.
컴포넌트는 재활용을 할 수 있다. 구성요소를 분산시킬 수 있다는 이유도 있음.
우리가 알고있는 함수는 컴포넌트랑 비슷한 개념이다. 이 컴포넌트를 가지고 섞고 적용시켜서 사용자 전체 인터페이스를 구성하기도 한다.
우리는 웹페이지를 만들때 HTML + CSS + JavaScript 를 흔히 사용한다.
리액트의 핵심 개념인 컴포넌트를 만들때도 HTML + CSS + JAvaScript를 사용하여 만드는데, 이런 모든 컴포넌트들을 조합하여 전체 사용자 인터페이스를 만든다.
정리하자면 리액트는 컴포넌트가 전부고 재활용이 가능하며 반응성이 있는 웹사이트를 만들 수 있다. 또한 리액트는 그런 컴포넌트를 만들때 “선언적 방식”을 사용한다.
아직은 리액트를 다룬지 얼마 안되어서 와닿지 않을 수 있지만
리액트는 항상 원하는 최종 상태를 정의한다.
라는 것을 머리에 넣어보자.
리액트는 이런 최종 상태를 정의할때 어떤 요소가 추가되어야 하는지, 삭제되어야 하는지, 업데이트되어야 하는지를 알아낸다. 바닐라 자바스크립트를 사용할때처럼 직접 작성할 필요가 없게되는것이다.
개발자는 단지 리액트와 리액트 컴포넌트를 사용할때 어떤 조건에서 어떤 상태를 사용해야하는지만 정의한다.
분석하기 이전에 앞서 리액트 코드는 단지 자바스크립트 코드다.
리액트에서 도입된 특수 문법을 사용하긴 하지만 궁극적으로 리액트 코드는 자바스크립트 코드임을 명심하자.
표준 리액트 프로젝트에서 중요한 index.js와 App.js를 분석해보자.
src 폴더
index.js 전체 코드
index.js의 역할
이 페이지는 눈에 보이지는 않지만 페이지가 로딩될때마다 가장 먼저 실행되는 파일이 index.js 파일이다. 우리가 localhost:3000을 방문할때마다 맨 처음으로 실행되는 파일이다.
중요한점은 이 파일의 코드가 그냥 실행되는것이 아니라 그 파일의 코드가 변환된 버전이 실행되는것이다. 이 프로젝트의 셋업에는 코드를 변환하고 최적화하는 스크립트가 포함되어있으며 배후에 실행된다.
변환된 버전이 실행된다는예를들면 index.js 파일안에 있는 import'./index.css';를 보자. 리액트는 단지 자바스크립트로 이루어져 있다고 했다. 바닐라 자바스크립트에서는 css파일을 import하는것은 불가능하다. 하지만 이 프로젝트 셋업에서는 index.css 파일을 우리의 앱 전체에 넣고싶다고 npm start 프로세스에게 말하고 있는것이다. 이렇게되면 index.css 파일의 코드를 고려해야한다.
이렇게 변환하여 사용되어야 하는 이유는
1. 코드를 쉽게 작성하기 위해서
2. 모든 브라우저에서 사용하기 위해서
index.js의 코드를 하나씩 분석해보면
import ReactDOM from 'react-dom/client';
react-dom 패키지에서 ReactDOM이라는 객체를 export한것을 import 한다는 의미이다. 써드 파티 라이브러리에서 리액트 의존성중 하나인 ReactDOM이라는 객체를 import 하는것이다. package.json 파일을 보면 실제로 리액트 의존성이 두개 있음을 알 수 있다.
[package.json]
"react": "^18.0.0",
"react-dom": "^18.0.0",
해당 라이브러리(react,react-dom)가 가지고 있는 기능을 이 index.js 파일에서 사용할 수 있게 하는것이다.
import App from './App';
App 파일로부터 App을 import 한다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
위 코드에서 만든 root 객체에서 render() 메서드를 호출해서 여기서 우리가 무엇을 렌더링 해야하는지 리액트에게 알려주는것.
App.js의 전체 코드
App.js의 역할은?
App.js 파일에서 export하고 index.js에서 import 하면서 이렇게 두 파일이 연결되는데 App.js에서 정의된 기능을 index.js 안에서 사용할 수 있게된다.
그런데 ... App이라는 함수가 있는데 조금 이상해보인다. return하는 값 안에서 HTML 문법이 있는데 이것은 자바스크립트에서 일반적인것은 아니긴하다. 하지만 여기선 유효하다. 리액트 팀이 고안하여 만든 특수한 문법이다. JSX 라고 불리우는 문법. 이게 실행되는 이유는 앞에서도 말했지만 배후에서 실행되고 있는 변환과정으로 가능하다.

컴포넌트 파일명을 지을때는 대문자로시작한다.
여러문자로 조합할경우 뒤 문자도 대문자로 시작해준다.
ExpenseItem.js , UserItem.js, UserName.js ...
리액트의 컴포넌트를 만들기전에 명심할점
리액트의 컴포넌트는 단지 자바스크립트 함수에 불과하다.
단지 특수한 문법을 가진 함수다.
웹페이지 하나에 컴포넌트들은 상당히 많을것이다. 몇십개 몇백개되는게 정상이다.
따라서 따로 폴더를 만들어 관리하지 않으면 프로젝트 관리에 있어서 어려움이 생길 가능성이 높다. 따라서 컴포넌트들을 한 폴더에 집어넣자.
근데.. App.js 도 어쨋든 컴포넌트 아닌가?? 이것도 저 컴포넌트 폴더에 넣어야되나??
App.js 도 결국에 컴포넌트가 맞지만 특수한 컴포넌트이며 루트 컴포넌트이다.
우리가 만든 컴포넌트들을 모아서 최종적으로 렌더링시키는게 App.js이며
App.js도 컴포넌트지만 특수한 루트 컴포넌트이므로 최상위폴더에 위치시킨다.
컴포넌트 만들기
Expenseitem.js
function ExpenseItem() {
return <h2>ExpenseItem item!</h2>;
}
export default ExpenseItem;
App.js
import ExpenseItem from "./components/ExpenseItem";
function App() {
return (
<div>
<h2>Let's get started!</h2>
<ExpenseItem></ExpenseItem> // 컴포넌트를 사용할땐 대문자로시작
</div>
);
}
export default App;
컴포넌트를 사용할때 반드시 대문자로 시작해야한다. 컴포넌트 라는것을 인식시켜주어야하기 때문.
좀더 많은 내용을 담고 있는 컴포넌트를 작성하려고 한다.
function ExpenseItem() {
return <div>Date</div>
<div><h2>Title</h2><div>Amount</div>
</div>;
}
export default ExpenseItem;
하지만 위의 코드는 가독성도 좋지않을 뿐더러 무엇보다 유효하지 않은 문법이다.
JSX 코드에서 리턴문은 단 하나의 루트 요소만을 가져야한다.
위의 코드는 두개의 루트요소를 가지고 있다.
하나의 루트 요소만을 갖게하고 가독성을 높여보자.
<div></div> // 하나의 루트요소만을 가지게 감싸기
소괄호로 감싸주어서 하나의 구문이라는것을 자바스크립트에게 전해주기
shift + alt + F (문서서식)으로 코드의 가독성 높이기
function ExpenseItem() {
return (
<div>
<div>Date</div>
<div>
<h2>Title</h2>
<div>Amount</div>
</div>
</div>
);
}
export default ExpenseItem;
CSS를 추가하여 컴포넌트에 디자인을 입혀보자.
일반적으로 CSS파일은 컴포넌트가 들어있는 폴더안에 넣는다.
ExpenseItem.css
.expense-item {
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
padding: 0.5rem;
margin: 1rem 0;
border-radius: 12px;
background-color: #4b4b4b;
}
.expense-item__description {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: flex-end;
flex-flow: column-reverse;
justify-content: flex-start;
flex: 1;
} //(뒤 코드 생략 ... )
이런 CSS들을 클래스로 적용하려고할때 HTML에서는 이런식으로 사용했을것이다.
<div class="expense-item"></div> // 틀림
JSX 문법에서는 이것이 통하지 않는다. class는 자바스크립트의 예약어라서
다른 특별한 문법을 고안이 되었는데 className 을 사용하면 된다.
<div className = "expense-item"></div> // 올바른 문법
추가로 리액트는 모든 파일을 적용하려 하지 않기때문에 import를 통하여 연결시켜 주어야 한다는것을 잊지 말자.
ExpenseItem.js
import './ExpenseItem.css';
function ExpenseItem() {
return (
<div className="expense-item">
<div>October 12th 2023</div>
<div className="expense-item__description">
<h2>Car Insurance</h2>
<div className="expense-item__price">$294.67</div>
</div>
</div>
);
}
export default ExpenseItem;
위 인터페이스를 보여주는 다음 코드의 문제점은 무엇일까?
ExpenseItem.js
import './ExpenseItem.css';
function ExpenseItem() {
return (
<div className="expense-item">
<div>October 12th 2023</div>
<div className="expense-item__description">
<h2>Car Insurance</h2>
<div className="expense-item__price">$294.67</div>
</div>
</div>
);
}
export default ExpenseItem;
데이터들이 “하드코딩”되어 있다는 점이다.
하드코딩 : 상수나 변수에 들어가는 값을 소스코드에 직접 입력하여 프로그램이 동작되는것.
이때까지는 컴포넌트가 함수라는것을 제외하면 HTML과 CSS로만 코드를 만들어 왔다.
즉, HTML + CSS + JS 에서 JS부분을 찾을 수 없었다.
위 하드코딩 되어 있는 문제는 JS를 사용하여 고치고 동적 데이터를 출력하고 표현식을 작업할 수 있다.
상수나 변수를 추가하고싶다면 리턴값 바깥쪽 영역에다 서술하고,
사용할때는 리턴값 내부에서 중괄호를 사용하여 사용한다. Math.random()같은식도 마찬가지로 가능하다.
ExpenseItem.js
import './ExpenseItem.css';
function ExpenseItem() {
const expenseDate = new Date(2021, 3, 28);
const expenseTitle = "Car Insurance";
const expenseAmount = 294.67;
return (
<div className="expense-item">
{/* 단순히 expenseDate만 사용하면 에러가 발생한다. 객체라서 객체를 텍스트로 표현못함.*/}
<div>{expenseDate.toISOString}</div>
<div className="expense-item__description">
<h2>{expenseTitle}</h2>
<div className="expense-item__price">${expenseAmount}</div>
</div>
</div>
);
}
export default ExpenseItem;
컴포넌트를 여러개 사용해서 나타내보자.
App.js
function App() {
return (
<div>
<h2>Let's get started!</h2>
<ExpenseItem></ExpenseItem>
<ExpenseItem></ExpenseItem>
<ExpenseItem></ExpenseItem>
<ExpenseItem></ExpenseItem>
</div>
);
}
컴포넌트가 복사되었다. 하지만 우리는 이런내용을 원하지 않는다.
대체로 각기 다른 내용과 데이터가 들어가야 할것이다.
위 사진은 여러번 사용만 했을뿐 항상 똑같은 데이터를 표시해준다는 문제점이 있는것.
이러한 문제가 생기는 원인은 데이터가 컴포넌트 안에 박혀있기 때문이다.
ExpenseItem.js
import "./ExpenseItem.css";
function ExpenseItem() {
// 데이터들이 컴포넌트에 박혀있다.
const expenseDate = new Date(2021, 3, 28);
const expenseTitle = "Car Insurance";
const expenseAmount = 294.67;
return (
...
);
}
export default ExpenseItem;
HTML의 요소가 속성을 가질 수 있듯이, 리액트도 사용자 지정 컴포넌트는 속성을 가질 수 있다. 다만 리액트에서는 속성이라고 하는 대신에 props라고 부른다. (props는 단지 properties의 줄임말.)
이러한 점을 통해서 컴포넌트 안에 데이터가 고정되어 있는점을 해소할 수 있다.
props는 어떻게 작동을 하는것이고 어떤 방식으로 만드는것?
App.js
const expenses = [
{
id: "e1",
title: "Toilet Paper",
amount: 94.12,
date: new Date(2020, 7, 14),
},
{ id: "e2", title: "New TV", amount: 799.49, date: new Date(2021, 2, 12) },
{
id: "e3",
title: "Car Insurance",
amount: 294.67,
date: new Date(2021, 2, 28),
},
{
id: "e4",
title: "New Desk (Wooden)",
amount: 450,
date: new Date(2021, 5, 12),
},
];
그런다음 위의 배열객체들을 . 으로 접근한다.
return (
<div>
<h2>Let's get started!</h2>
<ExpenseItem
title={expenses[0].title}
amount={expenses[0].amount}
date={expenses[0].date}
></ExpenseItem>
<ExpenseItem
title={expenses[1].title}
amount={expenses[1].amount}
date={expenses[1].date}
></ExpenseItem>
<ExpenseItem
title={expenses[2].title}
amount={expenses[2].amount}
date={expenses[2].date}
></ExpenseItem>
<ExpenseItem
title={expenses[3].title}
amount={expenses[3].amount}
date={expenses[3].date}
></ExpenseItem>
</div>
);
이때 좌변의 변수명(title,amount,date)은 임의의 이름을 작성해도 상관없으나,
우변에서 해당 객체의 키를 접근하려고할때는 당연하게도 위쪽의 자바스크립트에서 서술해놓은 해당 객체의 키의 이름과 일치해야한다.
중요한건 좌변의 변수명은 추후 파라미터로 데이터를 넘기고자 할때 키가 된다.
어찌되었든 여기까지하고 실행하려고 했지만 실행이 되지않는다...
사용자 지정 컴포넌트를 사용한곳에서 정의한 값들에 액세스를 해주어야 한다.
일반적인 자바스크립트에서는 데이터를 함수에 전달하기 위해서는 파라미터를 사용한다고 했다. 이경우 리액트에서도 비슷하다.
그럼 파라미터를 title, amount, date 이런식으로 전달해주어야하나???
그렇지는 않다. 단 하나의 파라미터만을 받으면 된다.
ExpenseItem.js
import "./ExpenseItem.css";
function ExpenseItem(props) {
return (
<div className="expense-item">
<div>{props.date.toISOString}</div>
<div className="expense-item__description">
<h2>{props.title}</h2>
<div className="expense-item__price">${props.amount}</div>
</div>
</div>
);
}
export default ExpenseItem;
함수 이름 옆에 props라는 파라미터를 전달받은것을 볼 수 있다. 파라미터명은 임의로 정할 수 있지만, 명시적으로 알 수 있게 props라고 기술해놓았다.
8.3 : 결과