[TIL] 로그인 2

신재욱·2023년 4월 14일
1
post-thumbnail

📒 오늘 공부한 내용

🔍수업목차

[23-1] 새로고침 이후 재접속 과정
[23-2] 브라우저에 accessToken 저장하기
[23-3] Next.js의 렌더링 방식
[23-4] 권한분기
[23-5] 권한분기를 진행하기 위한 필요한 사전지식
[23-6] 함수를 리턴하는 함수 HOF
[23-7] HOC

✅ 새로고침 이후 재접속 과정


  • accessToken을 변수에 넣어 두고 사용했다.
  • 하지만 새로고침을 하게되면 로그인 정보가 모두 날아가 다시 로그인을 해야 했다.
  • 새로고침은 브라우저에서 해당 주소로 다시 enter를 친것과 같다. 즉, 새로운 html,css,js를 다시 받아온다.
  • 이전에 그려주었던 state변수들이 날아가게 되기때문에 accesToken이 날아갔던 것

✅ 브라우저에 accessToken 저장하기


🎯 브라우저 저장소에 토큰을 저장하는 방식은 실무에서 사용하는 방식이 아니다. refreshToken을 배우기 전 정확한 방법을 알아가기위한 준비단계

login-localstorage

import { gql, useMutation } from "@apollo/client";
import { useState } from "react";
import type { ChangeEvent } from "react";
import type {
  IMutation,
  IMutationLoginUserArgs,
} from "../../../src/commons/types/generated/types";
import { useRouter } from "next/router";
import { useRecoilState } from "recoil";
import { accessTokenState } from "../../../src/commons/stores";

const LOGIN_USER = gql`
  mutation loginUser($email: String!, $password: String!) {
    loginUser(email: $email, password: $password) {
      accessToken
    }
  }
`;

export default function LoginPage(): JSX.Element {
  const router = useRouter();
  const [, setAccessToken] = useRecoilState(accessTokenState);

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loginUser] = useMutation<
    Pick<IMutation, "loginUser">,
    IMutationLoginUserArgs
(LOGIN_USER);

  const onChangeEmail = (event: ChangeEvent<HTMLInputElement>): void => {
    setEmail(event.currentTarget.value);
  };

  const onChangePassword = (event: ChangeEvent<HTMLInputElement>): void => {
    setPassword(event.currentTarget.value);
  };
  const onClickLogin = async (): Promise<void> => {
    try {
      // 1. 로그인 뮤테이션 날려서 accessToken 받아오기
      const result = await loginUser({
        variables: { email, password },
      });
      const accessToken = result.data?.loginUser.accessToken;
      console.log(accessToken);

      // 2. 받아온 accessToken을 globalState에 저장하기
      if (accessToken === undefined) {
        alert("로그인에 실패했습니다! 다시 시도해 주세요!");
        return;
      }
      setAccessToken(accessToken);
      localStorage.setItem("accessToken", accessToken); // 임시 사용(나중에 지울 예정)

      // 3. 로그인 성공 페이지로 이동하기
      void router.push("/section23/23-02-login-success-localstorage");
    } catch (error) {
      if (error instanceof Error) alert(error.message);
    }
  };

  return (
    <>
      이메일: <input type="text" onChange={onChangeEmail} />
      비밀번호: <input type="password" onChange={onChangePassword} />
      <button onClick={onClickLogin}>로그인</button>
    </>
  );
}
      setAccessToken(accessToken);
      localStorage.setItem("accessToken", accessToken); // 임시 사용(나중에 지울 예정)

로그인

  • 이부분이 이전의 로그인과 차이다. 위 코드가 스토리지에서 가져온 토큰 값을 로컬스토리지에 저장하게 해준다.

새로고침

  • 그런데 새로고침을 하면 토큰값을 그대로 로컬 스토리지에 있지만 데이터는 초기화된다.

