function App() {
return (
<>
<h1>안녕 리액트!</h1>
</>
)
}
const Header = () => { // 자식 컴포넌트
return (
<header>
<h1>header</h1>
</header>
)
}
function App() { // 부모 컴포넌트
return (
<>
<Header /> {/* 나 요기 */}
<h1>안녕 리액트!</h1>
</>
)
}
자식 컴포넌트: 다른 컴포넌트 내부에 삽입되는 컴포넌트
부모 컴포넌트: 자식 컴포넌트를 포함
├── components // 컴포넌트 폴더
│ └── Header.jsx
├── App.css
├── App.jsx
├── index.css
└── main.jsx
컴포넌트 폴더를 따로 생성하여, 그 속에서 Header와 같은 파일을 개발
그 후, App.jsx이나 부모 컴포넌트 파일에서 import로 불러와 사용
JavaScriptExtensions 으로
확장된 자바스크립트 문법을 말함
const Footer = () => {
const myName = '미나리';
return (
<footer>
<h1>안녕 내 이름 {myName}이야</h1>
</footer>
);
};
export default Footer;
위와 같이 중괄호 {} 안에 변수를 넣어 동적으로 값을 렌더링할 수 있음
div 태그 안에 style 속성으로 CSS 지정 가능
background-color)이 아닌 카멜 표기법(camelCase) 사용 (예: backgroundColor) style은 객체 형태로 작성 <div style={{ backgroundColor: "blue" }}>
내용
</div>
div 안에 직접 작성 시 코드가 길어지고 가독성 저하 가능
→ /components 폴더에 파일.css 생성 후 클래스 작성하여 import 방식 사용 권장
이때 CSS 클래스 지정 시 class가 아닌 className 사용
import "./Box.css";
<div className="box">
내용
</div>


