npm init vite 로 프로젝트를 생성하여 vite를 이용하여 프로젝트를 진행해보자!

버튼처럼 여러곳에서 재사용되는 요소는 컴포넌트로 만들어 분리해두는 것이 유지보수 측면에서 좋다.
✔️ Counter.jsx
return (
<div id="counter">
<Button type="button" onClick={handleDown}>
-
</Button>
<Button type="button" onClick={(event) => handleReset(event)}>
0
</Button>
<Button type="button" onClick={handleUp}>
+
</Button>
<span>{count}</span>
</div>
);
위의 Counter.jsx에서 세개의 버튼이 필요하다. 이 버튼은 같은 디자인을 유지하고, 재사용을 할 수 있도록 하기 위해서 버튼을 컴포넌트화 해보자
✔️ Button.jsx
import "./Button.css";
export default function Button({ children, type = "button", onClick }) {
// 태그 사이에 있는 문자열은 children으로 받아오기
// 타입이 생략되어 있으면 button으로 기본값
return (
<button className="rounded-button" type={type} onClick={onClick}>
{children}
</button>
);
}
Button에 데이터 전달 (props 사용)
Counter.jsx 는 각 Button에 type, onClick 함수, children을 전달하여 버튼이 클릭 되었을 때 실행할 행동과 버튼에 표시될 내용을 정의한다.
상위 컴포넌트에서 하위 컴포넌트로 데이터 전달 (단방향 데이터 흐름)
Counter가 Button에 prop을 통해 데이터를 전달하는 방식이다.➡️ 즉, Counter는 상태와 이벤트 핸들러를 관리하고, Button은 이를 받아 사용하는 형태로 데이터가 흐른다.
Button이 직접 count 상태에 접근하지 않고, Counter가 데이터를 관리하며 Button에 필요한 함수를 전달하는 방식이다.
👀 내가 헷갈렸던 점
Button.jsx에서는 export를 했으니까 Counter에서 Button을 사용할 수 있는건데, Button에 {children, type, onClick} 은 Counter에서 어떻게 받아오는 지가 궁금했다.
이것에 대한 대답은 위에서 말했듯 props이다.
React가 컴포넌트에 전달된 props를 매개변수로 자동으로 전달해주기 때문이다.
Counter 컴포넌트에서 Button을 사용할 때, 다음과 같이 type, onClick, children 을 prop으로 전달한다.
<Button type="button" onClick={handleDown}>-</Button>
<Button type="button" onClick={handleReset}>0</Button>
<Button type="button" onClick={handleUp}>+</Button>
여기서 Button 컴포넌트는 함수의 매개변수 자리에 { children, type = "button", onClick }을 받는다.
이 형태는 객체 구조 분해 할당을 사용하여 props 객체를 바로 분해해 필요한 값들을 꺼내오는 방식이다.
export default function Button({ children, type = "button", onClick }) {
return (
<button className="rounded-button" type={type} onClick={onClick}>
{children}
</button>
);
}
React는 Button 컴포넌트를 렌더링할 때 type, onClick, children 등 전달된 값을 모두 하나의 객체(props)로 만들어서 Button 컴포넌트 함수에 전달한다.
그러면 { children, type = "button", onClick } 형태의 구조 분해 할당으로 type, onClick, children을 쉽게 사용할 수 있게 된다.
결론
따라서, Button 함수 내부에서 { children, type, onClick }을 별도로 정의할 필요 없이, Counter에서 전달된 props를 자동으로 받아와서 사용할 수 있는 것이다.
(React 덕분에!)
children은 React에서 컴포넌트의 여는 태그와 닫는 태그 사이에 있는 모든 내용을 나타내는 특별한 prop이다.
예를 들면 <Button>내용</Button>과 같은 컴포넌트를 사용할 때, Button 태그 사이에 있는 텍스트나 요소들이 자동으로 children에 전달된다.
이를 통해 컴포넌트의 태그 사이에 자유롭게 내용을 넣을 수 있고, Button이 재사용 가능한 컴포넌트로 활용될 수 있다.
객체나 배열의 속성을 쉽게 추출해서 변수에 할당하는 JavaScript 문법이다.
가독성
객체나 배열의 형태에 따라 원하는 값을 간단하게 뽑아낼 수 있어서 코드의 가독성을 높이고, 코드 작성도 간편하게 만들어준다.
원본 객체 보호
구조 분해 할당으로 추출한 변수는 원본 객체의 값과 다른 독립적인 복사본이므로
이 변수를 수정해도 원본 객체에는 영향을 미치지 않는다.
➡️ 원본 객체를 건드리지 않고 데이터 가공 / 변형을 할 수 있다.
객체에서 필요한 속성만 선택해서 변수에 할당할 수 있다.
const user = {
name: "Alice",
age: 25,
location: "Seoul"
};
⭐️ const { name, age } = user; ⭐️
console.log(name); // "Alice"
console.log(age); // 25
// user.name이나 user.age를 직접 사용하는 대신 name, age 변수를 바로 사용할 수 있다.
// 변수를 수정해도 원본 객체는 바뀌지 않으므로 데이터를 자유롭게 변경할 수 있다.
배열의 순서를 기준으로 변수에 값을 할당한다.
const colors = ["red", "green", "blue"];
⭐️ const [first, second] = colors; ⭐️
console.log(first); // "red"
console.log(second); // "green"
⭐️ const [ , , third] = colors; ⭐️ // 필요한 위치의 요소만 골라서 할당
console.log(third); // "blue"
아래의 props를 매개변수에서 구조 분해하여 필요한 값만 받아오는 예시에서 쓰인다.
function greet(⭐️{ name, age }⭐️) {
console.log(`Hello, ${name}! You are ${age} years old.`);
}
const person = { name: "Alice", age: 25 };
greet(person); // "Hello, Alice! You are 25 years old."
구조 분해 할당을 사용하면 React 컴포넌트에서 원하는 prop만 선택해서 받아올 수 있다.
React는 모든 props를 한꺼번에 props 객체로 전달하므로, 필요한 prop만 구조 분해하여 받아오는 방식으로 처리할 수 있다.
✔️ Button 컴포넌트에서 {children, onClick} 만 받고 싶은 경우
export default function Button({ children, onClick }) { // 필요한 prop만 받아옴
return (
<button className="rounded-button" onClick={onClick}>
{children}
</button>
);
}
✔️ 나머지 props 모두 처리하기
나머지 props를 모두 받고 싶다면, rest 연산자(...)를 사용할 수 있다.
children, onClick 외에도 추가로 전달된 모든 prop를 rest 객체에 모아서 <button>에 전달할 수 있다.
export default function Button({ children, onClick, ...rest }) {
return (
<button className="rounded-button" onClick={onClick} {...rest}>
{children}
</button>
);
}
구조 분해 할당을 사용하지 않고 props 객체를 그대로 받으면, 모든 prop에 대해 props.속성명 형태로 접근할 수 있다.
export default function Button(props) {
return (
<button className="rounded-button" type={props.type} onClick={props.onClick}>
{props.children}
</button>
);
}
이렇게 하면 props 객체를 통해 type, onClick, children에 각각 접근할 수 있다. 이 방식의 장점은 필요한 props를 한 번에 다루고 구조 분해를 생략할 수 있다는 점이다.
하지만 props의 개수가 많아질 경우, props.를 계속 반복해서 사용해야하므로 코드가 길어질 수 있다.
👀 내가 헷갈렸던 점
그럼 위의 구조 분해 할당에서 알아본 것과 같이 props.속성명을 사용하면 원본 객체가 수정되는 문제가 발생하는 것 아닐까?
결론
React에서는props가 읽기 전용이기 때문에,props객체를 직접 수정할 수 없다.
React에서 컴포넌트의 props는 부모 컴포넌트로부터 내려받은 데이터이다. props는 기본적으로 읽기 전용이기 때문에 자식 컴포넌트에서 변경할 수 없다. 만약 props.속성명을 통해 원본 객체를 수정하려고 하면, 에러가 발생하거나 의도한 대로 동작하지 않는다.
props를 변경하고 싶을 때는?만약 props를 기반으로 새로운 값을 사용하거나 업데이트해야 한다면 state를 활용한다.
예를 들어, 컴포넌트 내부에서 props를 초기값으로 한 상태를 만든 뒤, 그 상태를 업데이트하여 사용할 수 있다.
아래 코드에서 initialCount는 props로 전달되지만, 실제 값 변경은 count라는 state에서 이루어지므로 initialCount 자체는 변경되지 않는다.
function Counter({ initialCount }) {
const [count, setCount] = React.useState(initialCount); // 여기서 상태 변경
const increment = () => setCount(count + 1);
return (
<div>
<button onClick={increment}>Increase</button>
<p>Count: {count}</p>
</div>
);
}
간결한 코드
구조 분해 할당을 사용하면 코드가 더 짧고 읽기 쉬워진다.
많은 props가 필요할 때
필요한 prop만 골라서 사용할 수 있어 불필요한 정보는 무시할 수 있다.
결론적으로, props.속성명을 사용하는 방법은 props 객체 전체를 다루기 편할 때 유용하지만, 구조 분해 할당은 코드의 가독성과 간결함을 높이는 데 더 유리하다.