자꾸 콘솔창에 key값을 설정하라는 경고장이 떠서 map함수와 key에 대해 알아보았다.
const IterationSample = ()=>{
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map(name=><li>{name}</li>);
return <ul>{nameList}</ul>
}
이렇게 자바스크립트 배열 객체의 내장함수인 map함수를 사용하여 반복되는 컴포넌트를 렌더링 할 수 있다.
map 함수는 파라미터로 전달된 함수를 사용하여 배열 내 각 요소를 원하는 규칙에 따라 변환한 후 그 결과로 새로운 배열을 생성한다.
리액트에서 key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용한다.
유동적인 데이터를 다룰 때에는 key가 없다면 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교하면서 변화를 감지한다.
하지만! key값이 있다면? 이 값을 이용하여 어떤 변화가 일어났는지 더욱 빠르게 알아낼 수 있다.
key값을 설정할때에는 map 함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯 설정하면 된다.
key값은 언제나 유일해야한다. 따라서 데이터가 가진 고유값을 key값으로 설정해야한다.
const articleList = articles.map(article =>(
<Article
title={article.title}
writer={article.writer}
key={article.id}
/>
게시판의 게시물을 렌더링 한다면 이렇게 게시물 번호를 key값으로 설정해야한다.
만약 고유 번호가 없다면? 이때는 map함수에 전달되는 콜백함수의 인수인 index값을 사용하면 된다.
const IterationSample = ()=>{
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map(( name, index )=><li key={index}>{name}</li>);
return <ul>{nameList}</ul>
}
하지만 고유한 값이 없을때만 index값을 사용해야한다.
index를 key로 사용하면 배열이 변경될 때 효율적으로 리렌더링하지 못한다고 한다.
설치
npm install react-daum-postcode
import
import DaumPostcode from "react-daum-postcode";
onComplete
함수에 주소 검색 완료 시 실행시킬 로직을 바인딩한다.나는 주소찾기를 모달창으로 사용했다. (대체적으로 모달창으로 띄워지기 때문이다.)
const [formData, setFormData] = useState({
name: "",
userId: "",
password: "",
confirmPassword: "",
phoneNumber: "",
address: "",
});
const [showPost, setShowPost] = useState(false);
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
const handleComplete = (data) => {
let fullAddress = data.address;
setFormData({ ...formData, address: fullAddress });
setShowPost(false); // 주소 검색 컴포넌트 숨기기
};
// 회원가입 폼
<div className="signup-container">
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
placeholder="이름"
value={formData.name}
onChange={handleChange}
/>
<input
type="text"
name="userId"
placeholder="아이디"
value={formData.userId}
onChange={handleChange}
/>
<input
type="password"
name="password"
placeholder="비밀번호"
value={formData.password}
onChange={handleChange}
/>
<input
type="password"
name="confirmPassword"
placeholder="비밀번호 확인"
value={formData.confirmPassword}
onChange={handleChange}
/>
<input
type="text"
name="phoneNumber"
placeholder="전화번호"
value={formData.phoneNumber}
onChange={handleChange}
/>
<div className="address-container">
<input
type="text"
name="address"
placeholder="주소"
value={formData.address}
onChange={handleChange}
/>
<button type="button" onClick={() => setShowPost(true)}>
주소 찾기
</button>
</div>
<button type="submit">회원가입하기</button>
</form>
{/* 주소 찾기 모달창 */}
{showPost && (
<div
className="modal"
onClick={(e) => {
if (e.target.className === "modal") {
setShowPost(false);
}
}}
>
<div className="modal-content">
<DaumPost handleComplete={handleComplete} />
</div>
</div>
)}
(DaumPost.js)
// 주소찾기 모달창
import DaumPostCode from "react-daum-postcode";
function DaumPost({ handleComplete }) {
const handleAddressSelection = (data) => {
let fullAddress = data.address;
let extraAddress = "";
const { addressType, bname, buildingName } = data;
if (addressType === "R") {
if (bname !== "") {
extraAddress += bname;
}
if (buildingName !== "") {
extraAddress += `${extraAddress !== "" && ", "}${buildingName}`;
}
fullAddress += `${extraAddress !== "" ? ` ${extraAddress}` : ""}`;
}
handleComplete({ address: fullAddress });
};
return (
<DaumPostCode onComplete={handleAddressSelection} className="post-code" />
);
}
export default DaumPost;
DaumPost.js에서
클릭한 주소를 fullAddress에, 나머지 주소를 "" 빈칸으로 선언한다.
받은 data에서 { addressType, bname, buildingName }
을 가져오고 기본주소 타입이 도로명(R)일 때, 법정동/법정리 이름(bname) 이 채워져있다면 extraAddress에 bname을,
만약 건물명(buildingName)이 채워져있고, extraAddress가 빈칸이면 그 뒤에 "," 콤마를 적은 후 건물명을 적고, 빈칸이면 바로 뒤에 건물명을 적는다.
그리고 fullAddress와 extraAddress를 합쳐준 후 handleComplete(부모 컴포넌트에서 가져옴)에 {address : fullAddress} 객체를 넣어준다.
(Join.js)
const handleComplete = (data) => {
let fullAddress = data.address;
setFormData({ ...formData, address: fullAddress });
setShowPost(false); // 주소 검색 완료 시 컴포넌트 숨기기
};
이 때 handleComplete에 들어간 data는 아까 전달받은
{address : fullAddress} 가 된다.
(validateInput.js)
function validateInput(formData) {
console.log(formData);
const errors = [];
// 공백이 아닌 문자가 있는지 검사
const nameReg = /\S/;
// 5~12자의 알파벳 및 숫자 (특수문자 사용하지 않음)
const idReg = /^[a-zA-Z0-9]{5,12}$/;
// 최소 8자, 영어(대문자 또는 소문자), 숫자포함
const passwordReg = /^(?=.*\d)(?=.*[a-zA-Z]).{8,}$/;
// 10~11자리의 숫자
const phoneNumberReg = /^\d{10,11}$/;
if (!nameReg.test(formData?.name)) {
errors.push("이름을 입력해주세요.");
}
if (!idReg.test(formData?.userId)) {
errors.push("아이디는 5~12자의 알파벳 및 숫자로 이루어져야 합니다.");
}
if (!passwordReg.test(formData?.password)) {
errors.push(
"비밀번호는 최소 8자리이며, 영어(대문자 또는 소문자), 숫자를 포함해야 합니다."
);
}
if (formData?.password !== formData?.confirmPassword) {
errors.push("비밀번호와 비밀번호 확인이 일치하지 않습니다.");
}
if (!phoneNumberReg.test(formData?.phoneNumber)) {
errors.push("올바른 전화번호를 입력해주세요.");
}
if (!nameReg.test(formData?.address)) {
errors.push("주소를 입력해주세요.");
}
if (errors.length > 0) {
alert(errors[0]);
return false;
}
return true;
}
export default validateInput;
validateInput
함수에서는 각각 유효성을 const로 선언 후 if문을 사용해 formData의 값들과 비교 후 error 변수에 경고문을 넣는다.
경고문이 하나라도 있으면 alert 창에 첫번째로 들어온 error 메세지를 띄우고 false를, 없다면 true 를 반환한다.
(Join.js)
if (!validateInput(formData)) {
return; // 유효성 검사 실패 시 함수 종료
}
만약 validateInput이 false라면 유효성 검사 실패인 것으로 함수를 종료하게 만든다.
이 로직의 의도는
1. 사용자가 폼을 제출하려고 할 때 handleSubmit
함수가 호출된다.
2. 함수 내에서 validateInput(formData)를 사용하여 제출된 데이터의 유효성을 확인한다.
3. 만약 데이터가 유효하지 않다면 함수는 즉시 종료되고, return;구문 때문에 나머지코드 (fetch를 사용하여 서버에 데이터를 전송하는 부분 등) 이 실행되지 않도록 한다. 바로 끝내버림!!
4. 데이터가 유효하다면 함수는 계속 실행되고 데이터를 서버에 전송한다.
회원가입 후 회원정보를 로그인 할 때 사용할 수 있을까 싶어서 localStorage와 비교해보았다.
localStorage
1. 저장목적 : localStorage는 웹 브라우저에서 제공하는 클라이언트 사이드 저장 메커니즘 중 하나이다.
2. 유지시간 : localStorage에 저장된 데이터는 영구적이다. 즉, 사용자가 브라우저의 탭을 닫거나 재시작해도 데이터가 남아있다.
3. 사용사례 : 세션관리, 사용자의 UI설정, 장바구니 정보, 최근 본 상품 등을 저장하는 데 유용하다.
MSW (Mock Service Worker)
1. 목킹목적 : MSW는 서비스 워커를 사용하여 네트워크 요청을 가로채고, 목킹된 응답을 반환하는 라이브러리이다.
즉, 실제 서버와의 통신 없이 프론트엔드에서 API 요청의 응답을 모방하는 것이 주된 목적이다.
2. 유지시간 : MSW는 어플리케이션의 런타임 중에만 네트워크 요청을 가로챈다.
3. 사용사례 : 백엔드 서버가 아직 준비되지 않았을 때 프론트엔드개발, 에러상황이나 특정상황을 재현하기 위한 테스트에서 사용된다.
따라서 회원정보를 저장하는 용으로 MSW는 적합하지 않고, 회원가입 요청을 MSW가 가로채고 모의 응답을 반환하는 데에 사용해야겠다.
mocks 폴더 안에 3개의 js파일을 만들어준다.
(server.js)
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
(worker.js)
import { setupWorker } from "msw";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
핸들러 코드를 불러와서 그대로 setupWorker()
함수의 인자로 넘겨주면 된다.
요청 핸들러 작성
(handlers.js)
import { rest } from "msw";
export const handlers = [
rest.post("/join", (req, res, ctx) => {
// 여기서 req.body에는 회원가입 데이터가 있다.
console.log("Received signup data:");
return res(ctx.status(200), ctx.json({ message: "회원가입 성공!" }));
}),
REST API를 모킹할 때는 msw
모듈의 rest
객체를 사용한다.
서비스워커 삽입하기
src/index.js
파일 수정하기
import { worker } from "./mocks/worker";
if (process.env.NODE_ENV === "development") {
worker.start();
}
일반적으로 개발환경에서만 가짜 API를 사용하므로 환경변수를 체크해서 선택적으로 서비스 워커가 구동되게 만들어준다.
msw로 백엔드 API 모킹하는 방법
try {
const response = await fetch("/join", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const data = await response.json();
if (response.ok) {
loginUser();
alert(data.message); // "회원가입 성공!"
} else {
alert(data.error || "회원가입 실패");
}
} catch (error) {
console.error("회원가입 에러:", error);
alert("회원가입 중 문제가 발생했습니다.");
}
};
비동기작업(ex. API 호출) 중에 예상치 못한 오류나 예외 상황을 처리하기 위해 try/catch 문을 사용했다.
fetch를 사용하여 서버에 요청을 보낼 때 발생 할 수 있는 오류는 네트워크문제, 서버오류, 클라이언트오류 가 있다.
이러한 상황들은 서버와의 통신 중 예상치 못한 오류를 발생시킬 수 있고 이런 오류상황에 대비해 try/catch 문을 사용하는 것이 좋다고 한다.
즉 어플리케이션의 안정성을 높일 수 있다.
공부하던 중 자꾸 나오는 비동기에 대해 알아보자
동기
Synchronous : 동시에 발생하는
비동기
Asynchronous : 동시에 발생하지 않는
자바스크립트는 동기 방식으로 코드를 해석한다고 한다.
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log('실패했어용'));
HTTP 메서드 지정 및 헤더 추가하기
fetch(url, {
method: 'POST', // GET, POST, PUT, DELETE 등
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data) // 서버에 전송할 데이터
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log('실패했어용'));
특징
Promise?
자바스크립트 비동기 처리에 사용되는 객체
async & await
function callAsync() {
const data = fetch('데이터url')
if(data.id === 1) {
console.log(data.name);
}
}
이 함수에서 콘솔창의 결과는 장담할 수 없다.
fetch는 비동기로 작동하기 때문에 콜백함수로 처리해야만 결과를 보장받을 수 있다.
이 단점을 보완하기 위해 등장한 것이 async와 await 이다.
async function callAsync() {
const data = await fetch('데이터url');
if(data.id === 1) {
console.log(data.name);
}
}
async 이라고 비동기함수를 선언해주고, 그 작업에 await를 붙여주면 이 결과를 사용하는 작업에서 동기처럼 처리가 된다.
즉, 비동기작업이 이루어진 후에 그 결과로 작업이 수행된다.
Promise, Fetch, Async, Await에 대해 알아보자