3주 동안 리액트에 대해서 공부하고 처음으로 백엔드와 협업을 진행했다.
짧은 시간동안 리액트에 대해서 숙지할 수 있다고 생각도 안했지만, 이렇게까지 어려울 줄을 상상도 못 했다 😭
API 명세서를 같이 작성했음에도 어떻게 이해하고 사용하는지 몰라서 초반에 굉장히 혼란스러웠다.
심지어 서로 개발을 마치고 맞춰보는 단계에서부터 난리가 나서 혼란스러움이 이루 말할 수 없었다.
그래도 좋은 팀원들 덕분에 확실하게 성장할 수 있었다.
프로젝트 시작 때 백엔드 분 중 한분이 조장이었고, (잘 몰랐지만 굉장한 실력자였다...!) 다 같이 모여서 api 명세서를 작성했었다.
처음에 json 서버를 이용해서 기능 구현하는 것만 익숙해서 나도 모르게 자연스럽게 이미 이전 과제에서 했던 코드를 똑같이 따라하고 있었다. 강의를 보면서 따라하는거에 익숙해서 그랬던것 같다.
당연히... 데이터가 하나도 연결이 안됐고, 요청도 응답도 받을 수 없었다.
axios.js
파일을 만들고, 해당 파일에 axios instance 코드를 추가했다.export const authInstance = axios.create({
// 1. authInstance라는 변수에 axios instance를 만들어서 할당한다.
baseURL: "http:서버주소.com",
// 2. baseURL을 서버 주소로 설정한다.
headers: {
// 3. axios는 기본적으로 js 데이터를 json 형식으로 변경한다.
// 3-1. 만약 serialized JSON Object를 전달할 경우, “application/x-www-form-urlencoded” 형식으로 파일을 전달한다.
Accept: "application/json",
"Content-Type": "application/json",
// 3-2. json 형식으로 확실하게 전달하기 위해서 headers에 어떤 content type을 명시해야 한다고 한다.
},
});
authInstance.get('/baseURL 다음에 붙일 api 주소')
형식으로 사용할 수 있다!axios instance가 기본 설정이라면, axios interceptor는 서버로 보내는 요청과 서버로 부터 받는 요청을 중간에 가로채서 필요한 일을 한 다음 데이터를 다시 전달한다고 한다.
요청 인터셉터
authInstance.interceptors.request.use((config) => {
// 1. axios instance를 이용해서 요청을 보낼 때, 중간에 요청을 가로채서 서버로 요청을 보내기 전에 함수가 실행된다.
if (config.headers === undefined) return;
// 2. 요청 config.header가 undefined면 아무 일도 실행되지 않는다.
const token = localStorage.getItem("id");
// 3. 로컬스토리지에 'id'라는 값으로 저장한 내용을 'token'이라는 변수에 담고,
config.headers["Authorization"] = `${token}`;
// 3-1. 로그인하면서 얻은 token 값을 그대로 header에 넣어서 요청을 보낸다.
return config;
});
const errorInterceptor = (error) => {
// 1. 서버에서 응답을 줄 때 해당 응답을 중간에 가로채는 응답 인터셉터도 활용할 수 있다.
// 1-1. 해당 인터셉터는 서버에서 내려준 response가 error response일 때 응답을 가로챈다.
if (error.response.status === 401) {
// 2. 만약 서버에서 보낸 응답 에러가 401에러일 경우,
window.location.replace("/login");
// 3. 로그인 페이지로 이동시키고
alert("로그인 기간이 만료되었습니다. 다시 로그인 해주세요.");
// 3-1. 로그인 기간이 만료되었다는 alert 메세지를 송출한다.
localStorage.clear();
// 4. 그리고 로컬 스토리지의 내용도 싹 다 비워준다!
}
return Promise.reject(error);
};
authInstance.interceptors.response.use(errorInterceptor);
// 5. 그리고 응답 인터셉터 중 errorInterceptor라는 인터셉터를 활용하겠다는 코드를 추가한다.
**** 대표 예시: 비밀번호 ****
// 인풋 값 가져오기
const [password, setPassword] = useState("");
// 1. 인풋의 value를 지정할 state 선언
// 오류 메세지 출력
const [passMsg, setPassMsg] = useState("");
// 2. 오류 메세지가 생길 경우 해당 메세지를 담을 state 선언
// 유효성 검사
const [isPass, setIsPass] = useState(false);
// 3. 사용자가 설정한 비밀번호가 유효하지 않을 경우 state는 false, 유효할 경우 true로 설정한다.
// input onChange 함수
const onChangePassword = (e) => {
// 4. password를 쓰는 input의 값이 바뀌는 것을 감지하는 함수
const currentPassword = e.target.value;
// 4-1. currentPassword라는 변수에 현재 이벤트가 발생하는 target의 value를 담는다.
setPassword(currentPassword);
// 4-2. 변경되는 값을 setPassword에 담아서 password의 state가 currentPassword로 될 수 있도록 설정한다.
const passRegExp = /^[a-zA-Z0-9!@#$%^&*]{8,15}$/;
// 4-3. 비밀번호 유효성 검사를 위해 passRegExp라는 변수에 백엔드와 협의한 내용으로 regular expression을 넣는다.
// 4-4. 대소문자와 숫자, 특수문자를 포함한 8-15자리의 비밀번호만 받는다.
if (!passRegExp.test(currentPassword)) {
// 4-5. 특정값이 정규표현식의 조건에 부합하는지 확인하는 js의 메소드는 regExp.test(string)이다.
// 4-6. 민약 currentPassword의 값이 passRegExp의 조건을 충족하지 못 하면,
setPassMsg("8-15자의 대소문자, 숫자, 특수문자(!@#$%^&*)로 입력해주세요.");
// 4-7. setPassMsg에 경고문구 (help text)를 추가한다.
setIsPass(false);
// 4-8. 유효하지 않은 비밀번호기 때문에 유효 여부를 확인하는 setIsPass의 state는 false로 설정한다.
} else {
// 4-9. 만약 currentPassword의 값이 passRegExp의 조건에 부합한다면,
setIsPass(true);
// 4-10. 유효 여부를 확인하는 setIsPass의 state를 true로 변경한다.
}
};
// return문 안에 있는 내용
<div>
<Input
onChange={onChangePassword}
// 4-11. 사용자가 비밀번호를 입력할 인풋필드에 onChangePassword 함수를 심어서 사용자가 작성하는 값을 인식한다.
type="password"
name="password"
label="비밀번호를 입력하세요."
width={"250px"}
/>
</div>
<p
style={{
ontSize: "11px",
color: "red",
}}
>
{passMsg}
// 2-1. 오류 메세지가 생길 경우 이 곳에 메세지가 출력된다.
</p>
[댓글 보기 버튼]
을 클릭 했을 때 화면에 댓글 리스트가 생기게 만들어봤다.// 버튼 클릭 여부를 확인하기 위한 state
const [display, setDisplay] = useState(false);
return (
<div>
<SmallButton
onClick={() => {
setDisplay(!display);
}}
// 1. 버튼을 클릭 했을 때 display의 state를 반대로 변경한다.
// 1-1. !display라고 설정할 경우, true일 땐 false로, false일 땐 true로 값을 변경시킨닫.
>
{display && "Hide"}
// 2. 만약 display의 값이 true일 경우, 버튼에 "Hide"라고 표시된다.
{!display &&
// 3. 만약 display의 값이 false일 경우,
(commentList.length === 0
// 3-1. 서버로부터 받아온 commentList의 길이가 0일 경우,
? "Replies"
// 3-2. 버튼의 내용이 "Replies"라고 표시되고,
: `View ${commentList.length} more replies`)}
// 3-3. 0이 아닐 경우 "view commentList.length more replies"라는 문구가 표시된다.
</SmallButton>
<div>
{display &&
// 4. 그리고 display가 true일 경우,
commentList.map((item) => (
// 4-1. commentList 배열을 map 돌려서 모든 코멘트가 1개씩 표시되도록 한다.
<div key={item.id}>
// 4-2. map을 돌릴 땐 하나의 item이 각자 고유의 key를 가져야하기 때문에 코멘트 아이디를 key로 설정하고,
<div>{item.nickname}</div>
<div>{item.content}</div>
<div>{item.createdAt}</div>
// 4-3. 각 코멘트에 있는 nickname, content, createdAt을 전부 표시한다.
</div>
))}
</div>
</div>
);
}
팀원과의 협업, 백엔드와 연결하는 방법, api 명세서 이해하고 요청 주고 받기 등등 이번 프로젝트는 새로운 시도를 많이 했다.
리액트만 공부하다가 처음으로 프론트 개발자처럼 프로젝트를 진행해본건데, 정말... 매우 힘들었다 🤯
요청을 주고 받는다는 느낌이 뭔지 이제 좀 감이 잡히고 전달받은 데이터를 저장하는 방식 등등..
아직 갈 길이 매우 멀지만, 이제 조금 감을 잡은 것 같아서 힘들면서도 배울 점이 많은 프로젝트였다!
출처: