실습 준비하기
npm create vite@latest → 파일 명 입력 → React → JavaScript
npm i → npm run dev → 로컬 호스트 주소 들어가기
불필요한 파일을 삭제해보자!
public 폴더 - vite.svg 삭제
src 폴더 - assets 폴더 - react.svg 삭제
App.jsx 파일 수정
import './App.css';
function App() {
return (
<>
<div>
<h1>Welcome to React</h1>
</div>
</>
);
}
export default App;
App.css, index.css 작성된 스타일들 모두 삭제
main.jsx 파일 수정
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(<App />);
.eslintrc.cjs 파일 수정 - 맨 마지막 두줄 보기
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'no-unused-vars': ['off', { varsIgnorePattern: '^[A-Z_]' }],
'react/prop-types': 'off',
},
},
];
no-unused-vars - 실제로 사용하지 않는 변수가 있을때 오류가 발생하면 혼란스럽기때문에 off해준다.
react/prop-types - 나중에 리액트를 배우고 나서 좀 더 안전하게 사용할 수 있도록 해주는 옵션인데 일단 off해준다.
React 컴포넌트
함수로 만든 컴포넌트를 React에서는 함수 컴포넌트라고 부른다. 화살표 함수로도 만들 수 있다.
import './App.css';
// html 태그를 반환하도록 설정하는 것 => 컴포넌트
// 컴포넌트를 부를때는 보통 함수 이름을 부른다.
const Header = () => {
return (
<header>
<h1>header</h1>
</header>
);
};
// 이거는 App 컴포넌트
function App() {
return (
<>
<div>
<h1>Welcome to React</h1>
</div>
</>
);
}
function Header() {
return (
<header>
<h1>header</h1>
</header>
);
}
export default App;
클래스보다는 함수를 이용해서 만드는 것이 훨씬 일반적이다 .
⚠️ 함수컴포넌트로 만들때 주의사항
컴포넌트를 생성하는 함수의 이름은 반드시 첫글자가 대문자여야한다.
현재 위의 코드를 실행시켜보면 header 텍스트가 안나타나기에
import './App.css';
// html 태그를 반환하도록 설정하는 것 => 컴포넌트
// 컴포넌트를 부를때는 보통 함수 이름을 부른다.
const Header = () => {
return (
<header>
<h1>header</h1>
</header>
);
};
// 이거는 App 컴포넌트
function App() {
return (
<>
<div>
{/* 여기에 헤더를 넣어보자 */}
<Header />
<h1>Welcome to React</h1>
</div>
</>
);
}
export default App;
App 컴포넌트 안에 Header 컴포넌트를 넣어준다.
Header 컴포넌트를 App 컴포넌트에 넣는 것을 자식 컴포넌트라고 부른다.
그렇다면 App 컴포넌트는 뭐가 될까? 부모 컴포넌트이다.
계층 구조
-----------------------
App 컴포넌트
| |
Header 컴포넌트
-----------------------
리액트의 컴포넌트들은 화면에 표시되기 위해 자식 컴포넌트로서 존재해야된다.
모든 리액트 컴포넌트들은 App 컴포넌트를 최상위 조상을 갖는 계층 구조이다.
이때 조상 역할을 하는 App 컴포넌트를 모든 컴포넌트의 뿌리역할 => 루트 컴포넌트 라고 부른다.
루트 컴포넌트는 main.jsx 파일에 render 메서드의 인수로서 전달이 된다.
관례상 App 컴포넌트를 루트 컴포넌트로 설정한다.
모듈화를 위해 컴포넌트별로 각각 파일을 나눠서 작성한다.
src 파일안에 components파일을 새롭게 추가한다! → Header 컴포넌트를 보관하기 위해 header.jsx 파일 생성
const Header = () => {
return (
<header>
<h1>header</h1>
</header>
);
};
// header 컴포넌트를 내보내기 위해 export default Header를 사용한다.
export default Header;
그러면 App.jsx에서
import Header from './Header.jsx';를 입력해준다.
vite에서는 import할 때 .jsx인 확장자를 생략해도 된다. 왜냐면.. 파일을 찾아가도록 내부에서 자동 설정되어있기 때문이다 .
JSX로 UI 표현하기
JSX란?
확장된 자바스크립트 문법을 의미한다.

