어제 로그인의 전체적인 흐름에 대해서 알아보았고 백엔드에 login api를 요청하여 로그인을 해봤는데 그 과정에서 문제점을 하나 발견했다.
로그인하고나서 새로고침을 하는 순간 로그인된 정보가 사라지게 되는 것이었다😱
내 정보 돌려내..
심각한 문제이기 때문에 로그인을 유지하기 위한 방법을 공부했다!
로그인을 하고 새로고침을 하는 순간 정보가 사라지는 이유는
결론부터 말하면 토큰을 변수에 담아놨기 때문에 새로고침을 하는 순간 초기화가 되면서 프론트엔드에서 html, css, js까지 전부 처음부터 받아와지기 때문에 토큰도 사라지게 된다.
따라서 html, css, js 등과 관련이 없는 브라우저 저장소에 토큰을 저장해야 하는데
로컬스토리지
, 세션스토리지
, 쿠키
에 저장을 해야한다.
- 로컬스토리지: 브라우저를 껐다가 켜도 정보가 남아있다.(보안에 취약)
- 세션스토리지: 브라우저를 껐다가 키면 정보가 사라진다.
- 쿠키: 브라우저를 껐다가 켜도 정보가 남아있다.
로컬스토리지와 쿠키의 차이점!
로컬스토리지는 브라우저 전용(프론트 서버나 백엔드가 전혀 알지 못함)
쿠키는 백엔드와 연동이 가능(api 요청시 쿠키를 같이 첨부하여 보내기 가능)
개발자 도구의 Application으로 들어가면 3가지가 있는 것을 볼 수 있고
로컬스토리지의 Key와 Value에 정보를 입력하면 콘솔에서 확인이 가능하다!
Next.js에서는 프리렌더링(Pre-rendering)
이라는 개념이 있는데 프론트엔드 서버에서 초기 밑그림을 먼저 그려보는 것을 말하며 이후 브라우저에 html, css, js넘겨주게 된다.
(초기 밑그림을 그릴 때는 onClick, onChange, useEffect 등은 실행되지 않음 / 브라우저에서 실행)
이때 밑그림과 브라우저에 넘겨진 것의 차이를 비교하고(= diffing) 변경사항을 최종적으로 적용
(= hydration)해준다.
(hydration 과정에서 onClick, onChange, useEffect 등과 같은 함수가 연결)
★ 따라서 프리렌더링은 프론트서버에서 진행되기 때문에 브라우저 전용인 로컬스토리지가 없다!!
따라서 이러한 과정때문에 Next.js의 렌더링 문제가 발생하여 에러가 나게되는 것이다.
프리렌더링을 할 때 로컬스토리지를 못찾겠다고 하니 3가지 방법을 통해 localStorage에 접근하자
//1. process.brower 방법
if (process.browser) { //함수실행할 때 브라우저 화면이라면
console.log("지금은 브라우저다!!!!");
localStorage.getItem("accessToken");
} else { //브라우저아니고 프론트 서버라면
console.log("지금은 프론트엔드 서버다!!!!(yarn dev 프로그램이다));
localStorage.getItem("accessToken");
}
//2. typeof window 방법
if (typeof window !== "undefined") { //윈도우를 빈 값이 아니면(=윈도우가 있으면 =브라우저)
console.log("지금은 브라우저다!!!!");
localStorage.getItem("accessToken");
} else { //윈도우가 빈 값이면(=프론트서버)
console.log("지금은 프론트엔드 서버다!!!!(yarn dev 프로그램이다!!");
localStorage.getItem("accessToken");
}
//3. useEffect 방법
useEffect(() => { //서버에서 실행되는 애 아니기 때문에 브라우저에서 실행
console.log("지금은 브라우저다!!!!");
const accessToken = localStorage.getItem("accessToken"); //로컬스토리지에서 꺼내온걸
setAccessToken(accessToken || ""); //글로벌스테이트에 담아주기 때문에 리렌더 되더라도 로컬스토리지에서 다시 꺼내와서 accessToken이 없어지지 않음
}, []);
위의 3가지 방법 중 useEffect가 가장 효율적인 방법이다!
✅ 한줄 정리(한줄이지만 매우 김)
프리렌더링을 할 때는 로컬스토리지를 찾지 못해 에러가 발생하는데 브라우저에서 실행되는 useEffect를 이용하면 프론트서버가 프리렌더링 후 useEffect 덕분에 한번 더 리렌더링이 되면서 로컬스토리지에 있는 accessToken이 꺼내와져서 글로벌 스테이트에 담아주기 때문에 새로고침해도 계속 사용가능
스토리지를 통해 토큰을 가져와서 계속 로그인을 유지하는데에 성공을 했지만
이제는 로그인을한 유저만 접속할 수 있는 페이지와
로그인을 하지 않은 유저만 접속할 수 있는 페이지를 나눠야 하는데
이를 권한분기
라고 한다.
위의 사진처럼 useEffect를 사용하여 로컬스토리지에 토큰이 없으면 다른 페이지로 이동을 시켜주면 권한분기를 적용시킨 것이다.
하지만 로그인을 해야 사용할 수 있는 페이지가 많고, 이동해야하는 페이지를 다르게 하려면 매번 페이지마다 찾아가서 수정을 하는 번거로움이 있기 때문에 HOC
를 적용해야 한다.
HOC(Higher Order Componenet)란 함수가 실행되기 전에 먼저 실행되는 컴포넌트를 말한다.(JSX를 리턴!)
먼저 실행되는 컴포넌트를 만들어주기 위해서 권한분기로 적용했던 useEffect를 어디에 넣어줘야할지 생각해봐야한다.
결론부터 말하면 먼저 실행되는 컴포넌트는 있든 없든 실행은 되며,
중간에 useEffect 로직을 추가하게 됨으로써 먼저실행하게끔 해주는 것이다.
Aaa컴포넌트에서 Hoc(Component)(props)로 실행하고
먼저실행 컴포넌트에 props를 통해 Bbb로 넘어가기 때문에
먼저 실행 컴포넌트는 있든 없든 실행되는 것이다.
함수형 컴포넌트로 바꾸기 위해서는 아래 사진과 같이 변경하면 된다.
아래 사진은 실제로 HOC를 적용한 모습이며
HOC는 다른컴포넌트랑 항상 같이 쓰이기 때문에 with를 붙여준다.(withAuth 등)
HOC는 클래스형에서 사용하며 함수형은 커스텀훅으로 더 간단하게 사용이 가능하다.
✅HOC 구조를 더 이해하기 위해서 Scope와 Closure의 개념을 알아야해서 블로그 내 Scope, Closure 글 참고하기
HOC(Higher Order Function)란 함수가 실행되기 전에 먼저 실행되는 함수를 말한다.(JSX를 리턴하지 않음!)
* 이전 방법(id값을 가져와 event.target.id으로 표현)
export default function Example() {
const onClickMove = (event) => {
router.push(`/${event.target.id}`)
}
return(
[객체].map map((el) => (
<div key={el.id} id={el.id} onClick={onClickMove}>
)
* HOF 방법
export default function HOF() {
const onClickMove = (boardId) => (event) => {
router.push(`/${boardId}`);
}
return(
[객체].map map((el) => (
<div key={el.id} onClick={onClickMove(el.id)}>
)
이전에는 id 값을 지정해 event.target.id로 가져왔다면
HOF는 return 부분의 onClickMove라는 함수 안에 id를 넣어주게 되면
실행이 될 때 event가 작동되어 boardID 함수가 실행된다.
✅ (el.id)이 (boardId)를 통해 작동이 되는 것이며 boardId의 이름은 아무거나 지어도 괜찮다!