해당 포스팅과 코드들은 Udemy 강의를 들으면서 작성되었습니다!
🔗 React 문서 - JSX 소개 : 리액트에서 JSX에 대해 소개한 문서입니다! 더 자세한 설명이 있으니 참고!
이 확장자는 개발 서버가 실행될 때 백그라운드에서 실행되는 빌드 프로세스에게 해당 파일이 JSX 코드를 포함하고 있다는 것을 알려준다.(이 역시 브라우저에선 지원이 안됨) → 이 확장자를 처리하는 것은 빌드 프로세스뿐!
.jsx대신 .js만 사용하는 리액트 프로젝트를 찾을 수 있고, .js파일 내에서도 JSX 코드를 찾을 수 있다. 이는 파일의 빌드 프로세스에 따라 JSX 구문을 사용할 때 어떤 확장자가 예상될 지 결정된다.<div id="root"></div>
ReactDOM.createRoot()에 전달 → 리액트 엘리먼트를 root.render()에 전달한다.<header>, <div>, ...) → 리액트가 컴포넌트를 관리하는 방식이 바뀔 수도 있기 때문이다.function App() {
return (
<div>
<header>
<img src="src/assets/react-core-concepts.png" alt="Stylized atom" />
<h1>React Essentials</h1>
<p>
Fundamental React concepts you will need for almost any app you are
going to build!
</p>
</header>
<main>
<h2>Time to get started!</h2>
</main>
</div>
);
}
export default App;
function Header() {
return (
<header>
<img src="src/assets/react-core-concepts.png" alt="Stylized atom" />
<h1>React Essentials</h1>
<p>
Fundamental React concepts you will need for almost any app you are
going to build!
</p>
</header>
);
}
function App() {
return (
<div>
{/* 이곳에 헤더를 추가하고 싶다.. */}
<Header />
<main>
<h2>Time to get started!</h2>
</main>
</div>
);
}
export default App;
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
const entryPoint = document.getElementById("root");
ReactDOM.createRoot(entryPoint).render(<App />);
<App /> 이 JSX 코드는 함수로 전환이 되지 않는 것이다. 대신 값으로 사용되고 있어 호출된 다른 메서드(render(<App />))의 인수로 쓰인다.
index.jsx는 HTML 파일에서 가장 먼저 로딩되는 파일로 리액트 앱의 주요 입구로 작용한다. 이 위치에서 리액트 앱이 부팅된다.
이곳(index.jsx)에 불러들인 전반적인 리액트 라이브러리에 속한 ReactDOM이 있고 이로 인해 앱 컴포넌트가 결과적으로 렌더링 된다.
index.jsx는 앱 컴포넌트의 내용을 화면에 출력하는 것을 담당한다.
createRoot() 메서드는 기존의 HTML 요소. 즉, 리액트에서 생성된 것이 아니고 index.html 파일의 한 부분인 요소를 입력값으로 설정한다. 이 경우, id="root"로 되어있는 <div>이며 getElementById를 통해 선택했음을 알 수 있다.
리액트 프로젝트의 경로로 그 요소(<div id="root"></div>)가 사용되면, 리액트가 리액트 컴포넌트를 삽입한다. → <div>안으로 렌더링.
🚨 이를 통해서 중첩된 컴포넌트가 있을 수 있음을 충분히 유추할 수 있다! → 컴포넌트 계층구조 설립(컴포넌트 트리)🚨
{}을 사용한다.const reactDescriptions = ["Fundamental", "Crucial", "Core"]; // index-max:2
function getRandomInt(max) {
return Math.floor(Math.random() * (max + 1));
} // 무작위로 숫자 생성
function Header() {
return (
<header>
<img src="src/assets/react-core-concepts.png" alt="Stylized atom" />
<h1>React Essentials</h1>
<p>
{reactDescriptions[getRandomInt(2)]} React concepts you will need for
almost any app you are going to build!
</p>
</header>
);
}
const description = reactDescriptions[getRandomInt(2)];
return (
<header>
<img src="src/assets/react-core-concepts.png" alt="Stylized atom" />
<h1>React Essentials</h1>
<p>
{description} React concepts you will need for
almost any app you are going to build!
</p>
</header>
)
위의 방법처럼 따로 빼는 방법이 가독성이 좋다!
import reactImg from "./assets/react-core-concepts.png";
function Header() {
return(
<img src={reactImg} alt="Stylized atom" />
)
}
import componentImg from "./assets/components.png";
function CoreConcept(props) {
return (
<li>
<img src={props.image} alt={props.title} />
<h3>{props.title}</h3>
<p>{props.description}</p>
</li>
);
}
function App() {
return (
<div>
{/* 이곳에 헤더를 추가하고 싶다.. */}
<Header />
<main>
<section id="core-concepts">
<h2>Core Concepts</h2>
<ul>
<CoreConcept
title="Components"
description="The core UI building block."
image={componentImg}
/>
<CoreConcept />
<CoreConcept />
<CoreConcept />
</ul>
</section>
</main>
</div>
);
}
<CoreConcept
title={CORE_CONCEPTS[0].title}
description={CORE_CONCEPTS[0].description}
image={CORE_CONCEPTS[0].image}
/>
그러나 이 방법 또한 효율적이진 않음!
<CoreConcept {...CORE_CONCEPTS[1]}
또한 CoreConcepts도 단순화할 수 있다.
function CoreConcept({ image, title, description }) {
// 디스트럭처링을 사용하는 대신, props로 설정하는 것과 이름을 동일하게 해야한다.
return (
<li>
<img src={image} alt={title} />
<h3>{title}</h3>
<p>{description}</p>
</li>
);
}
이렇게 분리하는 것은 CSS 변경이 용이하기 때문인데, 이렇게 분리해도 자동적으로 해당 컴포넌트에만 적용이 되도록 한정되는 것은 아니다!
children 속성이다.children prop은 컴포넌트 텍스트 사이 내용을 의미한다.
// 1. 방법 1
export default function TabButton(props) {
return (
<li>
<button>{props.children}</button>
</li>
);
}
// 2. 방법 2
export default function TabButton({children}) {
return (
<li>
<button>{children}</button>
</li>
);
}
<TabButton>Components</TabButton>같이 컴포넌트를 구축하는 것을 컴포넌트 합성이라고 한다.다음과 같이 작성할 수 있다.
// 1. 방법 1
// TabButton.jsx
export default function TabButton({ label }) {
return (
<li>
<button>{label}</button>
</li>
);
}
// App.jsx
<TabButton label="Components"></TabButton>
// 2. 방법 2
// TabButton.jsx
export default function TabButton({ children }) {
return (
<li>
<button>{children}</button>
</li>
);
}
// App.jsx
<TabButton>Components</TabButton>
onClick={}안에 함수를 삽입한다.// TabButton.jsx
export default function TabButton({ children }) {
function handleClick() {
console.log("clicked");
}
return (
<li>
<button onClick={handleClick}>{children}</button>
</li>
);
}
// App.jsx
function App() {
function handleSelect() {
console.log("selected");
}
return(
<TabButton onSelect={handleSelect}>Components</TabButton>
<TabButton onSelect={handleSelect}>JSX</TabButton>
<TabButton onSelect={handleSelect}>Props</TabButton>
<TabButton onSelect={handleSelect}>State</TabButton>
)
}
// TabButton.jsx
export default function TabButton({ children, onSelect }) {
return (
<li>
<button onClick={onSelect}>{children}</button>
</li>
);
}
// App.jsx
<TabButton onSelect={() => handleSelect("components")}>Components</TabButton>
<TabButton onSelect={() => handleSelect("jsx")}>JSX</TabButton>
<TabButton onSelect={() => handleSelect("props")}>Props</TabButton>
<TabButton onSelect={() => handleSelect("state")}>State</TabButton>
onSelect={handleSelect()}와는 다르게 동작한다.onClick = () => handleSelect('components')가 되니깐.function App() {
let tabContent = "Plz Click a button.";
function handleSelect(selectedButton) {
// selectedButton => 'components','jsx','props','state'
tabContent = selectedButton;
console.log(tabContent)
}
return(
{tabContent}
)
}
리액트는 컴포넌트 함수를 코드 내에서 처음 발견했을 떄 한 번밖에 실행하지 않는다. 따라서 컴포넌트 함수가 재실행 되야한다는 것을 리액트에게 다시 알려야 한다.
useState Hook은 일부 컴포넌트에 연결된 상태르 관리하게 한다. 이는 리액트에 의해 저장된 일부 데이터일 뿐이며 데이터가 변경되면 이 훅이 자신이 속한 컴포넌트 함수를 활성화하여 리액트에 의해 재검토된다.
// App.jsx
import { useState } from "react";
// useState : 리액트 훅.
function App() {
let [selectedTopic, setSelectedTopic] = useState("Plz click a button"); // Hook 함수는 이런 식으로 컴포넌트 함수 안에서 바로 호출되어야 하고 다른 코드 안에 중첩되면 안된다.
function handleSelect(selectedButton) {
setSelectedTopic(selectedButton);
console.log(selectedTopic);
}
return(
{selectedTopic};
)
}
import { CORE_CONCEPTS, EXAMPLES } from "./data.js";
function App() {
let [selectedTopic, setSelectedTopic] = useState("components"); // 초기화
return (
<div id="tab-content">
<h3>{EXAMPLES[selectedTopic].title}</h3>
<p>{EXAMPLES[selectedTopic].description}</p>
<pre>
<code>{EXAMPLES[selectedTopic].code}</code>
</pre>
</div>
)
}
function App() {
let [selectedTopic, setSelectedTopic] = useState();
return (
{!selectedTopic ? <p>Plz select a topic.</p> : null}
{/* null은 jsx 코드로 출력할 수 있는 것인데 단순히 아무것도 렌더링되지 않는 것이다. */}
{/* 선택한 토픽이 없다면 p 태그가 렌더링. 그외에는 아무것도 렌더링 하지 않는다.*/}
{selectedTopic ? (
<div id="tab-content">
<h3>{EXAMPLES[selectedTopic].title}</h3>
<p>{EXAMPLES[selectedTopic].description}</p>
<pre>
<code>{EXAMPLES[selectedTopic].code}</code>
</pre>
</div>
) : null}
)
}
// 혹은 다음과 같이 코드를 줄일 수 있다.
{!selectedTopic ? (
<p>Plz select a topic.</p>
) : (
<div id="tab-content">
<h3>{EXAMPLES[selectedTopic].title}</h3>
<p>{EXAMPLES[selectedTopic].description}</p>
<pre>
<code>{EXAMPLES[selectedTopic].code}</code>
</pre>
</div>
)}
{!selectedTopic && <p>Plz select a topic.</p>}
{selectedTopic && (
<div id="tab-content">
<h3>{EXAMPLES[selectedTopic].title}</h3>
<p>{EXAMPLES[selectedTopic].description}</p>
<pre>
<code>{EXAMPLES[selectedTopic].code}</code>
</pre>
</div>
)}
function App(){
let tabContent = <p>Plz select a topic.</p>;
if (selectedTopic) {
tabContent = (
<div id="tab-content">
<h3>{EXAMPLES[selectedTopic].title}</h3>
<p>{EXAMPLES[selectedTopic].description}</p>
<pre>
<code>{EXAMPLES[selectedTopic].code}</code>
</pre>
</div>
);
}
return(
{tabContent}
)
}
// TabButton.jsx
export default function TabButton({ children, onSelect, isSelected }) {
return (
<li>
<button className={isSelected ? "active" : undefined} onClick={onSelect}>
{children}
</button>
</li>
);
}
// App.jsx
<TabButton
isSelected={selectedTopic === "components"}
onSelect={() => handleSelect("components")}
>
Components
</TabButton>
<TabButton
isSelected={selectedTopic === "jsx"}
onSelect={() => handleSelect("jsx")}
>
JSX
</TabButton>
<TabButton
isSelected={selectedTopic === "props"}
onSelect={() => handleSelect("props")}
>
Props
</TabButton>
<TabButton
isSelected={selectedTopic === "state"}
onSelect={() => handleSelect("state")}
>
State
</TabButton>
TabButton에서 isSelected라는 속성을 추가한다. 그리고 className에 isSelected가 있다면 active를, 없다면 undefined(혹은 '')을 리턴하도록 한다. → CSS에 active 클래스와 관련된 스타일 정의App에서 isSelected 속성에 참/거짓을 갖도록 하여 TabButton의 삼항연산자에 의해 걸러질 수 있도록 한다.{[<p>Hello</p>, <p>World</p>]}// 이전
<CoreConcept
title={CORE_CONCEPTS[0].title}
description={CORE_CONCEPTS[0].description}
image={CORE_CONCEPTS[0].image}
/>
<CoreConcept {...CORE_CONCEPTS[1]}>
<CoreConcept {...CORE_CONCEPTS[2]}>
<CoreConcept {...CORE_CONCEPTS[3]}>
// 이후
{CORE_CONCEPTS.map((concepItem) =>
(<CoreConcept key={concepItem.title} {...concepItem})
)}
<Header />isSelected{...conceptItem}function CoreConcept({image, title, description}).map() 메서드를 사용해 데이터 배열을 매핑하여 JSX 요소 배열로 만들어 목록 데이터를 동적으로 출력. → 리액트가 목록을 효율적으로 렌더링 및 업데이트할 수 있도록 key prop을 추가해야한다.