✅ Next.js의 렌더링 방식


  • Next.jsdiffinghydration 과정을 거쳐 화면을 렌더링 한다.
  • 이 과정은 흔히 말하는 서버사이드 렌더링과는 살짝 다른개념 이다.
  • hydrationdiffing은 어떤 개념과 더 밀접한 연관이 있냐면, 바로 프리 렌더링이다.
  • 브라우저에서 특정 페이지를 요청하게 되면, 프론트엔드 서버에서는 해당 페이지의 HTML, CSS, JS를 미리 그려본다.
  • 이후 우리가 아는 것 처럼 브라우저로 HTML, CSS, JS 파일을 던져준다.
  • 그럼 보내준 페이지를 브라우저에서 렌더링하게 된다.

💡 diffing 과 hydration

diffing : 브라우저에서 그린내용과 프론트엔드 서버에서 렌더링한 내용을 얼마나 차이 나는지 비교하게 되는 과정
hydration : diffing을 통해 비교한 후 최종적으로 반영해 렌더링하게 되는 과정

  • 이제 우리는 오류의 이유를 알고 있다.
  • localStorage는 브라우저에만 있는데, 서버에서 먼저 화면을 그려보기 때문에 발생하는 오류다.
  • 이를 해결하기 위해서는 useEffect를 사용해 렌더링 이후에 실행

apollo

import {
  ApolloClient,
  ApolloLink,
  ApolloProvider,
  InMemoryCache,
} from "@apollo/client";
import { createUploadLink } from "apollo-upload-client";
import { useRecoilState } from "recoil";
import { accessTokenState } from "../stores";
import { useEffect } from "react";

const GLOBAL_STATE = new InMemoryCache(); // 컴퓨터 메모리에다가 백엔드에서 받아온 데이터 모두 임시로 저장해놓기 => 나중에 알아보기

interface IApolloSettingProps {
  children: JSX.Element;
}

export default function ApolloSetting(props: IApolloSettingProps): JSX.Element {
  const [accessToken, setAccessToken] = useRecoilState(accessTokenState);

  // 3. 프리렌더링 무시 - useEffect 방법
  useEffect(() => {
    const result = localStorage.getItem("accessToken");
    setAccessToken(result ?? "");
  }, []);

  const uploadLink = createUploadLink({
    uri: "백엔드 주소",
    headers: { Authorization: `Bearer ${accessToken}` },
  });

  const client = new ApolloClient({
    link: ApolloLink.from([uploadLink]),
    cache: GLOBAL_STATE, // 컴퓨터의 메모리에다가 백엔드에서 받아온 데이터 모두 임시로 저장해놓기 => 나중에 알아보기
  });

  // prettier-ignore
  return (
    <>
      <div>안녕하세요 영희입니다</div>
        <ApolloProvider client={client}>
          {props.children}
        </ApolloProvider>
      <div>안녕하세요 훈이입니다</div>
    </>
  );
}
  // 3. 프리렌더링 무시 - useEffect 방법
  useEffect(() => {
    const result = localStorage.getItem("accessToken");
    setAccessToken(result ?? "");
  }, []);
  • 이렇게 렌더링 이후에 들어오도록 까지 완료하면, 로그인 후 새로고침을해도 토큰이 날아가지 않는다.

✅ 권한분기


  • 로그인 인증이후에는 이에따른 권한 분기가 이루어진다.
  • 아주 작게는 로그인을 한 사람로그인을 하지 않은 사람.
  • 추가적으로 로그인에 등급을 매기면, 운영자로 로그인 한 사람, 판매자로 로그인 한 사람, 거래처 사장님으로 로그인 한 사람 등 다양하게 권한을 분리할 수 있다.

✅ 권한분기를 진행하기 위한 필요한 사전지식


📂 스택과 큐

스택 : 출입구가 하나인 우물 형태의 데이터 구조

  • 스택은 출입구가 하나이기때문에 가장 처음에 입력된 함수가 가장 나중에 스택을 빠져나가게 된다.
  • 이를 우리는 First In Last Out 이라고 하며, 앞글자를 따 FILO라고도 한다.

: 양방향 출입이 가능한 파이프 형태의 데이터 구조

  • 큐는 스택과 다르게 출입구가 나뉘어있어 가장 먼저 입력된 함수가 가장 먼저 빠져나간다.
  • 이를 우리는 First In First Out라고 한다.

📂 스코프 체인