이렇게 자바스크립트랑 HTML 코드를 혼용해서 작성가능하다.
⚠️ JSX 주의 사항
1. 중괄호 내부에는 자바스크립트 표현식만 넣는다.
- 자바스크립트 표현식? 삼항 연산자나 변수의 이름처럼 특정 값으로 평가
중괄호 안에 if문을 작성하면 오류 발생
2. 숫자, 문자열, 배열 값만 랜더링
const Main = () => {
const number = 10;
const obj = { a: 1 };
return (
<main>
<h1>main</h1>
{/* js값을 html로 렌더링하고 싶으면 js코드에 중괄호를 작성해야된다. */}
{/* 삼항 연산자도 사용이 가능하다 */}
<h2>{number % 2 === 0 ? '짝수' : '홀수'}</h2>
{obj.a}
</main>
);
};
export default Main;
객체를 랜더링 할 경우
{obj}만 쓰면 오류 발생, 점 표기법을 통해 문자열이나 숫자 값을 렌더링한다.
3. 모든 태그는 닫혀있어야한다.
4. 최상위 태그는 하나여야한다.
적절한 태그가 없을 경우 빈태그로 묶어줘도 괜찮다.
컴포넌트가 조건에 따라 각각 다른 UI를 렌더링하도록 만들어보기
const Main = () => {
const user = {
name: '이정환',
isLogin: true,
};
return <>{user.isLogin ? <div>로그아웃</div> : <div>로그인</div>}</>;
};
export default Main;
조건에 따라 로그아웃, 로그인이 된다.
삼항 연산자가 아닌 조건문을 통해 작성가능하다.
const Main = () => {
const user = {
name: '이정환',
isLogin: true,
};
if (user.isLogin) {
return <div>로그아웃</div>;
} else {
return <div>로그인</div>;
}
// return <>{user.isLogin ? <div>로그아웃</div> : <div>로그인</div>}</>;
};
export default Main;
DOM 요소에 스타일을 적용하는 방법 → JSX에서는 스타일을 설정하는 두가지 방법
1. 요소에 직접 스타일을 설정하는 방법 → 카멜 케이스 작성
단점 - 스타일 규칙이 많아질 수록 가독성 나빠진다.
const Main = () => {
const user = {
name: '이정환',
isLogin: true,
};
if (user.isLogin) {
return <div style={{ backgroundColor: 'red' }}>로그아웃</div>;
} else {
return <div>로그인</div>;
}
// return <>{user.isLogin ? <div>로그아웃</div> : <div>로그인</div>}</>;
};
export default Main;
2. 별도의 CSS 파일을 만들어서 스타일링한다.
생성한 CSS 파일을 모듈 시스템으로 부터 불러온다.
main.jsx파일에 import 구문을 작성한다.
컴포넌트에서 CSS 파일을 불러올때는 import 뒤에 파일의 경로만 입력하면 알아서 불러온다.
✔️ className으로 작성하기!
import './Main.css';
const Main = () => {
const user = {
name: '이정환',
isLogin: true,
};
if (user.isLogin) {
return <div className="logout">로그아웃</div>;
} else {
return (
<div div className="logout">
로그인
</div>
);
}
// return <>{user.isLogin ? <div>로그아웃</div> : <div>로그인</div>}</>;
};
export default Main;
.logout {
background-color: red;
}
Props로 데이터 전달하기
Props란? 컴포넌트에 값 전달하기

부모 컴포넌트가 자식 컴포넌트 들에게 마치 함수의 인수를 전달하듯 원하는 값을 전달해주는게 가능하다.
이렇게 전달된 값을 props라고 한다.
props를 이용하면 좋은점
props를 이용하면 컴포넌트를 마치 함수를 호출하듯이 전달하는 값에 따라서 각각 다른 UI를 렌더링하도록 만들 수 있다.

전달받은 props의 값을 활용하는 방법
props라는 매개변수에 객체 형태로 값이 저장되어있음으로 점 표기법을 사용해 props.text를 렌더링하도록 설정하면 버튼 안에 각각의 렌더링된다.