첫번째 네이버 포털 속 버튼 UI를 만들기 위해선 버튼 컴포넌트를 만들어서 이미지와 텍스트만 바꿔가며 렌더링 하면 될 것임
이 때, 각각 버튼(컴포넌트)에 전달된 값들을 Props(Properties)라고 함
...
return (
<>
<Button text={"메일"} color={"red"} />
<Button text={"카페"} />
<Button text={"블로그"} />
</>
)
버튼 컴포넌트를 사용하는 곳에서 위와 같은 props 가능
const Button = ({ text, color = "black" }) => {
return <button style={{ color: color }}>
{text}
</button>
};
기본값 문법으로 기본값을 설정할 수 있음
const buttonProps = {
text: "메일",
color: "red",
a: 1,
b: 2,
c: 3,
}
return (
<>
<Button {...buttonProps} />
<Button text={"카페"} />
<Button text={"블로그"} />
</>
)
스프레드 연산자를 통해 많은 값을 전달 가능
HTML 요소나 컴포넌트도 children prop로 전달 가능
//App
<Button text={"블로그"}>
<div>자식요소</div>
</Button>
---
//Button
const Button = ({ children, text, color = "black" }) => {
return <button style={{ color: color }}>
{text}
{children}
</button>
};
<button
onClick={onClickButton}
으로 클릭 시 이벤트 처리
const onClickButton = (e) => {
// 이벤트 객체
console.log(e);
여기서 e란 합성 이벤트 객체(SyntheticEvent)이다.

브라우저마다 다른 이벤트 객체 때문에 일어나는 Cross Browsing Issue는
일종의 통일된 규격의 합성 이벤트 객체로 일어나지 않음.

리액트의 컴포넌트는 이런 State를 가질 수 있음
State의 변화를 통해 렌더링이 다시 진행되는 것을 Re-Render 혹은 Re-Rendering이라고 부른다.
const state = useState(0);
console.log(state);
를 통해 state를 확인하면
[0, ƒ] 처럼 배열 형태로 반환되는 걸 볼 수 있음
0 번째 인덱스에는 state의 값
1 번째 인덱스에는 상태 변화 함수
그래서 보통
const [state, setState] = useState(0);처럼
배열로 만들고, 필요한 것을 사용함
예시 코드
const [light, setLight] = useState("off");
return(<>
...
<div>
<h1>{light}</h1>
<button
onClick={() => {
setLight(light === "on" ? "off" : "on");
}}>
{light === "on" ? "끄기" : "켜기"}
</button>
</div>
</>
토글 버튼을 만들 수 있음
근데 강의 속 다른 수강자들은 의문점이 들었다고 한다.
🤷: light와 같은 state가 아닌 let 같은 일반 변수로는 안됨?
-> 일반 변수로는 리렌더링이 되지 않아 변화하지 않는다.

강의 때 작성한 예시 컴포넌트에서
위는 count, 아래는 light 라는 state인데,
카운트 버튼을 누르면 Bulb 속 colsole.log가 실행된다.
리액트 컴포넌트의 리렌더링 발생 상황
3번을 이유로 자식 컴포넌트가 많아지면 성능이 안좋아질 수 밖에 없음
→ 관련이 없는 것들은 다른 컴포넌트로 분리하는 것이 좋음
모듈화를 통해 분리하면 Best
사용자 입력값을 관리할 때 가장 기본이 되는 방법은 useState
각 input, select, textarea에 대응하는 state를 만들고, onChange 이벤트에서 값을 업데이트하면 된다.
//Register.jsx
...
import { useState } from 'react';
const Register = () => {
const [name, setName] = useState('');
const [birth, setBirth] = useState('');
const [country, setCountry] = useState('한국');
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="이름"
/>
<input
type="date"
value={birth}
onChange={(e) => setBirth(e.target.value)}
/>
<select value={country} onChange={(e) => setCountry(e.target.value)}>
<option value="">국가 선택</option>
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="uk">영국</option>
</select>
</div>
);
};
useState로 입력값 관리
const [name, setName] = useState('');
value와 onChange 연결
value → state onChange → state 업데이트 함수 onChange에서 값 업데이트
setName(e.target.value);
위 예제 코드를 보면 많은 state들이 반복해서 있는데 이걸 객체표현처럼
작성할 수 있다. 이 때 onChange 내부에 ...input으로 나머지 state도 포함되어 있어야, 값이 유지도니다.
...
const [input, setInput] = useState({
name: "",
birth: "",
country: "",
bio: "",
})
const onChangeName = (e) => {
setInput({
...input,
name: e.target.value,
});
};
... 이어서 작성
개선: 공통 onChange 사용
const onChange = (e) => {
setInput({
...input,
[e.target.name]: e.target.value,
});
};
...
<input
name="name"
value={input.name}
onChange={onChange}
placeholder='이름'
/>
| 구분 | useRef | useState |
|---|---|---|
| 생성 | const ref = useRef(initialValue) | const [state, setState] = useState(initialValue) |
| 값 변경 | ref.current = value | setState(value) |
| 컴포넌트 내부 변수 활용 | ✅ 가능 | ✅ 가능 |
| 렌더링 영향 | ❌ 값 변경 시 컴포넌트 리렌더링 안됨 | ✅ 값 변경 시 컴포넌트 리렌더링 발생 |
| 용도 | DOM 접근, 이전 값 저장, 렌더링 영향 없는 값 | UI 상태 관리, 렌더링 반영 필요한 값 |
| 장점 | 렌더링 최적화, 불필요한 렌더링 방지 | 값 변경 시 UI 자동 업데이트 |
| 단점 | 값 변경해도 UI 반영 안 됨 | 빈번한 상태 변경 시 성능 부담 가능 |
const refObj = useRef(0); //로 생성 시,
// {current: 0} 로 출력
useRef는 컴포넌트의 렌더링에 영향을 미치지 않는 변수를 생성할 때 활용
...
const countRef = useRef(0);
const inputRef = useRef();
..
const onChange = (e) => {
countRef.current++;
console.log(countRef.current);
setInput({
...input,
[e.target.name]: e.target.value,
});
};
const onSubmit = () => {
if (input.name === "") {
inputRef.current.focus();
alert("이름을 입력해주세요.");
return;
}
alert("회원가입 완료!");
};
사용된 Ref는 사용자가 수정할 때 마다 count 해주는 것
근데 useRef가 아니라 그냥 전역변수처럼 외부에다가 let count 해주면 되는거 아님?
→ "사실 그러면 count가 되긴 한다."
하지만 컴포넌트를 여러 번 생성하면,
값이 모든 인스턴스에서 공유되어 의도치 않은 결과가 발생할 수 있다.
따라서 useRef를 사용하여 각 컴포넌트 인스턴스마다 독립적인 값을 유지하는 것이 안전하다.
사실2017년도 이전의 리액트에서는
클래스 컴포넌트를 대부분 사용했다. 조금 전 본 [State, Ref, etc...]를 모두 사용 가능했다.
함수 컴포넌트는 UI 렌더링만 할 수 있었다.
하지만 클래스 컴포넌트는 문법이 복잡했고,
결국 클래스 컴포넌트의 기능을 함수 컴포넌트에서도 쓸 수 있게 해주는 React Hooks가 되었다.
이미 use 가 앞에 붙어서 유추할 수 있다.
해당 리액트 훅들은 반복문이나 조건문에서는 사용이 불가능하다.
3가지 Hook 팁
// 입력값 상태와 onChange 로직을 재사용하는 커스텀 훅
function useInput() {
const [input, setInput] = useState("");
const onChange = (e) => {
setInput(e.target.value);
};
return [input, onChange];
}
---
// 커스텀 훅을 사용하는 컴포넌트
const HookExam = () => {
const [input, onChange] = useInput();
return (
<div>
<input value={input} onChange={onChange} />
</div>
);
};
export default HookExam;
useInput 훅 안에서 상태와 onChange 로직을 캡슐화 여기서 커스텀 훅인 useInput은
├── 📁 assets
├── 📁 components
├── 📁 hooks
...
components 폴더에 있는게아닌 따로 hooks 폴더를 만들어 그곳에서 관리하는게 좋다.
(물론 import문 작성 필수)