JSX로 UI 표현하기
jsx(JavaScript Extensions)란?
확장된 자바스크립트의 문법
jsx파일에서는 자바스크립트와 HTML을 혼용하여 사용가능하게 한다.
💡JSX 주의 사항
1. 중괄호 내부 자바스크립트 표현식만 넣을 수 있다.
삼항 연산자나 변수의 이름처럼 특정 값으로 평가될 수 있는 코드(if문, for문 안됨)
2. 숫자, 문자역, 배열 값만 렌더링된다.
(boolean, undefined, null은 오류는 발생하지 않지만, 화면에 렌더링도 안됨/객체는 화면에 바로 렌더링 되지 않고 값을 뺴와서 해야함)
3. 모든 태그는 닫혀있어야 한다.
4. 최상위 태그는 반드시 하나여야만 한다.
// JSX 주의 사항
// 1. 중괄호 내부 자바스크립트 표현식만 넣을 수 있다.
// 삼항 연산자나 변수의 이름처럼 특정 값으로 평가될 수 있는 코드(if문, for문 안됨)
// 2. 숫자, 문자역, 배열 값만 렌더링된다.(boolean, undefined, null은 오류는 발생하지 않지만, 화면에 렌더링도 안됨/객체는 화면에 바로 렌더링 되지 않고 값을 뺴와서 해야함)
// 3. 모든 태그는 닫혀있어야 한다.
// 4. 최상위 태그는 반드시 하나여야만 한다.
const Main = () => {
const number = 10;
const obj = { a: 1 };
return (
<main>
<h1>main</h1>
<h2>{number}</h2>
{[1, 2, 3]}
{/* boolean, undefined, null은 오류는 발생하지 않지만, 화면에 렌더링도 안됨 */}
{true}
{undefined}
{null}
{/* 객체는 화면에 바로 렌더링 되지 않고 값을 뺴와서 해야함 */}
{obj.a}
</main>
);
};
export default Main;
const Main = () => {
const user = {
name: "Ddd",
isLogin: true,
};
// 방법 1 - if문 사용
if (user.isLogin) {
return (
<div
// # css 사용 방법: 방법1 - className 사용
// HTML에서는 class라고 하지만.
// JSX에서는 자바스크립트와 HTML을 함께 쓰고 있기 때문에 자바크립트 예약어는 class를 쓸 수 없다.
className="logout"
// # css 사용 방법: 방법2 - style 사용
// style={{
// backgroundColor: "red",
// }}
>
로그아웃
</div>
);
} else {
return <div>로그인</div>;
}
// 방법 2 - 삼항연산자 사용
// return <>{user.isLogin ? <div>로그아웃</div> : <div>로그인</div>}</>;
};
export default Main;
5.4) Props로 데이터 전달하기
💡 부모 컴포넌트에서 자식 컴포넌트로만 props값 전달이 가능하다.
App.jsx
import { useState } from "react";
import "./App.css";
import Header from "./components/Header"; // vite로 만든 리액트 앱에서는 확장자까지는 안써도 자동으로 파일을 찾아가도록 내부적으로 자동 설정되어 있다.
import Main from "./components/Main";
import Footer from "./components/Footer";
import Button from "./components/Button";
function App() {
const buttonProps = {
text: "메일",
color: "red",
a: 1,
b: 2,
c: 3,
};
return (
<>
<Button {...buttonProps}></Button>
<Button text={"카페"}></Button>
<Button text={"블로그"}>
<div>자식요소</div>
</Button>
</>
);
}
export default App;
Button.jsx
// 전표 기법
// const Button = (props) => {
// 구조 분해할당 기법
const Button = ({ text, color, children }) => {
return (
<button style={{ color: color }}>
{/* props.color에 아무런 값이 전달되지 않았을 때도, toUpperCase를 호출하면 오류가 발생 -> Button.defaultProps으로 기본값을 지정하면 오류 안남 */}
{text} - {color.toUpperCase()}
{children}
</button>
);
};
Button.defaultProps = {
color: "black",
};
export default Button;
5.5) 이벤트 처리하기
Button.jsx
const Button = ({ text, color, children }) => {
// 이벤트 객체
const onClickButton = (e) => {
console.log(e);
console.log(text);
};
return (
<button style={{ color: color }} onClick={onClickButton}>
{text} - {color.toUpperCase()}
{children}
</button>
);
};
Button.defaultProps = {
color: "black",
};
export default Button;
App.jsx
import { useState } from "react";
import "./App.css";
import Header from "./components/Header"; // vite로 만든 리액트 앱에서는 확장자까지는 안써도 자동으로 파일을 찾아가도록 내부적으로 자동 설정되어 있다.
import Main from "./components/Main";
import Footer from "./components/Footer";
import Button from "./components/Button";
function App() {
const buttonProps = {
text: "메일",
color: "red",
a: 1,
b: 2,
c: 3,
};
return (
<>
<Button {...buttonProps}></Button>
<Button text={"카페"}></Button>
<Button text={"블로그"}>
<div>자식요소</div>
</Button>
</>
);
}
export default App;
// 이벤트 객체
const onClickButton = (e) => {
console.log(e);
console.log(text);
};
e 매개변수 출력(console.lot(e)) → SyntheticbaseEvent 가 출력된다.
SyntheticbaseEvent(합성이벤트 객체)라는 객체이다.
합성 이벤트? 모든 웹 브라우저의 이벤트 객체를 하나로 통일한 형태
이벤트 객체가 브라우저마다 조금씩 다르다.
크롬에서는 이벤트 객체에 이벤트가 현재 발생한 요소를 가져오는 target이라는 속성이 있는데,
사파리에서는 이것을 이벤트 타겟이라는 의미로, ETarget이라고 부를 수도 있다.(실제 X)
브라우저마다 규격도 다르고 동작 방식도 달라서 생긴 문제를 Cross Browsing Issue라고 한다.
그리하여 SyntheticbaseEvent(합성이벤트 객체)를 통해 여러 브라우저들의 규격을 참고해서 하나의 통일된 규격으로 이벤트 객체를 포맷팅한다.
일종의 통합 규격을 만들어 놓은 것이다.
5.6) State로 상태관리하기
💡State
: 현재 가지고 있는 형태나 모양을 정의, 변화할 수 있는 동적인 값
- State값에 따라 렌더링 되는 UI가 결정된다.
- 여러 state를 만드는 것도 가능하다.
useState함수는 인수로는 state의 설정한 초기값을 받아서 두 개의 요소를 담은 배열을 반환한다.
첫 번째 요소는, state의 현재 값이고
두 번째 요소는, state를 변경시키는 상태 변화 함수이다.
(const [count, setCount] = useState(0); 여기에서 conut가 첫 번째 요소이고, setCount가 두번째 요소에 해당된다.)
(useState()에 아무값을 안넣으면 출력값에서 첫 번째 요소 값이 undefined로 나온다. - 아래 사진에서 확인)
쉽게 말하면,
컴포넌트의 state값이 바뀌면 컴포넌트가 return을 다시한다. 그래서 화면을 다시 그린다.
그리고 그때 변경된 state의 값도 함께 화면에 반영된다.
🤔왜let = count;
로 하는게 아닌,const [count, setCount] = useState(0);
이런식으로 해야하는 걸까?
이유는 let으로 선언해봐야 리렌더링이 안되기 때문이다.
useState로 관리해야 값이 변경되면 상태를 파악하고 화면을 리렌더링한다.
App.jsx
import { useState } from "react";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
const [light, setLight] = useState("OFF");
return (
<>
<div>
<h1>{light}</h1>
<button
onClick={() => {
setLight(light === "ON" ? "OFF" : "ON");
}}
>
{light === "ON" ? "끄기" : "켜기"}
</button>
</div>
<h1>{count}</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
+
</button>
</>
);
}
export default App;
5.7) State와 Props
- bulb와 같은 자식 컴포넌트는 부모로부터 받는 props의 값이 바뀌면 리렌더링이 발생한다라는 사실을 알 수 있다.
# 리액트 컴포넌트는 리렌더링이 발생하는 3가지 상황
1. 자신이 관리하는 state의 값이 변경될 때
2. 제공받는 props의 값이 변경될 때
3. 부모 컴포넌트가 리렌더링될 때(부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 리렌더링된다.)
아래 코드에서 count를 세는 +버튼을 눌러도, 아무런 상관이 없는 light 버튼 켜기/끄기 버튼도 렌더링이 되고 있다.(3번 부모 컴포넌트가 리렌더링될 때 리렌더링이 된다.)
Bulb 컴포넌트는 App컴포넌트의 자식 컴포넌트이기 때문에, App 컴포넌트가 리렌더링되면 Bulb 컴포넌트도 리렌더링된다.
3번 처럼 불필요한 자식컴포넌트의 리렌더링을 막기 위해 목적에 맞게 함수로 따로 빼어 만들거나, 파일을 따로 만들어 사용한다.
App.jsx
const Bulb = ({ light }) => {
return (
<div>
{light === "ON" ? (
<h1 style={{ backgroundColor: "orange" }}>ON</h1>
) : (
<h1 style={{ backgroundColor: "gray" }}>OFF</h1>
)}
</div>
);
};
function App() {
const [count, setCount] = useState(0);
const [light, setLight] = useState("OFF");
return (
<>
<div>
{/* 버튼 켜기/끄기 */}
<Bulb light={light}>{light}</Bulb>
<button
onClick={() => {
setLight(light === "ON" ? "OFF" : "ON");
}}
>
{light === "ON" ? "끄기" : "켜기"}
</button>
</div>
{/* count 세기 */}
<h1>{count}</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
+
</button>
</>
);
}
export default App;
5.8) State로 사용자 입력 관리하기1
Register.jsx
import { useState } from "react";
// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
const Register = () => {
const [name, setName] = useState("이름");
const [birth, setBirth] = useState("");
const [country, setCountry] = useState("");
const [bio, setBio] = useState("");
const onChangeName = (e) => {
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>
<input
value={name}
onChange={onChangeName}
placeholder={"이름"}
/>
</div>
<div>
<input
value={birth}
onChange={onChangeBirth}
type="date"
/>
</div>
<div>
<select value={country} onChange={onChangeCountry}>
<option value=""></option>
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="uk">영국</option>
</select>
{country}
</div>
<div>
<textarea value={bio} onChange={onChangeBio} />
</div>
</div>
);
};
export default Register;
5.9) State로 사용자 입력 관리하기
import { useState } from "react";
// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
const Register = () => {
// const [name, setName] = useState("이름");
// const [birth, setBirth] = useState("");
// const [country, setCountry] = useState("");
// const [bio, setBio] = useState("");
const [input, setInput] = useState({
name: "",
birth: "",
country: "",
bio: "",
});
// console.log(input);
// onChange함수 중복 줄이기
// 통합 핸들러 만들기
const onChange = (e) => {
console.log(e.target.name, e.target.value);
setInput({
...input,
[e.target.name]: e.target.value,
});
};
// const onChangeName = (e) => {
// setInput(
// // 스프레드 연산자
// {
// ...input, // name말고 다른 값은 그대로 가져와서 set하겠다.
// name: e.target.value,
// }
// );
// };
// const onChangeBirth = (e) => {
// setInput({ ...input, birth: e.target.value });
// };
// const onChangeCountry = (e) => {
// setInput({ country: e.target.value });
// };
// const onChangeBio = (e) => {
// setInput({ ...input, bio: e.target.value });
// };
return (
<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 name="country" value={input.country} onChange={onChange}>
<option value=""></option>
{/* ## value값으로 저정함.. */}
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="uk">영국</option>
</select>
</div>
<div>
<textarea name="bio" value={input.bio} onChange={onChange} />
</div>
</div>
);
};
export default Register;
5.10) useRef로 컴포넌트의 변수 생성하기
💡useRef
새로운 Reference 객체를 생성하는 기능
5.11) React Hooks
💡React Hooks
클래스 컴포넌트의 기능을 함수 컴포넌트에서도 이용할 수 있도록 도와주는 메서드
Hooks: 낚아채다
함수 컴포넌트에서도 클래스 컴포넌트의 기능을 마치 낚아채듯이 가져와서 사용할 수 있게 해주는 React Hooks기능을 개발.
💡3가지 hook 관련된 팁
1. 함수 컴포넌트, 커스텀 훅 내부에서만 호출 가능
const state = useState(); // 여긴 안됨..
2. 조건부로 호출될 수 없다.
-> 왜? 조건문이나 반복문 내부에서 호출하게 되면 서로 다른 hook들의 호출 순서가 엉망이 되어 버리는 현상 발생해서 내부적인 오류가 발생할 수 있다.
3. 나만의 훅(Custom Hook)을 직접 만들 수 있다.
메서드명 앞에 'use'를 붙여주면 리액트는 커스텀 훅이라고 판단하게 되어, 다른 리액트 훅을 내부에서 호출한다고 하더라고 오류가 발생하지 않는다.
4. 커스텀 훅은 보통 컴포넌트랑 같은 파일에 두지 않고, src 디렉토리 아래에 별도의 폴더 hooks를 만들어서 hook의 이름으로 파일을 만들어 보관하는게 일반적이다.
HookExam.jsx
import { useState } from "react";
// # 3가지 hook 관련된 팁
// 1. 함수 컴포넌트, 커스텀 훅 내부에서만 호출 가능
// const state = useState(); // 여긴 안됨..
// 2. 조건부로 호출될 수 없다.
// 3. 나만의 훅(Custom Hook)을 직접 만들 수 있다.
// 메서드명 앞에 'use'를 붙여주면 리액트는 커스텀 훅이라고 판단하게 되어, 다른 리액트 훅을 내부에서 호출한다고 하더라고 오류가 발생하지 않는다.
function useInput() {
const [input, setInput] = useState("");
const onChange = (e) => {
setInput(e.target.value);
};
return [input, onChange];
}
const HookExam = () => {
const state = useState(); // 클래스 컴포넌트는 함수 컴포넌트, 커스텀 훅 내부에서만 호출 가능 O
// 2. 조건부로 호출될 수 없다.
// -> 왜? 조건문이나 반복문 내부에서 호출하게 되면 서로 다른 hook들의 호출 순서가 엉망이 되어 버리는 현상 발생해서 내부적인 오류가 발생할 수 있다.
// if (true) {
// const state = useState();
// }
// 3.
const [input, onChange] = useInput();
const [input2, onChange2] = useInput();
return (
<div>
<input value={input} onChange={onChange}></input>
<input value={input2} onChange={onChange2}></input>
</div>
);
};
export default HookExam;
'공부 > frontend' 카테고리의 다른 글
라이프 사이클(useEffect) (1) | 2025.01.31 |
---|---|
React 프로젝트 1.카운터 앱 (0) | 2025.01.30 |
React.js 개론 (1) | 2025.01.30 |
Node.js 기초 (0) | 2025.01.29 |
JavaScript 심화 (0) | 2025.01.29 |