그렇다면 컬러의 값도 활용하려면 어떻게 해야할까?
버튼의 style을 color는 props의 color라고 설정해주면 된다.
버튼이 빨간색으로 렌더링된것을 확인할 수 있다.
{props.text}-{props.color} 이렇게 입력하면 버튼의 텍스트와 색상이 함께 렌더링되는 것을 확인할 수 있다.
카페와 블로그는 props로 전달을 안했기때문에 컬러값이 없어서 dash만 렌더링되었다.
props로 color라는 값이 무조건 들어올거라고 예상하고 위의 처럼 코드를 작성하면 치명적인 오류가 발생한다.
왜냐하면 카페와 블로그에는 props로 전달된 값이 없기때문이다.
그렇다면 어떻게 하면 좋을까?
구조분해할당 방법을 사용하면 된다.
컬러 props의 기본값이 잘 들어갔다.
즉, props의 기본 값을 설정하고 싶을때는
props를 구조분해할당 문법으로 받아오도록 변경한 다음 이때 구조분해 할당 문법에서 기본 값 문법을 잉요해 props의 기본 값 설정을 해주면 된다.
props를 전달하는 부모 컴포넌트 측에서
만약에 텍스트나 컬러 말고
function App() {
return (
<>
<div>
{/* 컬러 props를 추가하면 test: 메일, color:"red"로 큰솔에 출력된다. */}
<Button text={'메일'} color={'red'} a={1} b={1} c={3} />
<Button text={'카페'} />
<Button text={'블로그'} />
</div>
</>
);
}
export default App;
color={'red'} a={1} b={1} c={3} 이런식으로 다양한 값들이 props로 전달해야될 때는 어떻게 해야될까?
이 때는 하나의 객체로 묶어서 spread연산자를 통해 한방에 전달되도록 만든다.
특정 객체에 props로 전달해야된느 값들이 보관되어 있는 상황이면 객체의 값을 하나하나 적을 필요 없다!
spread연산자를 이용하자!
일반적인 자바스크립트 값 뿐만 아니라 html요소나 React 컴포넌트도 전달할 수 있다. 🤩
구조분해 할당 문법에서 children이라는 이름으로 div태그를 받아올 수 있게된다.
자식요소로 배치한 div태그가 children props로 제공된다. 렌더링된것도 확인 가능하다!
header 컴포넌트도 렌더링 가능!
이벤트 처리하기
Event Handling이란 무엇일까?
Event - 웹 내부에서 발생하는 사용자의 행동 (버튼 클릭, 메시지 입력, 스크롤 등등)
Handling - 다루다, 취급하다, 처리하다
=> 이벤트가 발생했을 때 그것을 처리하는 것 (웹에서 발생하는 사용자들의 행동을 처리하는 것) (버튼 클릭시 경고창 노출)
각각의 버튼을 누르면 큰솔창에 메일, 카페, 블로그가 출력된다.
첫번째 방법
onClick={() => {
console.log(text);
}}
위의 코드를 이벤트를 실질적으로 처리해서 이벤트 핸들러라고 부른다.
두번째 방법 - 변수로 지정해서 작성하기
const Button = ({ children, text, color = 'black' }) => {
const onClickButton = () => {
console.log(text);
};
return (
<button onClick={onClickButton} style={{ color: color }}>
{text} - {color}
{children}
</button>
);
};
export default Button;
밑의 코드와 같이 함수의 결과를 출력하면 x
<button onClick={onClickButton} style={{ color: color }}>
다른 이벤트들도 같게 설정해주면 된다.
이벤트 객체
이벤트 함수를 호출하면서 호출된 이벤트 핸들러 함수에 매개변수로 이벤트
const Button = ({ children, text, color = 'black' }) => {
// 이벤트 객체
const onClickButton = (e) => {
console.log(e);
console.log(text);
};
return (
<button
onClick={onClickButton}
// onMouseEnter={onClickButton}
style={{ color: color }}
>
{text} - {color}
{children}
</button>
);
};
export default Button;
// 이벤트 객체
const onClickButton = (e) => {
console.log(e);
console.log(text);
};
버튼을 누르면 SyntheitcBaseEvent 객체가 출력된다. 매개변수 e에 저장된 이벤트 객체이다.
SyntheitcBaseEvent => 합성 이벤트 객체 의미
합성 이벤트 객체란? 🤔
모든 웹 브라우저의 이벤트 객체를 하나로 통일한 형태를 의미
event 객체가 서로 다르다.
그래서!
Cross Browsing Issue(브라우저 별 스펙이 달라 생기는 문제)가 발생 => 리액트의 Synthetic Event가 해결
State로 상태관리하기
State - 상태
어떤 사물이 현재 가지고 있는 형태나 모양을 정의 변화할 수 있는 동적인 값
리액트의 컴포넌트들은 자신의 형태나 모양을 정의하는 state를 갖고 있다.
위에서 만든 컴포넌트는 아직 state를 갖고 있지 않았다 .
state - 컴포넌트의 현재 상태를 보관하는 변수
state를 갖는 컴포넌트들은 이 state의 값에 따라(현재의 상태에 따라)
각가 다른 UI를 화면에 렌더링한다.
컴포넌트가 다시 랜더링되는 상태를 리렌더링이라고 한다 .
리액트에서 각각의 컴포넌트에 이 컴포넌트 상태를 의미하는 값이자 변화할 수 있는 값인 State를 만들 수 있으며
하나의 컴포넌트에 여러개의 state를 만드는 것이 가능하다.
이런식으로 각각의 state를 만들어서 저장가능
함수 컴포넌트에서 state를 생성하려면
1. import
import { useState } from 'react';
function App() {
useSate는 2개의 요소가 들어있는 배열이 출력
1. undefined → 새롭게 생성된 state값, useSate의 인수로 초기 값을 숫자 0이라고 넣고 저장하면 큰솔에 새로운 배열 추가된다.
이제 0은 state의 값인것이다 . => state의 현재 값
2. 함수 → state의 값을 변경하는, 상태를 변화시키는 함수가 들어있다. => 상태 변화함수
반환 값을 변수에 저장보단 배열의 비구조화할당(=구조분해할당)을 통해 한다.
import './App.css';
import { useState } from 'react';
function App() {
const [state, setSate] = useState(0); // 초기값
console.log(state);
return <></>;
}
export default App;
버튼을 누르면 함수 컴포넌트를 리렌더링한다.
컴포넌트 역할을 하고 있는 앱 함수를 재호출해 새롭게 반환하고
새롭게 반환한 값을 화면에 다시 렌더링한다.
useState(0) -> 이게 초기값
React에서는 왜 state라는 값을 만들어야할까?
(개인적인 생각이지만 그건 메타 마음인듯.. 하지만 개발자의 숨은 의도를 알아보자☺️)
let light = "OFF"로 설정하면 컴포넌트가 렌더링되지 않는다.
왜냐하면리액트 컴포넌트는 일반적인 변수가 아니라 state의 값이 변할때마다 리렌더링되기 때문이다.
삭제 확인 알림창은 소중하다. 커밋하려다가 삭제할뻔했다.. 휴..🫨
State와 Props
import './App.css';
import { useState } from 'react';
const Bulb = ({ light }) => {
return (
<div>
{light === 'ON' ? (
<h1 style={{ backgroundColor: 'orange' }}>ON</h1>
) : (
<h1 style={{ backgroundColor: 'gray' }}>OFF</h1>
)}
</div>
);
};
function App() {
// state 생성
// 기본 구조: const [state, setState] = useState(0);
const [count, setCount] = useState(0);
// 2. lightState의 값을 on으로 바꾸면 전구가 렌더링된다.
const [light, setLight] = useState('OFF');
return (
<>
<div>
<Bulb light={light} />
{/* 1. 켜기 버튼을 눌러 */}
<button
onClick={() => {
setLight(light === 'ON' ? 'OFF' : 'ON');
}}
>
{light === 'ON' ? '끄기' : '켜기'}
</button>
</div>
<div>
<h1>{count}</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
+
</button>
</div>
</>
);
}
export default App;
끄기 켜기 버튼을 눌러 lightState를 계속 바꾸면 Bulb 컴포넌트(const Bulb)가 계속 리렌더링되는 것이다.
⭐ bulb같은 자식 컴포넌트는 부모로부터 받는 props의 값이 바뀌면 리렌더링이 발생한다.
📝 props는 (의 값은) const Bulb = ({여기 부분})
+1버튼을 눌렀는데 큰솔창에는 bulb 컴포넌트가 계속 리렌더링되는 것을 확인할 수 있다.
왜 그런것일까?
✅ 리액트 컴포넌트는 리렌더링이 발생하는 3가지 상황이 있다.
1. 자신이 관리하는 state의 값이 변경될 때
2. 자신이 제공받는 props의 값이 변경될 때
3. 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 리렌더링이 됨
자 다시 돌아와서 설명을 해보겠다.
+ 버튼을 누르면 app컴포넌트에서 count state선언한 부분의 useState 값이 계속 바뀐다.
이걸 볼때 1번과 3번이 적용된다.
하지만 bulb 컴포넌트는 count의 값과 아무런 상관이 없다.
이런 자식 컴포넌트들이 많아 지면 성능 저하 문제 발생!
🤔 이런 문제를 방지하려면 어떻게 해야할까?
관련 없는 두개의 state를 하나의 컴포넌트로 몰아넣기 보다는 서로 다른 컴포넌트로 분리하는 것이 좋다 .
import './App.css';
import { useState } from 'react';
const Bulb = () => {
const [light, setLight] = useState('OFF');
return (
<div>
{light === 'ON' ? (
<h1 style={{ backgroundColor: 'orange' }}>ON</h1>
) : (
<h1 style={{ backgroundColor: 'gray' }}>OFF</h1>
)}
<button
onClick={() => {
setLight(light === 'ON' ? 'OFF' : 'ON');
}}
>
{light === 'ON' ? '끄기' : '켜기'}
</button>
</div>
);
};
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<h1>{count}</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
+
</button>
</div>
);
};
function App() {
// state 생성
// 기본 구조: const [state, setState] = useState(0);
// const [count, setCount] = useState(0);
// 2. lightState의 값을 on으로 바꾸면 전구가 렌더링된다.
// const [light, setLight] = useState('OFF');
return (
<>
<Bulb />
{/* 1. 켜기 버튼을 눌러 */}
{/* <button
onClick={() => {
setLight(light === 'ON' ? 'OFF' : 'ON');
}}
>
{light === 'ON' ? '끄기' : '켜기'}
</button> */}
<Counter />
</>
);
}
export default App;
이런식으로 코드를 정리해주면 불필요한 요소들이 리렌더링되지 않는다.
각각의 컴포넌트를 src 컴포넌트 폴더 아래 저장해두면 훨씬 좋다. => 모듈화 작업
State로 사용자 입력 관리하기 1
회원가입 폼 렌더링해보기
import { useState } from 'react';
- useState는 React의 상태 관리를 위한 Hook인데,
- App.js 파일 안이나 Register 컴포넌트 안에서 사용되지 않았음.
- 사용하지 않는 import는 보통 경고가 뜨거나 불필요한 코드가 되니까 삭제해도 무방
그럼 언제 사용하냐?
seState는 "상태를 저장하고 바꾸고 싶을 때" 사용해!
예를 들어 버튼을 클릭하면 숫자가 올라가는 기능을 만들때
상황 | useState 써야 할까? |
버튼 누르면 값이 바뀌어야 해 | o |
입력창에 글 쓰면 실시간으로 상태 저장 | o |
어떤 요소를 숨기거나 보여주고 싶을 때 | o |
단순히 텍스트/컴포넌트 보여주기만 할 때 | x |
import { useState } from 'react';
// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
const Register = () => {
// 초- 초기 값 설정하고 싶으면 useState('여기에 적기');
const [name, setName] = useState('이름');
const [birth, setBirth] = useState('');
const [country, setCountry] = useState('');
const [bio, setBio] = useState('');
const onChangeName = (e) => {
// console.log(e);
// e.target.value; // value 프로퍼티에 접근하면 현재 사용자가 작성한 텍스트에 접근 가능
setName(e.target.value);
};
const onChangeBirth = (e) => {
setBirth(e.target.value);
};
const onChangeCountry = (e) => {
setCountry(e.target.value);
};
const onChangeBio = (e) => {
setBio(e.target.value);
};
return (
<div>
{/* div로 감싸면 한 줄씩 보여준다. */}
<div>
{/* 초 - input태그의 value 속성으로 이 state인 name 설정하기 */}
<input value={name} onChange={onChangeName} placeholder={'이름'} />
{/* 입력 값 보여줌 */}
{/* {name} */}
</div>
<div>
{/* date picker 렌더링 */}
<input value={birth} onChange={onChangeBirth} type="date" />
</div>
<div>
{/* select는 기본으로 맨 위에 있는 옵션이 됨, 만일 아무것도 안하고 싶으면 빈 옵션 넣기 */}
<select value={country} onChange={onChangeCountry}>
<option></option>
{/* value로 더 간결한 값을 사용하는 경우가 많다. */}
<option value="kr">한국</option>
<option>미국</option>
<option>일본</option>
</select>
</div>
<div>
<textarea value={bio} onChange={onChangeBio} />
</div>
</div>
);
};
export default Register;
onChange 속성말고도 value 속성도 함께 적용해주기
State로 사용자 입력 관리하기
✅ 4개의 state를 하나의 객체로 묶기
import { useState } from 'react';
// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
const Register = () => {
// 상태 초기화 -> 하나의 객체로 만듦
const [input, setInput] = useState({
name: '',
birth: '',
country: '',
bio: '',
});
// input의 값 어떻게 변경되는지 확인 가능
console.log(input);
const onChangeName = (e) => {
setInput({ ...input, name: e.target.value });
};
const onChangeBirth = (e) => {
setInput({ ...input, birth: e.target.value });
};
const onChangeCountry = (e) => {
setInput({ ...input, country: e.target.value });
};
const onChangeBio = (e) => {
setInput({ ...input, bio: e.target.value });
};
return (
<div>
{/* div로 감싸면 한 줄씩 보여준다. */}
<div>
<input
value={input.name}
onChange={onChangeName}
placeholder={'이름'}
/>
</div>
<div>
{/* date picker 렌더링 */}
<input value={input.birth} onChange={onChangeBirth} type="date" />
</div>
<div>
{/* select는 기본으로 맨 위에 있는 옵션이 됨, 만일 아무것도 안하고 싶으면 빈 옵션 넣기 */}
<select value={input.country} onChange={onChangeCountry}>
<option></option>
{/* value로 더 간결한 값을 사용하는 경우가 많다. */}
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="jp">일본</option>
</select>
</div>
<div>
<textarea value={input.bio} onChange={onChangeBio} />
</div>
</div>
);
};
export default Register;


