Components are UI Building blocks.
⇒ 리액트 애플리케이션은 컴포넌트를 결합하여 만들어지는 것
👀
React Component
:HTML
+CSS
+JavaScript
Reusable building blocks
a. Create small building blocks & compose the UI from them
b. If needed: Reuse a building block in different parts of the UI
⇒ 복잡한 사용자 인터페이스를 관리하기 쉽도록 작게 분리하여 UI의 다른 위치에도 사용할 수 있게 해줌
Related code lives together
a. Related HTML & JS code is stored together
b. Since JS influences the output, storing JS + HTML together makes sense
Separation of concerns
a. Different components handle different data & logic
b. Vastly simplifies the process of working on complex apps
👀 동작 순서:
index.html
→index.jsx
→App.jsx
JSX
(JavaScript Syntax eXtension
) : JS 문법 확장자
⇒ JS 파일 내에 HTML 마크업 코드를 작성하여 HTML 요소를 설명하고 생성할 수 있게 함✋ But, 브라우저에선 사용 불가능 ⇒ 그치만, 리액트 개발자로서 작성하는 코드는 브라우저에 도달하기 전에 개발 서버에서 변환됨
PascalCase
(e.g. MyHeader
)🤷♀️ why?
div
,image
,header
와 같은 내장 요소는 소문자로 시작하는 반면,
커스텀 컴포넌트는 리액트에게 내장된 컴포넌트가 아니라는 것을 알리기 위해 무조건 대문자로 시작해야 함!!
(내장 요소들은 리액트에서 DOM 노드로서 렌더링되는 반면, 커스텀 컴포넌트는 단순 함수이므로 리액트에서 함수로서 실행됨)
function Header () {
return (
<header>Header!!</header>
)
}
function App() {
return (
<div>
<Header />
</div>
)
}
‣ JSX
목적: 컴포넌트로부터 생성되어야 하는 타겟 HTML 코드를 더 쉽게 정의할수 있도록 하는 것
‣ JSX
의 강점: JSX 코드 안에 일반 HTML 코드 같이 컴포넌트 함수 사용 가능!
👀
.jsx
는 브라우저에선 지원되지 않는 파일 확장자이지만, 리액트 프로젝트에선 지원하기 때문에 작동 가능!
→ 개발 서버가 실행될 때 백그라운드에서 실행되는 빌드 프로세스에게 해당 파일이 JSX 코드를 포함하고 있다는 것을 알려줌
⇒ 즉,.jsx
확장자를 처리하는 것은 빌드 프로세스뿐 !
💡 index.jsx
파일은 HTML 파일에 가장 먼저 로딩되는 파일
// index.jsx
import ReactDOM from 'react-dom/client';
// 이로 인해 앱 컴포넌트가 결과적으로 렌더링됨
// => 앱 컴포넌트의 내용을 화면에 출력하는 것을 담당
const entryPoint = document.getElementById("root");
ReactDOM.createRoot(enyryPoint).render(<App/>);
// createRoot와 렌더링 메소드가 단 하나의 루트 컴포넌트 렌더링
<!-- index.html -->
<body>
<!-- 여기에 위의 'root' id 요소가 매칭됨 -->
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
태그 내 중괄호 사이에 자바스크립트 표현 추가 가능
const description = customFun(2);
<p>{description}</p>
<img src="src/assets/react.png" alt="react icon" />
// -> 최적의 방법이 아님!
// 배포시 이미지가 사라질 수도 있음
// 배포 과정에서 모든 코드가 변환 및 최적화되고 함께 묶여짐
// 묶여지는 과정에서 이와 같이 로딩된 이미지 파일이 무시될 수 있고, 배포 과정에서 유실될 수 있음
// 또한, 추가적인 최적화 단계를 사용할 수 없음
// ---------------------------------------
// ⇒ 최적의 방법으로 이미지 불러오기
import reactImg from './assets/react.png';
// reactImg: 사용할 경로를 포함하는 변수
<img src={reactImg} alt="react icon" />
props
: 데이터를 컴포넌트로 전달하고 그 데이터를 그 곳에 사용 가능⇒ key-value 쌍들을 모두 가진 하나의 객체를 props 매개변수를 통해 얻는 것임!
<CoreConcept
title={CORE_CONCEPTS[0].title}
description={CORE_CONCEPTS[0].description}
image={CORE_CONCEPTS[0].image}
/>
// ----> spread syntax로 축약하여 표현 가능
/* 변수를 불러와 동적 설정 가능 → 1개의 컴포넌트로 다양한 항목 표시 가능 */
<CoreConcept {...CORE_CONCEPTS[0]} />
<CoreConcept {...CORE_CONCEPTS[1]} />
<CoreConcept {...CORE_CONCEPTS[2]} />
<CoreConcept {...CORE_CONCEPTS[3]} />
function CoreConcept(props) {
return (
<li>
<img src={props.image} alt={props.title} />
<h3>{props.title}</h3>
<p>{props.description}</p>
</li>
);
}
// ----> 구조 분해 할당으로 축약하여 표현 가능
// 구조 분해 할당을 통해 props 설정 가능
// {} : 1번째 매개변수 함수를 구조 분해할 때 사용
// 독립된 변수로 제공
function CoreConcept({ image, title, description }) {
return (
<li>
<img src={image} alt={title} />
<h3>{title}</h3>
<p>{description}</p>
</li>
);
}
✋ Header.css
파일에 있는 스타일이 <Header/>
컴포넌트에서 import했다고 해서 해당 컴포넌트로 범위가 제한된 것은 아니므로 전체 페이지 내 헤더 태그에 스타일이 적용됨에 주의!
👀 컴포넌트별 CSS 파일로 구분하면 어떤 스타일이 어떤 컴포넌트에 적용되는지 구분하는 것과 스타일 조절하는 것에 있어서 so easy~!
(✋ 해당 컴포넌트에 자동적으로 한적적으로 적용되는 것이 아니라는 점에 주의!)
<section id="examples">
<h2>Examples</h2>
{/* menu: 버튼 목록 만드는데 사용 가능 */}
<menu>
{/* 이렇게 감싸서 컴포넌트 구축하는 것을 'Components Composition ; 컴포넌트 합성' */}
<TabButton>Components</TabButton>
</menu>
</section>
‣ children
prop : 리액트에서 설정한 prop으로, 어느 특정한 속성에 의해 설정한 prop이 아님!
export default function TabButton({children}) {
return <li><button>{children}</button></li>
}
<TabButton>Components</TabButton>
‣ Attribute props
export default function TabButton({label}) {
return <li><button>{label}</button></li>
}
<TabButton label="Components" />
export default function TabButton({ children, onSelect }) {
// func name recommandation: e.g. handleClick or clickHandler
function handleClick() {
console.log("Hello World!");
}
return (
<li>
{/*
- handleClick()으로 실행해선 안됨! -> 이 경우 함수가 바로 실행됨 함수를
- 값으로 사용하고자 하므로 함수 이름을 사용해야 함!
*/}
<button onClick={handleClick}>{children}</button>
</li>
);
}
function handleSelect(selectedButton) {
console.log(selectedButton);
}
/*
해당 코드가 분석될 때 화살표 함수만 정의되기 때문에 화살표 함수 안의 코드는 아직 실행되지 않음
버튼이 클릭되어 함수가 실행되면 화살표 함수 코드가 실행됨
=> 리액트에서 이벤트에 따라 실행되는 함수를 정의하고 싶은데 어떻게 불려질지 그리고
어떤 인수를 실행할지를 통제하고 싶을 떄 자주 사용하는 패턴 !
*/
<TabButton onSelect={() => handleSelect('components')}>Components</TabButton>
🤷♀️ Q: 이벤트로부터 독립적인 함수는 어떻게 구성/설정 할 수 있을까?
🎙️ A: 이벤트를 핸들링하는 함수의 실행을 다른 함수로 감싸면, 그 다른 함수가 이벤트 핸들링의 prop(속성)의 값으로 전달된다. so, 메인 함수는 바로 실행되지 않고, 이벤트 발생시에만 실행된다!
let tabContent = <p>Please select a topic.</p>;
// 위와 같이 state가 아닌 let 으로 선언한 값을 변경하는 경우
// 코드는 최초 1번만 실행되기 때문에 새로운 내용이 감지되지 않아 UI가 업데이트되지 않음
// 🚨 리액트는 컴포넌트 함수를 코드 내에서 처음 발견했을 때 1번 밖에 실행하지 않음 주의!
// => 함수가 실행되고 변수는 업데이트 되지만 UI는 업데이트되지 않음
// ✨ 일반적인 변수로는 UI 업데이트 불가
// -> 컴포넌트 함수가 재실행되어야 한다는 것을 리액트에게 알려줄 방법을 찾아야 함
// => state(상태) 필요!
리액트 프로젝트에서 use
로 시작하는 모든 함수 ⇒ React Hooks
React Hook은 일반 함수이지만, 리액 컴포넌트 함수 또는 다른 리액트 Hook 안에서 호출되어야 함
컴포넌트 함수 안에서 바로 호출해야 하며, 다른 코드 안에 중첩되면 안됨 (내부 함수 안에 중첩하여 사용 ❌)
⇒ 컴포넌트 함수의 최상위에서 호출해야 함!
- Only call Hooks inside of Component Functions
- Only call Hooks on the top level
👀 useState
는 배열을 반환하며, 요소는 항상 2개!
import { useState } from 'react';
const [selectedTopic, setSelectedTopic] = useState('Please click a button');
⇒ 즉, useState
hook을 통해 리액트에게 이 컴포넌트 함수가 다시 실행되어야 한다고 알릴 수 있음
💡 상태를 업데이트했어도
console.log
로그를 출력하면 상태 변경 전의 상태 값이 나옴🤷♀️ why?
상태를 업데이트 시키는 함수인 setState를 부를 때 리액트는 이 상태 업데이트의 스케줄을 조정하며, 이 컴포넌트 함수를 재실행함✨ So, App 컴포넌트 함수를 다시 실행하고 나서야 업데이트된 값 사용 가능
⇒ 그제서야 새로운 값을 사용할 수 있으므로 업데이트의 스케줄이 조정되자마자 로그를 출력하면 업데이트된 값이 보이지 않는 것임
⇒ 다시 말해서, 아직은 예전 App 함수에 있다는 것!
// 합쳐진 삼항연산자를 사용하는 것보다 이렇게 두 부분을 사용하는 것이 보다 읽기 편하고 이해하기 쉬운 코드!
{!selected && <p>Please select a topic.</p>}
{selected && <div id="tab-content">...</div>}
// --> 변수 사용
let tabContent = <p>Please select a topic.</p>;
if (selectedTopic) {
tabContent = <div id="tab-content">...</div>;
}
{tabContent}
<ul>
{CORE_CONCEPTS.map((conceptItem) => (
<CoreConcept key={conceptItem.title} {...conceptItem} />
))}
</ul>