๋ฆฌ์กํธ๋?
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ์ํ JavaScript ์คํ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
์ ์ธํ์ด๊ณ , ์ปดํฌ๋ํธ ๊ธฐ๋ฐ์ด๊ณ , ๋ค์ํ ๊ณณ์์ ํ์ฉํ ์ ์๋ค๋ ํน์ง์ด ์๋ค.
์ ์ธํ: ํ ํ์ด์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด HTML / CSS / JS๋ก ๋๋ ์ ๊ธฐ ๋ณด๋ค๋
ํ๋์ ํ์ผ์ ๋ช
์์ ์ผ๋ก ์์ฑํ ์ ์๊ฒ JSX๋ฅผ ํ์ฉํ ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ์ ์งํฅํ๋ค.
์ปดํฌ๋ํธ ๊ธฐ๋ฐ: ๋ฆฌ์กํธ๋ ํ๋์ ๊ธฐ๋ฅ ๊ตฌํ์ ์ํด ์ฌ๋ฌ ์ข
๋ฅ์ ์ฝ๋๋ฅผ ๋ฌถ์ด๋ ์ปดํฌ๋ํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ๋ฐํ๋ค.
์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ๋ฉด ์๋ก ๋
๋ฆฝ์ ์ด๊ณ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์, ๊ธฐ๋ฅ ์์ฒด์ ์ง์คํ์ฌ ๊ฐ๋ฐํ ์ ์๋ค.
๋ฒ์ฉ์ฑ: ๋ฆฌ์กํธ๋ JavaScript ํ๋ก์ ํธ ์ด๋์๋ ์ ์ฐํ๊ฒ ์ ์ฉ๋ ์ ์๋ค.
Facebook์์ ๊ด๋ฆฌ๋์ด ์์ ์ ์ด๊ณ , ๊ฐ์ฅ ์ ๋ช
ํ๋ฉฐ, ๋ฆฌ์กํธ ๋ค์ดํฐ๋ธ๋ก ๋ชจ๋ฐ์ผ ๊ฐ๋ฐ๋ ๊ฐ๋ฅํ๋ค.
DOM์์ ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋จผ์ ๋ณด์.
์๋์ ๊ฐ์ด Hello, HTML DOM์ ํ๋ฉด์ ํ์ํ๊ธฐ ์ํด์ ์๋ ์์์ฒ๋ผ
HTML(index.html)๊ณผ JavaScript(index.js)๋ฅผ ์ฐ๊ฒฐํ๋ ์์
์ ์งํํด์ผ ํ๋ค.
import "./styles.css";
const user = {
firstName: "HTML",
lastName: "DOM"
};
function formatName(user) {
return user.firstName + " " + user.lastName;
}
const heading = document.createElement("h1");
heading.textContent = `Hello, ${formatName(user)}`;
document.body.appendChild(heading);
๊ทธ๋ ๋ค๋ฉด React์์๋ ์ด๋ป๊ฒ ์์
ํ ๊น?
์๋ ์์ ๋ JSX๋ฅผ ์ฐ์ง ์์ React๋ฅผ ๋จผ์ ๋ํ๋ด๊ณ ์๋ค.
JSX ์ฐ์ง ์์ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ ํ, ์ฃผ์ ์ฒ๋ฆฌํ์.
์ฝ๋ ๋ผ์ธ 17๋ฒ์งธ ์ค ์ฃผ์์ ํด์ ํ๋ฉด JSX๋ฅผ ํ์ฉํ React์ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ค ์ ์๋ค.
import React from "react";
import "./styles.css";
function App() {
const user = {
firstName: "React",
lastName: "JSX Activity"
};
function formatName(user) {
return user.firstName + " " + user.lastName;
}
// JSX ์์ด ํ์ฉํ React
return React.createElement("h1", null, `Hello, ${formatName(user)}!`);
// JSX ๋ฅผ ํ์ฉํ React
// return <h1>Hello, {formatName(user)}!</h1>;
}
export default App;
์ฐ๋ฆฌ๋ App.js ์ด ํ ๊ฐ์ JavaScript ํ์ผ ์์์ HTML๊ณผ JavaScript๋ก ๋๋์ด์ก๋ ๋ ๊ฐ์ง ์ผ์ ํ ๋ฒ์ ์ฒ๋ฆฌํ๊ณ ์๋ค.
์ฒ์์๋ HTML๊ณผ JavaScript๋ฅผ ํ ๊ณณ์ ์์ฑํ๋ ๊ฒ ์ด์ํ ์ ์์ง๋ง, ํ๋์ ํ์ผ์์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์กฐ์ ๋์์ ํ๋์ ํ์ ํ ์ ์๋ค. ์ด๋ ๊ฒ JSX๋ ๊ฐ๋ฐ์๊ฐ ์ฝ๋๋ง ๋ฐ๋ผ๋ณด๋ ๊ฒ ์๋๋ผ, ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ผ๋ณด๊ฒ ๋๋๋ค.
ํ๋์ ์๋ฆฌ๋จผํธ ์์ ๋ชจ๋ ์๋ฆฌ๋จผํธ๊ฐ ํฌํจ
JSX์์ ์ฌ๋ฌ ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๊ณ ์ ํ๋ ๊ฒฝ์ฐ, ์ต์์์์ opening tag์ closing tag๋ก ๊ฐ์ธ์ฃผ์ด์ผ ํ๋ค.
// ํ๋ฆฐ ์์
<div>
<h1>Hello</h1>
</div>
<div>
<h2>World</h2>
</div>
// ์ณ์ ์์
<div>
<div>
<h1>Hello</h1>
</div>
<div>
<h2>World</h2>
</div>
</div>
์๋ฆฌ๋จผํธ ํด๋์ค ์ฌ์ฉ ์, className์ผ๋ก ํ๊ธฐ
React์์ CSS class ์์ฑ์ ์ง์ ํ๋ ค๋ฉด className
์ผ๋ก ํ๊ธฐํด์ผ ํ๋ค.
๋ง์ฝ class๋ก ์์ฑํ๊ฒ ๋๋ค๋ฉด React์์๋
์ด๋ฅผ html ํด๋์ค ์์ฑ ๋์ , ์๋ฐ์คํฌ๋ฆฝํธ ํด๋์ค๋ก ๋ฐ์๋ค์ด๊ธฐ ๋๋ฌธ์ ์ฃผ์ํด์ผ ํ๋ค.
// ํ๋ฆฐ ์์
<div class="greeting">Hello!</div>
// ์ณ์ ์์
<div className="greeting">Hello!</div>
JavaScript ํํ์ ์ฌ์ฉ ์, ์ค๊ดํธ ({}) ์ด์ฉ
JSX์์ JavaScript๋ฅผ ์ฐ๊ณ ์ ํ๋ค๋ฉด, ๊ผญ ์ค๊ดํธ๋ฅผ ์ด์ฉํด์ผ ํ๋ค.
์ค๊ดํธ๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์ผ๋ฐ ํ ์คํธ๋ก ์ธ์ํ๋ค.
function App() {
const name = 'Josh Perez';
return (
<div>
Hello, {name};
</div>
);
)
์ฌ์ฉ์ ์ ์ ์ปดํฌ๋ํธ๋ ๋๋ฌธ์๋ก ์์ (PascalCase)
React ์๋ฆฌ๋จผํธ๊ฐ JSX๋ก ์์ฑ๋๋ฉด "๋๋ฌธ์"๋ก ์์ํด์ผ ํ๋ค.
์๋ฌธ์๋ก ์์ํ๊ฒ ๋๋ฉด ์ผ๋ฐ์ ์ธ HTML ์๋ฆฌ๋จผํธ๋ก ์ธ์์ ํ๊ฒ ๋๋ค.
์ด๋ ๊ฒ ๋๋ฌธ์๋ก ์์ฑ๋ JSX ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ์ฌ์ฉ์ ์ ์ ์ปดํฌ๋ํธ๋ผ๊ณ ๋ถ๋ฅธ๋ค.
// ํ๋ฆฐ ์์
function hello() {
return <div>Hello!</div>;
}
function HelloWrold() {
return <hello />;
}
// ์ณ์ ์์
function Hello() {
return <div>Hello!</div>;
}
function HelloWrold() {
return <Hello />;
}
์กฐ๊ฑด๋ถ ๋ ๋๋ง์๋ ์ผํญ์ฐ์ฐ์ ์ฌ์ฉ
์กฐ๊ฑด๋ถ ๋ ๋๋ง์ if๋ฌธ์ด ์๋ ์ผํญ์ฐ์ฐ์๋ฅผ ์ด์ฉํด์ผ ํ๋ค.
<div>
{
(1+1 === 2) ? (<p>์ ๋ต</p>) : (<p>ํ๋ฝ</p>)
}
</div>
์ฌ๋ฌ ๊ฐ์ HTML ์๋ฆฌ๋จผํธ๋ฅผ ํ์ํ ๋, map() ํจ์๋ฅผ ์ด์ฉ
React ์์ ์ฌ๋ฌ ๊ฐ์ HTML ์๋ฆฌ๋จผํธ๋ฅผ ํ์ํ ๋๋ "map()" ํจ์๋ฅผ ์ฌ์ฉํ๋ค.
map ํจ์๋ฅผ ์ฌ์ฉํ ๋๋ ๋ฐ๋์ "key" JSX ์์ฑ์ ๋ฃ์ด์ผ ํ๋ค.
"key" JSX ์์ฑ์ ๋ฃ์ง ์์ผ๋ฉด ๋ฆฌ์คํธ์ ๊ฐ ํญ๋ชฉ์ key๋ฅผ ๋ฃ์ด์ผ ํ๋ค๋ ๊ฒฝ๊ณ ๊ฐ ํ์๋๋ค.
const posts = [
{ id: 1, title: "Hello World", content: "Welcome to learning React" },
{ id: 2, title: "Installation", content: "You can install React from npm" }
];
export default function App() {
// ํ ํฌ์คํธ์ ์ ๋ณด๋ฅผ ๋ด์ ๊ฐ์ฒด post๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๊ณ
// obj๋ฅผ JSX ํํ์์ผ๋ก ๋ฐ๊ฟ ๋ฆฌํดํด์ฃผ๋ ํจ์ postToJSX
const postToJSX = (post) => {
return (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
};
// TODO: postToJSX ํจ์๋ฅผ ์ด์ฉํ์ฌ ์ฌ๋ฌ ๊ฐ์ ์๋ฆฌ๋จผํธ๋กค ํ์ํด๋ณด์ธ์.
return (
<div className="App">
<h1>Hello JSX</h1>
{posts.map(postToJSX)} // or posts.map((el) => postToJSX(el))
</div>
);
}
๋ฆฌ์กํธ SPA๋ฅผ ์ฝ๊ณ ๋น ๋ฅด๊ฒ ๊ฐ๋ฐํ ์ ์๋๋ก ๋ง๋ค์ด์ง ํด ์ฒด์ธ์ด๋ค.
npx create-react-app@latest ํด๋์ด๋ฆ
์ด๋ฒ React Twittler Intro ๊ณผ์ ์์๋ ์ ๋ช SNS ์๋น์ค์ธ Twitter์ ์ ์ฌํ Twittler๋ฅผ React๋ก ๊ฐ๋ฐํ๋ค.
์์ผ๋ก ์์ React Twittler Router, React Twittler State Props ๊ณผ์ ํต๊ณผ๋ฅผ ์ํด์ JSX ๊ธฐ๋ณธ ๋ฌธ๋ฒ์ ์์งํ๊ณ , ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ๊ฐ๋ฐ์ ์ฒ์์ผ๋ก ์์ํ๋ ๋จ๊ณ์ด๋ค.
npm install์ ํตํด ํ์ํ ๋ชจ๋์ ์ค์นํ๋ค.
npm run start๋ฅผ ํตํด์ ๋ฆฌ์กํธ ๊ฐ๋ฐ์ ์์ํด ๋ณด์.
import React from 'react';
import './App.css';
import './global-style.css';
import { dummyTweets } from './static/dummyData';
// ! ์ ์ฝ๋๋ ์์ ํ์ง ์์ต๋๋ค.
console.log(dummyTweets); // ๊ฐ๋ฐ ๋จ๊ณ์์ ์ฌ์ฉํ๋ ๋๋ฏธ ๋ฐ์ดํฐ์
๋๋ค.
const Sidebar = () => {
return (
<section className="sidebar">
{/* TODO : ๋ฉ์ธ์ง ์์ด์ฝ์ ์์ฑํฉ๋๋ค. */}
</section>
);
};
const Counter = () => {
return (
<div className="tweetForm__input">
<div className="tweetForm__inputWrapper">
<div className="tweetForm__count" role="status">
TODO : dummyTweet๋ก ์ ๋ฌ๋๋ ๋ฐ์ดํฐ์ ๊ฐฏ์๋ฅผ ๋ณด์ฌ์ค๋๋ค.
</div>
</div>
</div>
);
};
const Footer = () => {
return (
<div>
<img id="logo" src={`${process.env.PUBLIC_URL}/codestates-logo.png`} />
Copyright @ 2022 Code States
</div>
);
};
// TODO : Footer ํจ์ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค. ์๋ฉํฑ ์๋ฆฌ๋จผํธ footer๊ฐ ํฌํจ๋์ด์ผ ํฉ๋๋ค.
const Tweets = () => {
return (
<ul className="tweets">
{dummyTweets.map((tweet) => {
return (
<li className="tweet" key={tweet.id}>
<div className="tweet__profile">
{/* TODO: ํธ์ ์ ์์ ํ๋กํ ์ฌ์ง์ด ์์ด์ผ ํฉ๋๋ค. */}
</div>
<div className="tweet__content">
<div className="tweet__userInfo">
{/* TODO : ์ ์ ธ ์ด๋ฆ์ด ์์ด์ผ ํฉ๋๋ค. */}
{/* TODO : ์ด๋ฆ์ด "parkhacker"์ธ ๊ฒฝ์ฐ, ์ด๋ฆ ๋ฐฐ๊ฒฝ์์ rgb(235, 229, 249)์ผ๋ก ๋ฐ๊ฟ์ผ ํฉ๋๋ค. */}
{/* TODO : ํธ์ ์์ฑ ์ผ์๊ฐ ์์ด์ผ ํฉ๋๋ค. */}
</div>
TODO : ํธ์ ๋ฉ์ธ์ง๊ฐ ์์ด์ผ ํฉ๋๋ค.
</div>
</li>
);
})}
</ul>
);
};
const Features = () => {
return (
<section className="features">
<div className="tweetForm__container">
<div className="tweetForm__wrapper">
<div className="tweetForm__profile"></div>
<Counter />
</div>
</div>
<Tweets />
TODO : Footer ์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค.
</section>
);
};
const App = () => {
return (
<div className="App">
<main>
TODO : Sidebar ์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค.
<Features />
</main>
</div>
);
};
// ! ์๋ ์ฝ๋๋ ์์ ํ์ง ์์ต๋๋ค.
export { App, Sidebar, Counter, Tweets, Features, Footer };
ํ์ฌ ์ด ํ๋ฉด์ ์ด๋ ๊ฒ ๊ตฌํํด์ผ ํ๋ค.
๊ฐ์ด๋๋ผ์ธ์ ๋ฐ๋ผ ์ฝ๋ฉํ๋ ์ด๋ ต์ง ์์๋ค.
import React from "react";
import "./App.css";
import "./global-style.css";
import { dummyTweets } from "./static/dummyData";
// ! ์ ์ฝ๋๋ ์์ ํ์ง ์์ต๋๋ค.
const Sidebar = () => {
return (
<section className="sidebar">
<div>
<i className="far fa-comment-dots" />
</div>
</section>
);
};
const Counter = () => {
return (
<div className="tweetForm__input">
<div className="tweetForm__inputWrapper">
<div className="tweetForm__count" role="status">
total: {dummyTweets.length}
</div>
</div>
</div>
);
};
const Footer = () => {
return (
<footer>
<img id="logo" src={`${process.env.PUBLIC_URL}/codestates-logo.png`} />
Copyright @ 2022 Code States
</footer>
);
};
const Tweets = () => {
return (
<ul className="tweets">
{dummyTweets.map((tweet) => {
const isParkHacker = tweet.username === "parkhacker";
const tweetUserNameClass = isParkHacker
? "tweet__username tweet__username--purple"
: "tweet__username";
return (
<li className="tweet" key={tweet.id}>
<div className="tweet__profile">
<img src={tweet.picture} />
</div>
<div className="tweet__content">
<div className="tweet__userInfo">
<span className={tweetUserNameClass}>{tweet.username}</span>
{/* TODO : ์ด๋ฆ์ด "parkhacker"์ธ ๊ฒฝ์ฐ, ์ด๋ฆ ๋ฐฐ๊ฒฝ์์ rgb(235, 229, 249)์ผ๋ก ๋ฐ๊ฟ์ผ ํฉ๋๋ค. */}
<span className="tweet__createdAt">{tweet.createdAt}</span>
</div>
<span className="tweet__message">{tweet.content}</span>
</div>
</li>
);
})}
</ul>
);
};
const Features = () => {
return (
<section className="features">
<div className="tweetForm__container">
<div className="tweetForm__wrapper">
<div className="tweetForm__profile"></div>
<Counter />
</div>
</div>
<Tweets />
<Footer />
</section>
);
};
const App = () => {
return (
<div className="App">
<main>
<Sidebar />
<Features dummyTweets={dummyTweets} />
</main>
</div>
);
};
// ! ์๋ ์ฝ๋๋ ์์ ํ์ง ์์ต๋๋ค.
export { App, Sidebar, Counter, Tweets, Features, Footer };
map ๋ฉ์๋์ ์ํด li ํ๊ทธ์ ์ฝ์
๋๊ณ ์๋ ์๋ฆฌ๋จผํธ,
์ฆ, tweet์ ํ์ ์ ๋ฌด์์ผ๊น?
๋ฐ๋ก ๊ฐ์ฒด๋ค. ๊ฐ์ฒด์ ๊ฐ์ ์ฌ์ฉํ๋ ค๋ฉด dot notation์ ์ด์ฉํ๋ฉด ๋๋ ๊ฒ์ด ํฌ์ธํธ์๋ค.
์, ์ด์ ์ค๋น๋ ๋ชจ๋ ๋๋ฌ์ผ๋ parkhacker๋ฅผ ์ฐพ์๋ณด์.
์ฐ์ ๊ฐ๊ฐ์ tweet ์ค์์ usename์ด parkhacker์ธ ๊ฒฝ์ฐ๋ฅผ ์๋ก์ด ๋ณ์์ ํ ๋นํ๋ค.
const isParkHacker = tweet.username === 'parkhacker'
๋ค์์ ์ผํญ ์ฐ์ฐ์๋ฅผ ์ด์ฉํ์ฌ tweet.username์ด
parkhacker์ธ ๊ฒฝ์ฐ์ ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์ ๋ํด className์ ์ง์ ํด ์ค๋ค.
const tweetUserNameClass = isParkHacker
? 'tweet__username tweet__username--purple'
: 'tweet__username';
๋ง์ง๋ง์ผ๋ก ๊ฐ๊ฐ์ tweet์ด ๋ ๋๋ง ๋๊ณ ์๋ li tag ์์ username์ ๋ฐ๋ผ ๋ฐฐ๊ฒฝ์์ด ๋ฌ๋ผ์ง ์ ์๋๋ก ์ ์ฉํด ๋ณด์.
์ค๊ดํธ {}๋ฅผ ์ด์ฉํ JSX ๋ฌธ๋ฒ์ ํ๊ทธ์ ํ๊ทธ ์ฌ์ด๋ ๋ฌผ๋ก
className์ฒ๋ผ ํ๊ทธ ์์ ๋ค์ด๊ฐ๋ ์์ฑ์ ๊ฐ์๋ ์ฌ์ฉํ ์ ์๋ค.
<li className="tweet" key={tweet.id}>
<span className={tweetUserNameClass}>{tweet.username}</span>
</li>