여러개의 state로 나눠서 관리하든 4가지의 데이터를 객체 형태로 만들어 하나의 state로 만들었다.
✅이벤트 핸들러도 하나로 묶기
import { useState } from 'react';
// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
const Register = () => {
// 상태 초기화 -> 하나의 객체로 만듦
const [input, setInput] = useState({
name: '',
birth: '',
country: '',
bio: '',
});
// input의 값 어떻게 변경되는지 확인 가능
console.log(input);
const onChange = (e) => {
setInput({
...InputDeviceInfo,
[e.target.name]: e.target.value,
});
};
return (
<div>
{/* div로 감싸면 한 줄씩 보여준다. */}
<div>
<input
name="name"
value={input.name}
onChange={onChange}
placeholder={'이름'}
/>
</div>
<div>
<input
name="birth"
value={input.birth}
onChange={onChange}
type="date"
/>
</div>
<div>
{/* select는 기본으로 맨 위에 있는 옵션이 됨, 만일 아무것도 안하고 싶으면 빈 옵션 넣기 */}
<select name="country" value={input.country} onChange={onChange}>
<option></option>
{/* value로 더 간결한 값을 사용하는 경우가 많다. */}
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="jp">일본</option>
</select>
</div>
<div>
<textarea name="bio" value={input.bio} onChange={onChange} />
</div>
</div>
);
};
export default Register;
🔎 코드 동작 방법 살펴보기
1. input, select, textarea에 onChange를 모두 같은 함수로 설정했기 때문에 → 어디에 입력을 하든지 공통 onChange 함수가 실행
<input name="name" onChange={onChange} />
<input name="birth" onChange={onChange} />
<select name="country" onChange={onChange} />
<textarea name="bio" onChange={onChange} />
2. 그럼 onChange(e)가 실행될 때
const onChange = (e) => {
setInput({
...input,
[e.target.name]: e.target.value,
});
};
setInput()이 실행된다
그리고 이 안에 있는 객체는 스프레드 문법(...input)으로 기존 상태를 복사한 후
[e.target.name]이라는 프로퍼티 키에 e.target.value 값을 넣는다
3. [e.target.name] 부분 (핵심)
자바스크립트에서 객체의 키 자리에 [ ] 대괄호를 쓰면 그 안에 있는 값이 키가 된다
예) e.target.name이 "birth"라면 → 결국 birth: e.target.value가 된다!
4. 예시 흐름 하나 따라가 봄
<input name="birth" onChange={onChange} />
사용자가 생년월일을 입력했을 때 → 그 입력창은 name="birth"로 되어 있음
그래서 e.target.name은 "birth" → 입력한 값은 e.target.value, 예: "2000-01-01"
그러면 setInput은 이렇게 실행됨:
setInput({
...input,
birth: '2000-01-01'
});
6. 이렇게 하면 결과적으로
입력 필드의 name 속성에 따라 해당 필드 이름을 키로 쓰고 입력한 값을 그 키에 맞춰서 바꾸는 코드가 되는 것!
즉, 모든 필드에 대해서 onChange 하나로 처리 가능!
📝 setInput은 input 상태를 갱신하는 함수고, e.target.name을 객체의 키로 쓰고,
e.target.value를 그 키의 값으로 지정해서 전체 input 상태를 하나의 객체로 관리하는 패턴
이 패턴을 쓰면 여러 입력 필드를 하나의 핸들러로 통합해서 깔끔하게 처리할 수 있다.
useRef로 컴포넌트의 변수 생성하기
useRef - 새로운 Reference 객체를 생성하는 기능
const refObject = useRef()
refObject → 컴포넌트 내부의 변수(일반적인 값 저장 가능)
useRef | useState |
Reference 객체 생성 | State 생성 |
컴포넌트 내부의 변수 활용 가능 | 컴포넌트 내부의 변수로 활용 가능 |
어떤 경우에도 리렌더링 유발x | 값이 변경되면 컴포넌트 리렌더링 |
useRef를 이용하면
해당 요소에 Foucus를 주거나 요소의 스타일 변경 가능
1. 레퍼런스 객체의 활용 사례
import { useRef } from 'react';
리액트로 부터 이 useRef라는 함수를 불러온다.
const refObj = useRef();
console.log(refObj);
래퍼런스 객체란 이 Currnet라는 프로퍼티에 현재 보관할 값을 담아두기만 하는 단순한 자바스크립트 객체이다.
초기값 설정도 가능
const refObj = useRef(0);
console.log(refObj.current);
값을 사용하고 싶을 때 점 표기법으로 .current라고 적으면 된다 .
import { useState, useRef } from 'react';
// import { useRef } from 'react';
const Register = () => {
const [input, setInput] = useState({
name: '',
birth: '',
country: '',
bio: '',
});
const refObj = useRef(0);
console.log(refObj.current);
const onChange = (e) => {
setInput({
...Info,
[e.target.name]: e.target.value,
});
};
return (
<div>
<button
onClick={() => {
refObj.current++;
console.log(refObj.current);
}}
>
ref +1
</button>
<div>
<input
name="name"
value={input.name}
onChange={onChange}
placeholder={'이름'}
/>
</div>
<div>
<input
name="birth"
value={input.birth}
onChange={onChange}
type="date"
/>
</div>
<div>
<select name="country" value={input.country} onChange={onChange}>
<option></option>
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="jp">일본</option>
</select>
</div>
<div>
<textarea name="bio" value={input.bio} onChange={onChange} />
</div>
</div>
);
};
export default Register;
0은 rejObj 초기값, ref +1 버튼을 통해서 값이 출력되지만 리렌더링을 유발 x
refObj는 컴포넌트 내부에서 렌더링에 영향을 미치지 않을 때 사용될 수 있다.
수정횟수를 counting하는걸 만들어 보겠다
import { useState, useRef } from 'react';
// import { useRef } from 'react';
const Register = () => {
const [input, setInput] = useState({
name: '',
birth: '',
country: '',
bio: '',
});
// 출 - 수정을 할때마다 countRef랑 reference 객체의 값
const countRef = useRef(0);
const onChange = (e) => {
// 출 - 을 1씩 증가 시킴
countRef.current++;
console.log(countRef.current);
setInput({
...input, //
[e.target.name]: e.target.value,
});
};
return (
<div>
<button
onClick={() => {
countRef.current++;
console.log(countRef.current);
}}
>
ref +1
</button>
<div>
<input
name="name"
value={input.name}
onChange={onChange}
placeholder={'이름'}
/>
</div>
<div>
<input
name="birth"
value={input.birth}
onChange={onChange}
type="date"
/>
</div>
<div>
<select name="country" value={input.country} onChange={onChange}>
<option></option>
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="jp">일본</option>
</select>
</div>
<div>
<textarea name="bio" value={input.bio} onChange={onChange} />
</div>
</div>
);
};
export default Register;
2. 새로운 레퍼런스 객체를 생성해 레지스터 컴포넌트가 렌더링하고 있는 DOM 요소들을 직접 조작
- 회원가입 제출하는 버튼 생성
import { useState, useRef } from 'react';
// import { useRef } from 'react';
const Register = () => {
const [input, setInput] = useState({
name: '',
birth: '',
country: '',
bio: '',
});
// 출 - 수정을 할 때마다 countRef랑 reference 객체의 값
const countRef = useRef(0);
// input 레퍼런스에 접근하기 위해
const inputRef = useRef();
const onChange = (e) => {
// 출 - 을 1씩 증가시킴
countRef.current++;
console.log(countRef.current);
setInput({
...input, //
[e.target.name]: e.target.value,
});
};
const onSubmit = () => {
if (input.name === '') {
// 이름을 입력하는 DOM 요소에 포커스
console.log(inputRef.current);
inputRef.current.focus();
}
};
return (
<div>
<div>
{/* 레퍼런스 객체를 사용하여 input에 접근 */}
<input
ref={inputRef}
name="name"
value={input.name}
onChange={onChange}
placeholder="이름"
/>
</div>
<div>
<input
name="birth"
value={input.birth}
onChange={onChange}
type="date"
/>
</div>
<div>
<select name="country" value={input.country} onChange={onChange}>
<option value="">국가 선택</option>
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="jp">일본</option>
</select>
</div>
<div>
<textarea
name="bio"
value={input.bio}
onChange={onChange}
placeholder="자기소개를 입력하세요"
/>
</div>
<div>
<button onClick={onSubmit}>제출</button>
</div>
</div>
);
};
export default Register;
자바스크립트로 함 바꿔봄 -> 어떤 문제점이 생기냐?에대한 설명
import { useState, useRef } from 'react';
// import { useRef } from 'react';
const Register = () => {
const [input, setInput] = useState({
name: '',
birth: '',
country: '',
bio: '',
});
// 출 - 수정을 할 때마다 countRef랑 reference 객체의 값
const countRef = useRef(0);
// input 레퍼런스에 접근하기 위해
const inputRef = useRef();
let count = 0;
const onChange = (e) => {
// 출 - 을 1씩 증가시킴
// countRef.current++;
count++;
console.log(countRef);
setInput({
...input, //
[e.target.name]: e.target.value,
});
};
const onSubmit = () => {
if (input.name === '') {
// 이름을 입력하는 DOM 요소에 포커스
console.log(inputRef.current);
inputRef.current.focus();
}
};
return (
<div>
<div>
{/* 레퍼런스 객체를 사용하여 input에 접근 */}
<input
ref={inputRef}
name="name"
value={input.name}
onChange={onChange}
placeholder="이름"
/>
</div>
<div>
<input
name="birth"
value={input.birth}
onChange={onChange}
type="date"
/>
</div>
<div>
<select name="country" value={input.country} onChange={onChange}>
<option value="">국가 선택</option>
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="jp">일본</option>
</select>
</div>
<div>
<textarea
name="bio"
value={input.bio}
onChange={onChange}
placeholder="자기소개를 입력하세요"
/>
</div>
<div>
<button onClick={onSubmit}>제출</button>
</div>
</div>
);
};
export default Register;
자바스크립트 코드로 작성하면 onChangeEventHandler가 실행되면서 state의 값을 변경하낟. 그러면 어떻게 되느냐?
Register 컴포넌트가 리렌더링된다.
컴포넌트가 리렌더링 된다는 것은 레지스터 역할이 다시 호출이 되는 것이다. 그래서 함수 안에 있는것도 다시 호출되는 것이다.
🤔 만약에 let count = 0을 Register 밖 즉, 외부에 하면 어떻게 되냐? 라는 궁금증이 생긴다.
그러면 수정횟수가 잘 카운팅 되는 것을 확인할 수 있지만 좋은 방법은 아니다.
만일 두개의 Register컴포넌트가 있을 경우 하나의 변수를 공유하기 문제가 발생한다.
그래서 useRef를 이용하는 것이다!
React Hooks
클래스 컴포넌트 기능을 함수 컴포넌트에서도 이용할 수 있는것
2017년도 이전 React.js
Class 컴포넌트 | Function 컴포넌트 |
모든 기능을 이용할 수 있음 | UI 렌더링만 할 수 있음 |
문법이 복잡함 |
Class 컴포넌트의 기능을 낚아채서 가져와서 Function 컴포넌트에 적용
낚아채다! 어떻게? Hoooooooooooooooooooooooooooks 🦜🏴☠️
useState와 useRef 역시 React Hooks이었음
useState - State 기능 낚아챔
useRef - Reference 기능 낚아챔
React Hooks는 이름 앞에 use가 붙는다.
리액트 Hooks는 함수 컴포넌트 내부에서만 호출 가능하다.
React Hook을 이용해서 나만의 Hook 만들기 가능!
Hook 관련 팁
1. 함수 컴포넌트, 커스텀 훅 내부에서만 호출 가능
import { useState } from "react";
const HookExam = () => {
const state = useState();
return <div>hookexam</div>;
};
export default HookExam;
2. 조건부로 호출될 수 x
조건문이나 반복문 내부에서 hook이 호출 x
import { useState } from 'react';
const HookExam = () => {
if (true) {
const state = useState();
}
for(;;)
return <div>hookexam</div>;
};
export default HookExam;
내부에서 hook을 호출하면 서로 다른 hook들의 호출 순서가 엉망이 되어버린다. 내부적으로 오류가 발생한다.
3. 나만의 훅(Custom Hook) 직접 만들 수 있다.
커스텀 훅을 만드는 방법은 함수의 이름앞에 use를 작성하면 된다 .
import { useState } from 'react';
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;
근데 hook은 hook파일을 새로 만들어서 정리해준다.
hook 파일 만들고 userInput만들기
import { useState } from 'react';
function useInput() {
// 반복적으로 쓰는 녀석들 작성해주기
const [input, setInput] = useState('');
const onChange = (e) => {
setInput(e.target.value);
};
return [input, onChange];
}
export default useInput;
app.jsx
import './App.css';
import Register from './components/Register';
import HookExam from './components/HookExam';
function App() {
return (
<>
<HookExam />
</>
);
}
export default App;
'🔷 React' 카테고리의 다른 글
[React] 라이프사이클 (0) | 2025.04.08 |
---|---|
[React] 카운터앱, State Lifting (4) | 2025.04.07 |
[React] React 개론 (1) | 2025.04.01 |
[React] Node.js 기초 (0) | 2025.04.01 |