안녕하세요! 웹파트 OB 김건휘입니다. 이번 시간에는 리액트 공식문서 - UI표현하기(첫번째 컴포넌트, 컴포넌트 import 및 export하기, JSX로 마크업 작성하기, 중괄호에 있는 JSX안에서 자바스크립트 사용하기, 컴포넌트에 props 전달하기)를 읽고 정리한 내용에 대한 아티클을 작성해보겠습니다.
🌐컴포넌트란?
컴포넌트는 리액트 앱을 구성하는 기본 단위로, 재사용 가능한 UI 조각을 말한다. 이 컴포넌트들은 각각 자체적인 렌더링 로직과 상태 관리를 가지며, 복잡한 UI를 구성하기 위해 서로 조합하고 재사용할 수 있습니다.
다음 사진과 같이 컴포넌트들를 조합하여 화면을 구성할 수 있다. 컴포넌트를 한 번 정의한 다음 원하는 곳에서 원하는 만큼 여러 번 사용할 수 있다는 점이 바로 React의 킥이라고 할 수 있다.
🔥주의사항 - React 컴포넌트는 대문자로 시작해야 한다
<Component />처럼 JSX에서 컴포넌트는 대문자로 시작해야 React가 이를 사용자 정의 컴포넌트로 인식한다.
//Profile.jsx
export default function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
);
}
//Gallery.jsx
import Profile from './Profile.js';
export default function Gallery() {
return (
<section>
<Profile />
<Profile />
<Profile />
</section>
);
}
import와 export를 사용하여, 다음과 같이 별도의 파일에서 정의해준 Profile 컴포넌트를 import해와서 원하는 곳에서 여러번 재사용할 수 있다. 또한, 리액트는 JSX로 마크업을 작성한다.
🌐JSX(JavaScript XML)란?
JSX는JavaScript와 HTML을 결합한 문법으로, React에서 UI를 정의할 때 사용하는 문법이다. JSX는 자바스크립트 코드 안에서 HTML과 비슷한 구조를 표현할 수 있게 해주며, React 컴포넌트의 구조를 직관적으로 작성할 수 있게 도와준다.
📌JSX의 특징 및 동작 원리
1. HTML과 유사하지만 JavaScript 확장 문법
JSX는 HTML과 비슷하게 보이지만, 내부적으로 JavaScript로 변환된다. 브라우저는 JSX를 이해하지 못하므로 Babel 같은 도구가 JSX 코드를 React.createElement() 함수 호출로 변환한다.
const element = <h1>Hello, World!</h1>;
위 코드는 Babel(React에서 사용하는 JSX 문법을 브라우저가 이해할 수 있는 JavaScript 코드로 변환해주는 도구)을 통해 다음과 같이 변환된다.
const element = React.createElement('h1', null, 'Hello, World!');
2. JavaScript 표현식 사용 가능 ({} 중괄호)
JSX 내부에서는 중괄호 {}를 사용하여 JavaScript 표현식을 사용할 수 있다.
const name = 'Alice';
const element = <h1>Hello, {name}!</h1>;
- 조건문, 반복문, 함수 호출 등의 자바스크립트 코드를 JSX 안에 넣어 사용할 수 있다.
📌JSX의 장점
1. UI의 명확한 구조 표현
HTML과 유사한 문법을 사용하므로, 컴포넌트의 UI 구조를 쉽게 이해할 수 있다. => 적절한 시맨틱 태그의 중요성이 JSX에도 적용된다.
2. JavaScript와의 강력한 결합
JavaScript 표현식을 사용해 동적 데이터를 다루기 쉬워진다.
🌐컴포넌트에 props 전달하기
React 컴포넌트는 props를 이용해 서로 통신할 수 있다. 부모 컴포넌트는 props를 줌으로써 몇몇의 정보를 자식 컴포넌트에게 전달할 수 있다. 객체, 배열, 함수를 포함한 모든 JavaScript 값을 전달할 수 있다. 리액트에서 props는 정말 중요한 역할을 한다. props를 사용하지 않는다면 리액트를 사용할 이유가 없을 정도로 강력한 무기이다. props는 리액트에서 매우 중요한 개념이므로 제대로 이해하고 넘어가자!
📌props를 활용하는 다양한 상황
1. 부모-자식 간 데이터 전달
- 상위 컴포넌트가 하위 컴포넌트에 데이터를 전달하는 방식이 props
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
function App() {
return <Greeting name="Alice" />;
}
부모 컴포넌트(App)에서 자식 컴포넌트(Greeting)으로 name을 props로 전달하고 있다.
2. 컴포넌트의 재사용성 증가
- 동일한 컴포넌트를 다양한 props로 호출하여 여러 상황에 맞게 재사용할 수 있다.
function Button({ label, color }) {
return <button style={{ backgroundColor: color }}>{label}</button>;
}
function App() {
return (
<>
<Button label="Primary" color="blue" />
<Button label="Danger" color="red" />
</>
);
}
button의 text와 backgroundColor를 props로 전달해주어 Button 컴포넌트의 재사용성을 증가 시켜줄 수 있다.
3. UI와 로직의 분리
function UserProfile({ user }) {
return <h1>{user.name}</h1>;
}
function App() {
const user = { name: "Alice", age: 25 };
return <UserProfile user={user} />;
}
부모 컴포넌트가 데이터를 제어하고, 자식 컴포넌트는 데이터를 표현하는 역할만 담당하게 할 수 있다.
4. 콜백 함수로 상위 컴포넌트와 소통 가능
function Button({ onClick }) {
return <button onClick={onClick}>Click Me</button>;
}
function App() {
const handleClick = () => alert('Button clicked!');
return <Button onClick={handleClick} />;
}
props를 통해 자식 컴포넌트가 콜백 함수를 호출하여 상위 컴포넌트와 소통할 수 있다. 이를 통해 양방향 데이터 흐름이 가능하다.
5. 조건부 렌더링과 동적 UI 구현 가능
function Message({ isLoggedIn }) {
return <p>{isLoggedIn ? 'Welcome back!' : 'Please log in.'}</p>;
}
function App() {
return <Message isLoggedIn={true} />;
}
props를 통해 동적으로 데이터를 변경하면서 UI를 제어할 수 있다. 이를 통해 조건부 렌더링이나 다양한 UI 상태를 쉽게 구현할 수 있다.
6. 컴포넌트 간 결합도 감소
- props를 통해 데이터를 명시적으로 전달함으로써 컴포넌트들이 서로 느슨하게 결합된다. => 응집도와 결합도가 뭔데?
❗️응집도 (Cohesion)
응집도는 하나의 컴포넌트 내부에 존재하는 요소들이 서로 얼마나 밀접하게 관련되어 있는지를 나타낸다. 응집도가 높은 컴포넌트는 잘 정의된 목적을 가지고 있으며, 해당 컴포넌트 안의 모든 기능이 이 목적과 직접적으로 관련되어 있다. 응집도가 높으면 컴포넌트의 재사용성, 유지보수성, 이해하기 쉬움이 향상된다.
❗️결합도 (Coupling)
결합도는 서로 다른 컴포넌트들 간의 의존성의 정도를 나타낸다. 결합도가 낮은 시스템은 각 컴포넌트가 독립적으로 작동하며, 다른 컴포넌트와의 의존성이 적다. 결합도가 낮을수록 컴포넌트를 수정하거나 대체하기가 더 쉬워지고, 시스템의 유연성이 증가한다.
- 컴포넌트 내부에서 직접 상태를 가지지 않고, 외부에서 필요한 데이터를 주입받으므로 테스트와 유지보수가 쉬워진다.
7. 상태 관리와의 통합
function Counter({ count }) {
return <h1>Count: {count}</h1>;
}
function App() {
const [count, setCount] = React.useState(0);
return (
<>
<Counter count={count} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
}
props는 상태(state)와 함께 사용되어 더 복잡한 UI와 데이터 흐름을 쉽게 관리할 수 있게 해준다.
상태와 props를 함께 사용해 상위 컴포넌트에서 데이터 관리를 하고, 자식 컴포넌트는 데이터를 표시하기만 한다.
8. 컴포넌트 간 확장성 제공 (children)
// Card.js
import React from 'react';
function Card({ children }) {
return (
<div style={{ border: '1px solid #ddd', padding: '16px', borderRadius: '8px' }}>
{children}
</div>
);
}
export default Card;
// App.js
import React from 'react';
import Card from './Card';
function App() {
return (
<div style={{ padding: '20px' }}>
<Card>
<h2>Welcome</h2>
<p>This is a reusable card component.</p>
</Card>
<Card>
<img src="https://via.placeholder.com/150" alt="Sample" />
<p>Image inside a card.</p>
</Card>
<Card>
<button onClick={() => alert('Button clicked!')}>Click Me</button>
</Card>
</div>
);
}
export default App;
Card 컴포넌트는 children을 사용하여 동적으로 콘텐츠를 삽입할 수 있다.
어떤 종류의 콘텐츠도 전달 가능하며, 이를 통해 컴포넌트의 재사용성이 높아진다. => children을 사용하면 하나의 컴포넌트로 여러 종류의 UI를 처리할 수 있다.
🔥children을 잘 활용하면 변경에 유연한, 확장 가능성있는 컴포넌트를 설계할 수 있다!!
- Props를 전달하려면 HTML 어트리뷰트를 사용할 때와 마찬가지로 JSX에 props를 추가합니다.
- Props를 읽으려면 function Avatar({ person, size }) 구조 분해 할당 문법을 사용합니다.
- size = 100 과 같은 기본값을 지정할 수 있으며, 이는 누락되거나 undefined 인 props에 사용됩니다.
- 모든 props를 <Avatar {...props} />로 전달할 수 있습니다. JSX spread 문법을 사용할 수 있지만 과도하게 사용하지 마세요!
- <Card><Avatar /></Card>와 같이 중첩된 JSX는 Card컴포넌트의 자식 컴포넌트로 나타납니다.
- Props는 읽기 전용 스냅샷으로, 렌더링 할 때마다 새로운 버전의 props를 받습니다.
- Props는 변경할 수 없습니다. 상호작용이 필요한 경우 state를 설정해야 합니다.
해당 내용은 공식문서 컴포넌트에 props 전달하기 카테고리의 마지막에 나와있는 요약본이다.
해당 내용을 읽고 바로바로 이해가 되고 납득이 되었다면 props에 대해서 어느정도 통달?했다고 할 수 있다.
"Props는 변경할 수 없습니다. 상호작용이 필요한 경우 state를 설정해야 합니다."
function Counter({ initialCount }) {
// 잘못된 예: props를 직접 수정 시도
initialCount += 1; // ❌ props는 변경할 수 없습니다.
return <h1>Count: {initialCount}</h1>;
}
function App() {
return <Counter initialCount={0} />;
}
리액트는 부모에서 전달된 props를 불변(immutable)으로 간주한다. 따라서 위와 같이 자식 컴포넌트는 props를 직접 변경할 수 없다.
=> 상호작용이 필요한 경우 state(상태)를 사용해야 한다!
import { useState } from 'react';
function Counter({ initialCount }) {
// state를 사용해 동적으로 값 관리
const [count, setCount] = useState(initialCount);
const increment = () => setCount(count + 1); // count 값 증가
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
}
function App() {
return <Counter initialCount={0} />;
}
export default App;
사용자 상호작용(버튼 클릭)에 따라 값을 변경하려면 state를 사용해 변경 가능한 상태를 관리해야 한다.
useState 훅을 사용하면 초기값으로 initialCount를 설정하고, 상태가 변경될 때마다 컴포넌트가 리렌더링된다.
🧐궁금 Point
리액트 공식문서를 읽으면서 들었던 궁금증 중 하나는 JSX에서 함수를 직접 전달하는 것과 래핑(wrapping)한 함수를 전달하는 것의 차이점은 무엇일까?이다.
- 예를들어서 JSX 안에서 <button onClick={() => handleClick()} />과 <button onClick={handleClick} /> 있으면, 성능과 가독성에 어떤 영향 들이 있는지 차이점과 장단점에 대해서 궁금했다.
React에서 props는 불변(immutable)이어야 한다고 하지만, 만약 컴포넌트 내에서 props를 직접 수정하면 어떤 문제들이 발생할까?
- 예측 가능성을 높이기 위해 props를 불변으로 간주하는건 납득이 가지만, 오히려 리소스가 적은 props 변경에 있어서는 state를 사용하여 얻는 성능문제(리렌더링 발생)보다 이점이 크지 않을까?라는 궁금증이 들었다.
'나야, 리액트 스터디' 카테고리의 다른 글
[week 1] 리액트 컴포넌트와 JSX (4) | 2024.10.27 |
---|---|
[week1] 컴포넌트 딥다이브 🌊 (4) | 2024.10.27 |
[week1] - 첫번째 컴포넌트, 컴포넌트 import 및 export하기, JSX로 마크업 작성하기, 중괄호에 있는 JSX안에서 자바스크립트 사용하기, 컴포넌트에 props 전달하기 (4) | 2024.10.27 |
[week 1] - UI 표현하기 (컴포넌트, JSX, props) (4) | 2024.10.27 |
[week 1]- function Component ? class Component? (4) | 2024.10.25 |