🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [29]일차
🚀29일차에는 상태관리를 할 수 있는 useState와 DOM객체를 참조할 수 있는 useRef에 대해서 배웠다.
또한 부모-자식으로 묶여있는 컴포넌트들을 한번에 가져와 화면에 반영해볼 수 있었다. 범위는 적었지만 그 안에서 새롭게 배운것이 많아서 이번 회고를 통해 정리를 잘 해놔야겠다고 생각했다.
여러 컴포넌트 전달
▶️실습 - XML 요소(Element)안의 내용들을 props로 전달
- XML요소 <div>Hello</div>
- props.children : props가 가지고 있는 내용을 전달
Parent.js
function Parent(props){
return(
<div>
{props.children}
</div>
)
}
export default Parent;
App.js
import Parent from "./Parent";
// ...
<Parent>
<div>Children Test</div>
</Parent>
- div태그안에 있던 "Children Test" (텍스트 내용)이 출력
▶️실습 - 여러 Children컴포넌트를 받은 Parent 컴포넌트
Parent.js
const Parent = (props) => {
const style = {
border: "4px solid green",
padding: "16px",
};
return <div style={style}>{props.children}</div>;
};
export default Parent;
- props.children 으로 인해 First와 Second컴포넌트들이 Parent 컴포넌트에 포함될 것이다.
- style은 const로 정의하였으므로 자바스크립트 코드이므로
➡️JSX문법에서 style을 적용할때는 = {style} 처럼 중괄호 안에 적어주어 명시
First.js : First컴포넌트는 아무 props없이 컴포넌트 생성
Second.js : Second컴포넌트는 props를 받아서 컴포넌트 생성
ChildrenExam.js
import First from "./First";
import Parent from "./Parent";
import Second from "./Second";
const ChildrenExam = () => {
return (
<Parent>
<First></First>
<Second name="Walcott"></Second>
</Parent>
);
};
export default ChildrenExam;
- 부모 컴포넌트와 자식컴포넌트들을 포함하는 ChildrenExam 컴포넌트를 만들고
- return 안으로 <Parent> <First></First> <Second name=”Walcott”></Second></Parent> 작성
- Parent 컴포넌트 안에서 First에는 아무 속성을 넘기지 않고 Second에는 name속성을 넘겨 각 컴포넌트를 만든다.
⭐즉 부모컴포넌트만 부를때는 props를 쓰겠지만 그 부모컴포넌트가 포함한 자식 컴포넌트들도 부르고자할때는 props.children으로 불러주어야한다.
App.js
import ChildrenExam from "./propsChildren/ChildrenExam";
return (
<div className="card">
<ChildrenExam />
</div>
);
- App에서 ChildrenExam을 import시킨 후 <ChildrenExam/> 컴포넌트만 만들어주어도
- Parent, First, Second의 계층관계로써 컴포넌트들을 출력
조건부 렌더링
- 삼항연산자 혹은 단축평가를 이용하여 조건부 렌더링을 수행 가능
조건부렌더링 (단축평가)
isSpecial && <b>**</b>
- isSpecial이 true이면 뒤 문장을 실행하여 " ** " 를 출력
조건부렌더링 (삼항연산자)
Welcome.js
const Welcome = (props) => {
return props.flag ? <h1>안녕하세요</h1> : null;
};
export default Welcome;
- 삼항연산자를 활용하여 받아들여온 속성(props)의 flag(=props.flag)가
➡️true이면 <h1>안녕하세요</h1>
➡️false이면 null로 컴포넌트를 만들어냄 - 삼항연산자
➡️ 조건식 ? 참일때 실행할 문장 : 거짓일때 실행할 문장
➡️ props.flag가 참이면 <h1>컴포넌트를 반환하고, 거짓이면 null을 반환할 것
즉 거짓일 경우에 null을 두어 문장을 실행하지 않도록 활용할 수 있다.
App.js : <Welcome flag={true} /> 로 flag속성 전달
- props로 true가 전달되어 <h1>안녕하세요</h1> 컴포넌트가 만들어져 전달되었음을 알 수 있다.
useState와 <input>
▶️실습 - input 박스 입력값에 따라 상태값 변경
import { useState } from "react";
const MyInputBox = () => {
const resetHandler = () => {
setText("");
};
const changeHandler = (e) => {
setText(e.target.value);
};
const [text, setText] = useState("기본값");
return (
<div>
<label>출력 : {text}</label>
<br />
<br />
<input type="text" value={text} onChange={changeHandler} />
<button onClick={resetHandler}>RESET</button>
</div>
);
};
export default MyInputBox;
- useState(”기본값”)을 이용하여 text를 바꿔주고 그렇게하기위한 함수는 setText()
- <input onChange={ } /> 속성
➡️input박스에 변화가 일어날때 수행할 이벤트를 지정 - changeHandler()
➡️ target을 통해 이벤트가발생했을때의 컴포넌트를 찾아내어서 그 컴포넌트의 value값으로 setText() 수행 ➡️onChange는 클릭했을때가 아닌 input박스에 입력을 할때부터 변화의 시점을 가져가므로
(input박스 안의 텍스트)와 (<label>출력 : {text})의 텍스트가 동시에 값이 바뀌게됨 - resetHandler()
➡️ input박스안에 있는 값을 비워주기 위한 기능 구현 필요
➡️ setText(””)로 아무것도 없는 값으로 갱신하여 리셋 구현
▶️실습 - 버튼 클릭에 따라 상태값 변경
- 13시부터는 다시 1시로 카운트되도록 구현
import { useState } from "react";
const UseStateExam01 = () => {
const [hour, setHour] = useState(0);
const timeHandler = () => {
hour < 12 ? setHour(hour + 1) : setHour(1);
};
return (
<div>
<span>시간 : {hour} 시</span>
<button onClick={timeHandler}>UPDATE</button>
</div>
);
};
export default UseStateExam01;
여러 useState() 사용
▶️실습 - 여러개의 useState 사용 방법 1(서점 프로그램)
- input이 5개 이상처럼 여러 개이면 직접 useState를 사용하는 것이 비효율적일 것이다.
- 이때 객체는 (키, 값)로 값을 저장할 수 있으므로 (제목, 가격) 처럼 저장하는 객체방식을 사용하면 좋을 것
import { useState } from "react";
const BookStore = () => {
// useState의 초기값을 "객체"로 설정
const [inputs, setInputs] = useState({
title: "",
price: "",
});
// inputs의 값을 비구조화 할당
const { title, price } = inputs;
const changeHandler = (e) => {
const { name, value } = e.target;
setInputs({
...inputs,
[name]: value,
});
};
const inputResetHandler = (e) => {
setInputs({
title: "",
price: "",
});
};
// ...return 부분...
};
export default BookStore;
- const [inputs, setInputs] = useState({ title: "", price: ""});
➡️useState의 초기값을 "객체"로 설정
➡️useState로 객체를 만든 후 구조분해할당으로 inputs에 넣어주고 있음을 알 수 있다. - const { name, value } = e.target;
➡️input 박스에 입력이 발생(onChange)했을때 e.target으로 값이 들어오는데
그 중 name 속성의 속성값을 가져오고 (title or price), value속성의 속성값을 가져온다(”책제목” or “3000원 등”)
즉 "구조 분해 할당" 하고 있는 것 - setInputs({ ...inputs, [name]: value });
➡️원래 객체 …inputs (스프레드 연산자가 key, value를 출력해준다),
객체나 배열이 들어가는 것이 아니라 그 요소들이 복사되어 들어가게 되는 것이다.
➡️객체는 키 값이 중복되지 않으므로(=키값은 오로지 한개)
똑같은 키 값이 추가되면 새로운 키 값으로 대체되는 특징을 이용할 수 있다. - [name] : value
➡️문법으로써 name이라는 key와 value의 값을 통해 기존의 값에서 새롭게 교체시켜줌
만약 name : value이면 name이라는 텍스트가 키 값으로 들어가게될 것이다.
하지만 [name] : value이면 name이라는 “키”값으로 value가 들어가게 될 것이다.
키 값을 통해서 값(value)을 넣어주는 것이기때문에 [value]와 같은 코드는 올바르지 않다.
(키:값의 구조가 아닌 값:값 구조처럼 판단되는 것이기때문이다)
// return 부분
<div>
<label>title : {title}</label>
<br />
<label>price : {price}</label>
<br />
제목 :
<input
type="text"
placeholder="제목을 입력하세요."
name="title"
value={title}
onChange={changeHandler}
/>
<br />
가격 :
<input
type="text"
placeholder="가격을 입력하세요."
name="price"
value={price}
onChange={changeHandler}
/>
<button onClick={inputResetHandler}>RESET</button>
</div>
- placeholder 속성 :
➡️안내문구를 의미
➡️value를 각각 {title}, {price}로 하여 RESET버튼이 눌리면 value가 “” 로 초기화되고
다시 placeholder가 표시되는 것을 확인할 수 있다. - 장점 : 기존 코드에서 각각의 핸들러를 관리하고 각각의 useState()를 사용했다면 이 코드에서는 객체방식 useState를 사용하여 더 효과적으로 상태를 관리하고 있음을 알 수 있다.
▶️실습 - 여러개의 useState 사용 방법 2(map 사용)
const UseStateExam02 = () => {
const [input, setInput] = useState("");
const [names, setNames] = useState(["Barens", "Walcott", "Lennon"]);
return (
<div>
<input type="text" value={input} onChange={inputChangeHandler} />
<button onClick={uploadHandler}>입력</button>
{/*입력받은 값들을 리스트로 출력*/}
</div>
);
};
export default UseStateExam02;
- 초기값을 (””)로 주는 useState와 배열로 선언과 초기화를 하는 useState
map() 사용 전
{/*입력받은 값들을 리스트로 출력*/}
<p>{names[0]}</p>
<p>{names[1]}</p>
<p>{names[2]}</p>
- names[index] 대신 map함수를 이용하여 더 간단히 출력이 가능
map() 사용 후
{/*입력받은 값들을 리스트로 출력*/}
{names.map((name) => {
return <p>{name}</p>;
})}
- map은 새로운 배열을 리턴해주므로 이 둘의 결과가 동일하게 나오고 있음
- 장점 : map함수 사용으로 많은 데이터에도 index를 직접지정하여 출력하지않고 사용할 수 있다.
uploadHandler()
const uploadHandler = () => {
setNames();
console.log(names);
console.log(...names);
setNames([input, ...names]);
};
❓console.log(names); 와 console.log(…names)의 차이점
➡️배열자체가 출력되느냐, 아니면 배열의 값이 출력되느냐의 차이
setNames([input, …names])
➡️새로운 값 (input)이 들어오는데 기존에 있던 배열의 값들을 복사해 넣어 setNames로 배열을 갱신한다.
setNames([input, names])
➡️배열의 값들이 들어오지 않고 배열자체가 들어오게되어 원치않는 값이 출력된다.
➡️이경우 리액트가 배열이 바뀌었다는 것을 “인지하지 못함”
❓스프레드 문법 (=스프레드 연산자)
➡️...배열
이러한 문법은 "스프레드 문법(Spread Syntax)"
배열이나 객체의 요소를 개별적으로 풀어서 사용할 때 활용
...(스프레드 연산자)는 ES6에서 도입
새로운 배열에 기존 배열의 요소를 복사한 후 새로운 값들을 추가하는 목적으로 사용
기존 객체의 내용을 펼쳐서 복사해주는 역할로 “불변성”을 지키기위해 복사하는 것
불변성을 지켜야 리액트 컴포넌트에서 상태가 업데이트 됐음을 감지하여 리렌더링이 진행
즉 리액트의 컴포넌트 업데이트 성능 최적화를 제대로 할 수 있는 것
const [상태값, 함수] = useState(초기값);
- 리액트에서는 객체를 업데이트할때 기존 객체를 “직접 수정하지 않고” 새로운 객체를 만들어서
“새로운 객체에 변화를 주어야한다” - 함수 부분에 새로운 객체를 넣어주어야 “리렌더링”이 된다.
- 같은 참조를 전달하면 바뀌었다는 것을 감지하지 못함
setInputs({ ...inputs, [name]: value, });
➡️...inputs
바뀔 객체를 의미하며 기존 배열 inputs 을 넣어서 기존 배열 inputs 값이 바뀌면
기존의 값들은 사라지므로 ...inputs(스프레드연산자)를 통해 기존 배열의 요소값들을 “복사하여” 넣어주는 표현
➡️[name]: value
새롭게 넣을 객체를 의미하며 name은 키 값, value는 밸류 값을 의미하며
[name]을 통해 해당 키 값을 찾아 밸류 값을 넣어주어 업데이트
useRef
- useRef로 특정 DOM선택가능
- 마치 querySelector()와 getElementById()를 통해 DOM객체를 선택했던 방식과 유사
- ref를 사용할때 useRef Hook 함수를 사용
- useRef()를 사용해 Ref객체를 만들고 선택하고픈 DOM에 ref값으로 설정
➡️Ref객체의 .current 값은 우리가 원하는 DOM을 가리키게된다. - 만약 input객체이면 .current.focus() 사용 또한 가능
- .current
➡️DOM객체가 가지고 있는 값을 가져올 수 있음
❓useRef의 용도 2가지
- 리액트에서 DOM객체를 직접참조하여 가져오는 기능
- 값이 DOM을 갖고있는것이아니라 값을 가지고 있어서 컴포넌트가 리렌더링되어도 값은 유지하면서 매번 렌더링을 시킬 필요는 없는 경우
▶️실습 - useRef 사용 전
- 자바스크립트에서 특정 input창에 포커스
<input id='title' type='text/>
<button onClick='document.getElementById('title').focus()'>Click</button>
- id="title"인 id의 input창에 커서 위치
- 리액트에서도 DOM을 직접 선택해야할때 ref를 사용
▶️실습 - useRef 사용 후
const textInput = useRef();
return (
<div>
<label>출력 : {text}</label>
<br />
<br />
<input type="text" value={text} onChange={changeHandler} ref={text}/>
<button onClick={resetHandler}>RESET</button>
</div>
);
};
- useRef()로 정의하고, 찾고자하는 DOM에서 ref속성을 지정해주면 되는 것
- textInput에는 useRef()를 통해 바로 저 input박스 객체를 받아와 담기게된다.
➡️useRef()를 console.log로 출력해보면 Object object 타입이 담김
▶️실습 - useRef의 focus() 사용
const resetHandler = () => {
setText("");
textInput.current.focus();
};
- .current.focus()
➡️useRef()로 얻어온 객체에게 “커서를 위치하도록” 할 수 있다.
배열 렌더링
const boardList = [
// 데이터들...
];
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
</tr>
<tr>
<td>{boardList[0].id}</td>
<td>{boardList[0].title}</td>
<td>{boardList[0].writer}</td>
<td>{boardList[0].created}</td>
<td>{boardList[0].readCount}</td>
</tr>
//...추가 데이터 테이블...
- <목차> 테이블 부분 밑으로 실제 데이터들이 담길 테이블을 만드는데 index로 접근해서 각각 만들어내고 있다.
1.map() 활용으로 개선 (BoardList 컴포넌트)
- 각각 인덱스를 통해 배열의 테이블을 생성하지 않고 map() 활용
<table border="1">
<caption>게시판 목록</caption>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
</tr>
{
boardList.map( (borad) => (
<tr>
<td>{board.id}</td>
<td>{board.title}</td>
<td>{board.writer}</td>
<td>{board.created}</td>
<td>{board.readCount}</td>
</tr>
))
}
</table>
2. Board 컴포넌트 만들기
- <td>부분을 별도의 컴포넌트로 분리
const Board = (board) => {
return (
<tr>
<td>{board.id}</td>
<td>{board.title}</td>
<td>{board.writer}</td>
<td>{board.created}</td>
<td>{board.readCount}</td>
</tr>
);
};
export default Board;
3. 기타 테이블 오류 개선 (BoardList 컴포넌트)
<tbody>
{boardList.map((board) => (
<Board
key={board.id}
id={board.id}
title={board.title}
writer={board.writer}
created={board.created}
readCount={board.readCount}
/>
))}
</tbody>
- Board 컴포넌트를 활용하여 그 안에 속성(props)를 Board를 넘긴다.
- key={board.id}
➡️ 변하지 않는 키와 같은 값이 필요한데 이 테이블에서는 (번호)가 “키”가 될 수 있을 것이다.
➡️ 테이블 오류 해결을 위해 key를 설정해줄 수 있다. - <thead>와 <tbody> 구역설정
➡️테이블의 헤드부분과 바디부분을 명시함으로써 테이블 오류를 해결할 수 있다.
4. 게시판 목록의 번호순서대로 오름차순 정렬
{
boardList.sort((o1, o2) => o1.id - o2.id);
}
- boardList.js 의 return부 위에 자바스크립트로 감싸준 후 sort()함수 이용
❓자바스크립트의 sort()함수 원리
➡️양수: o2가 o1보다 앞에 위치 (o2 - o1) = 내림차순 정렬 (작은 값이 뒤에 위치)
➡️음수: o1이 o2보다 앞에 위치 (o1 - o2) = 오름차순 정렬 (작은 값이 앞에 위치)
➡️0: o1와 o2의 순서를 유지
정렬 기준을 생략하면 기본적으로 문자열 기준으로 정렬
따라서 숫자를 기준으로 배열을 정렬한다고 하면 자바에서 equals()메소드를 오버라이딩했듯 비교 함수를 제공해야함
정리하자면 a, b순서일때
➡️return a - b → 오름차순.
➡️return b - a → 내림차순.
▶️실습 - useState() vs useRef() vs let 의 차이점
import { useRef, useState } from "react";
const UseRefExam2 = () => {
console.log("컴포넌트 랜더링!!");
const [countState, setCountState] = useState(0);
const countRef = useRef(0);
let countLet = 0;
const increaseState = () => {
setCountState(countState + 1);
console.log("useState ::::::::::::: " + (countState + 1));
};
const increaseRef = () => {
countRef.current++;
console.log("useRef::::::::::::::::::::::" + countRef.current);
};
const increaseLet = () => {
countLet++;
console.log("Let ::::::::::::::::: " + countLet);
};
return (
<div>
<p>useState : {countState}</p>
<p>useRef : {countRef.current}</p>
<p>Let : {countLet}</p>
<button onClick={increaseState}>useState update</button>
<button onClick={increaseRef}>useRef update</button>
<button onClick={increaseLet}>Let update</button>
</div>
);
};
export default UseRefExam2;
- useState()는 컴포넌트를 다시 렌더링시키는 (리렌더링)작업을 수행 가능
- 컴포넌트가 리렌더링할때 countState와 countRef의 값은 변하지 않지만 countLet같은 값 다시 초기화
➡️countState는 실시간으로 값을 업데이트 시킬때마다 값도 유지되고 값도 출력
➡️countRef는 내부적으로 값이 업데이트 되고 있다가 useState()를 통해 리렌더링이 되어야만
화면에업데이트된 값이 반영 - 과정 :
1. useState update 버튼 클릭
2. countState값 증가
3. 컴포넌트 “리렌더링” 수행
4. useRef update 버튼 클릭
5. useRef값 업데이트
6. Let update 버튼 클릭
7. let값 업데이트
8. useState update 버튼 클릭
9. countState값 증가
10. 컴포넌트 "리렌더링" 수행
11. useRef값 화면에 반영
12. let 초기화
🚀 useState의 활용과 useRef를 통한 객체참조를 정리할 수 있었다.
각각의 차이점을 회고를 통해 다시 공부할 수 있었고 스프레드 문법(...배열)에 대해서도 새롭게 알게되었다.
리액트가 직관적인면이 있어서 재밌는 것 같다가도 처음보는 문법을 보면 멈칫하기도한다.
그만큼 더 노력해야할 것 같다.
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_30일차_"리액트 Express" (0) | 2025.01.14 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_30일차_"리액트 Todo" (1) | 2025.01.14 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_28일차_"리액트 React" (1) | 2025.01.10 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_27일차_"자바스크립트 이벤트" (0) | 2025.01.09 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_26일차_"자바스크립트 비동기처리" (0) | 2025.01.08 |