<!DOCTYPE html>
<html lang="ko">
	<head>
		<title>클로저 실습</title>
		<script>
			function aaa(){
				banana = 3
				console.log(banana)
			}
			aaa();
		</script>
	</head>
	<body>
		
	</body>
</html>
  • local에 바나나가 있었다면 local에 바나나가 들어와 있었겠지만, 없었기 때문에 글로벌까지 찾아 올라간다.
  • 스코프 체인이란 해당 스코프에 없으면 상위 스코프에는 있나 스코프를 찾아 올라가는 과정을 의미한다.

📂 클로저(closure)

<!DOCTYPE html>
<html lang="ko">
	<head>
		<title>클로저 실습</title>
		<script>
			function aaa(){
				const apple = 10

				function bbb(){
					console.log(apple)
				}
				bbb()
			}
			aaa();
		</script>
	</head>
	<body>
		클로저 실습
	</body>
</html>
  • bbb함수 스코프 안에 apple이라는 변수가 없어 aaa라는 상위함수의 스코프로 찾아 올라가 apple 이라는 변수를 찾게 된다.
  • aaa 함수는 bbb의 closure가 되며 bbb가 apple을 찾아 올라갈 수 있는 이유는 실행컨텍스트가 외부 환경 요소를 수집하기 때문이다.
  • 클로저라고 함은 상위 함수와, 해당함수(여기서는 bbb함수)가 선언된 스코프 즉 상위함수를 둘러싼 환경

💡 스코프 체인과 클로저 정리

  • 클로저(closure) : 상위 함수 + 상위함수의 lexical enviroment(상위함수를 둘러싼 환경)
  • 스코프 체인(scope chain) : 바로위 함수 스코프 뿐만아니라 global 스코프 까지 찾아 올라가는 과정을 scope chain

📂 호이스팅

// closure.html 파일
<!DOCTYPE html>
<html lang="ko">
	<head>
		<title>클로저 실습</title>
		<script>
			function aaa(){
				const apple = 10

				function bbb(){
					console.log(apple)
				}
				bbb()

				const qqq = 3
			}
			aaa();
		</script>
	</head>
	<body>

	</body>
</html>
  • qqq 변수를 추가해서 다시 aaa()에 브레이크 포인트를 걸어 실행 후 aaa함수 안으로 들어가보면
  • 스코프의 local을 보니 아직 실행되지도 않았는데 qqq가 undefined로 들어와 있다.
  • 이는 호이스팅 때문이다.
  • var였다면 접근도 가능했겠지만, letconstTDZ(Temporal Dead Zone)에 들어가 있기 때문에 접근은 불가능하다.

✅ 함수를 리턴하는 함수 HOF


함수 선언식

function aaa(apple){

	return function bbb(banana){
		console.log(banana)
		console.log(apple)
	}
}

aaa(2)(3)

// 실행 결과
// 2 => aaa에 넣은 인자값
// 3 => bbb에 넣은 인자값
  • 이렇게 각 변수를 함수의 파라미터로 넘기게 되면, 실행시 넘겨준 인자값으로 콘솔이 출력되게 된다.
  • bbb에 apple이 없음에도 불구하고 받아오는 이유는 클로저 때문이다.

화살표 함수로 변경

const aaa = (apple)=>{
	return (banana)=>{
				console.log(apple)
				console.log(banana)
		}
}

aaa(2)(3)
  • 화살표함수로 바꿔줄 수 있고, 여기서 중괄호와 리턴사이에 아무것도 없다면 중괄호를 생략해서 적을 수 있다.

최종

const aaa = (apple)=>(banana)=>{
				console.log(apple)
				console.log(banana)
}

aaa(2)(3)

✅ HOC


HOC : Higher Order Component 개념은 위의 클로저로부터 확장된 개념

  • HOC는 상위에 있는 컴포넌트로 다른 컴포넌트보다 먼저 실행되는 컴포넌트라고 생각하면 된다.
  • 내부에 컴포넌트가 없음에도 불구하고 컴포넌트를 리턴하고 있다.
  • 클로저로 외부의 컴포넌트를 가지고 올 수 있기때문에 사용이 가능하다.
profile
1년차 프론트엔드 개발자

0개의 댓글