회원기능 만들기 Auth.js사용한 소셜 로그인

|

NextAuth (Auth.js) 라이브러리를 사용하면 소셜로그인 구현이 쉽다.

NextAuth 세팅

라이브 러리 설치

npm install next-auth

pages/api/auth/[…nextauth].js
위의 경로에 파일 만들고, 아래 코드처럼 입력

import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';

export const authOptions = {
  providers: [
    GithubProvider({
      clientId: 'Github에서 발급받은ID',
      clientSecret: 'Github에서 발급받은Secret',
    }),
  ],
  secret: 'jwt생성시쓰는암호',
};
export default NextAuth(authOptions);

참고로 소셜로그인은 기본적으로 JWT방식이 기본이라서 secret : 란에 JWT 생성용 암호를 임의로 길게 입력해주면 된다.

내 시크릿 코드가 깃허브에 그대로 유출되는거를 방지하기 위해서 .env 파일에 암호를 써서 사용해주자.

로그인 버튼과 페이지 생성

next-auth라이브러리안에 있는 기능을 설정해주면 자동으로 로그인페이지를 제작헤준다.
onClick이 들어가니깐 ‘use client’ 컴포넌트로 만들어서 집어넣는다.


'use client';
import { signIn, signOut } from 'next-auth/react'

// 로그인기능
<button onClick={()=>{ signIn() }}>로그인버튼</button>

// 로그아웃 기능
<button onClick={()=>{ signOut() }}>로그아웃버튼</button>

layout.js 파일에 로그인 기능이있는 컴포넌트인 LoginBtn 컴포넌트 불러오기

(layout.js는 서버컴포넌트라서, 클라이언트 컴포넌트 하나를 따로 생성해서 불러와줬디.)

(app/layout.jsx)

import LoginBtn from "./LoginBtn.js"

(...)
<body>
  <div className="navbar">
    <Link href="/" className="logo">Appleforum</Link>
    <Link href="/list">List</Link>
    <LoginBtn></LoginBtn>
  </div>
  {children}
</body>

페이지에서 로그인된 유저의 정보 출력을 할 때

서버 컴포넌트에서 유저정보 가져와서 클라이언트 컴포넌트에 전송해주는게 낫다. useSession 함수는 html을 다 보여주고나서 한 박자 늦게 실행될 수 있기 때문이다.

  • client component의 경우
(page.js 옆에 있는 layout.js)

'use client'

import { SessionProvider } from "next-auth/react"

export default function Layout({ children }){
  return (
    <SessionProvider>
      {children}
    </SessionProvider>
  )
}

SessionProvider로 감싸놓으면 이 안에 들어가는 client component 컴포넌트들은 로그인된 유저 정보를 출력할 수 있다.

(page.js)

'use client'

import { useSession } from 'next-auth/react'
export default function Page(){
  let session  = useSession();
  if (session) {
    console.log(session)
  }
(생략)

useSession() 쓰면 그자리에 지금 로그인한 유저의 정보가 담겨있다.

  • server component의 경우
import { authOptions } from "@/pages/api/auth/[...nextauth].js"
import { getServerSession } from "next-auth"

export default function Page(){
  let session = await getServerSession(authOptions)
  if (session) {
    console.log(session)
  }

getServerSession() 가져오고 authOptions 가져와서 컴포넌트안에서 사용하면 그 자리에 유저 정보가 남는다.

let session 같은 변수에 담아서 출력해보거나 하면 된다.

참고로 authOption은 맨 위에서 NextAuth 세팅 카테고리해서 작성한 파일이다.

조건문으로 타입만들기, infer

|

코딩애플강의를 보고 정리했습니다.

조건부로 타입만들기

자바스크립트 삼항연산자처럼 타입스크립트도 똑같이 할 수있다.
extends 키워드와 삼항연산자를 이용하면된다.
extends는 왼쪽이 오른쪽의 성질을 가지고 있냐 라는 뜻이다.

Ex)

type Age<T> = T extends string ? string : unknown;
let age : Age<string> //age는 string 타입
let age2 : Age<number> //age는 unknown 타입

좀 더 심화로 이런식으로 array를 활용해서 타입에 조건문을 걸어줄 수 도있다.

type FirstItem<T> = T extends any[] ? T[0] : any

let age1 :FirstItem<string[]>; // string 타입이 됨
let age2 :FirstItem<number>;  // any 타입이됨

infer 키워드

  • 지금 입력한 타입을 변수로 만들어주는 키워드이다.

Ex)

type Person<T> = T extends infer R ? R : unknown;
type 새타입 = Person<string> // 새타입은 string 타입이다.
  1. infer 키워드는 조건문 안에서만 사용이 가능하다.
  2. infer 우측에 자유롭게 작명해주면 타입을 T에서 유추해서 R이라는 변수에 집어넣으라는 뜻이다.
  3. R을 조건식 안에서 마음대로 사용 가능하다.

등등, 이런식으로 타입파라미터에서 타입을 추출해서쓰고 싶을 때 쓰는 키워드이다.

infer 키워드를 활용하는 예시를 더 알아보자.

  1. array 안에 있던 타입이 어떤 타입인지 뽑아서 변수로 만들어줄 수 있다.
type 타입추출<T> = T extends (infer R)[] ? R : unknown;
type NewType = 타입추출< boolean[] > // NewType 은 boolean 타입이다.
  1. 함수의 return 타입이 어떤 타입인지 뽑아서 변수로 만들어줄 수 있다.
type 타입추출<T> = T extends ( ()=> infer R ) ? R : unknown;
type NewType = 타입추출< () => number > // NewType은 number 타입이다.

오브젝트 타입 변환기

|

코딩애플강의를 보고 정리했습니다.

keyof 연산자

object의 key를 뽑아서 새로운 타입을 만들고 싶을 때 사용하는 연산자이다.
object 타입에 사용하면object 타입이 가지고 있는 모든 key 값을 union type으로 합쳐서 내보내준다.

유니온타입은 number|string 이런식의 타입을 뜻한다.

Ex)

interface Person {
  age: number;
  name: string;
}

type PersonKeys = keyof Person;   //"age" | "name" 타입이 된다.

let a :PersonKeys = 'age'; //가능
let b :PersonKeys = 'ageeee'; //불가능

Person 타입은 age, name이라는 key를 갖고 있어서,
PersonKeys는 ‘age’|’name’이라는 리터널 타입이 된다.

나아가서, 이런식으로 코드를 짤 수 있다.

interface Person {
  [key :string]: number;
}
type PersonKeys = keyof Person;   //string | number 타입이 된다.
let a :PersonKeys = 'age'; //가능
let b :PersonKeys = 'ageeee'; //가능

Mapped Types

오브젝트 안에 있는 속성들을 다른 타입으로 한번에 모두 변환할때 유용하다.

type Car = {
  color: boolean,
  model: boolean,
  price: boolean | number,
};

위의 타입에 명시된 모든 속성들을 string 타입으로 바꾸고 싶을때 이런식으로 해주면 된다.

type Car = {
  color: boolean,
  model : boolean,
  price : boolean | number,
};

type TypeChanger <MyType> = {
  [key in keyof MyType]: string;
};

여기서 in 키워드는 왼쪽이 오른쪽에 들어있냐라는 뜻이고,
keyof는 오브젝트 타입에서 key값만 유니온타입으로 뽑아주는 역할이다.

type Car = {
  color: boolean,
  model : boolean,
  price : boolean | number,
};

type TypeChanger <MyType> = {
  [key in keyof MyType]: string;
};

type 새로운타입 = TypeChanger<Car>;

let obj :새로운타입 = {
  color: 'red',
  model : 'kia',
  price : '300',
}

이렇게 하면 새로운타입은 color,model,price 속성을 갖고있고, 전부 string 타입이 된다.

object index signatures

|

코딩애플강의를 보고 정리했습니다.

signatures는 다음과 같은 상황에서 유용하다.

  • object 자료에 타입을 미리 만들어주려고 하지만, object 자료에 어떤 속성들이 들어올 수 있는지 아직 모르는 상태
  • 타입지정할 속성이 너무 많을때

등등..

예시

index signatures

interface StringOnly {
  [key: string]: string;
}

let obj: StringOnly = {
  name: 'kim',
  age: '20',
  location: 'seoul',
};

` [key: string]: string;` 이부분을 주의깊게 살펴봐야한다.

  • {모든속성:string} 이라는 뜻이다.
  • []대괄호에는 string,number 둘 중 하나의 유형만 가능하다.
interface StringOnly {
  age: number; ///에러남
  [key: string]: string;
}

interface StringOnly {
  age: string; ///가능
  [key: string]: string;
}

[] 이 문법은 다른 속성과 함께 사용할 수 있지만,
{ 모든 속성 : string, age : number } 이건 뭔가 논리적으로 말이 되지 않아 에러를 낸다.
하지만 아래처럼 하면 가능하다.

interface StringOnly {
  age: number; ///가능
  [key: string]: string | number;
}

array형태도 가능하다.

interface StringOnly {
  [key: number]: string,
}

let obj :StringOnly = {
  0 : 'kim'
  1 : '20',
  2 : 'seoul'
}

위의 타입 코드는 { 모든숫자속성 : string } 이라는 뜻이다.
하지만, 숫자 key만 넣을거면 그냥 array + tuple 타입 쓰는게 더 직관적일 수 있다.

Recursive Index Signatures

만약 다음과 같이 오브젝트 안에 또 오브젝트가 있다고 가정해보자

let obj = {
  'font-size': {
    'font-size': {
      'font-size': 14,
    },
  },
};

이렇게 중첩된 오브젝트들을 한번에 타입 지정할때 어떻게해야 할까?

interface MyType {
  'font-size': {
    'font-size': {
      'font-size': number,
    },
  };
}

직접 interface 안에 ‘{}’ 이걸 3번 중첩되게 만들어도 상관없겠지만, 이런식으로도 타입 지정을 할 수 있다.

interface MyType {
  'font-size': MyType | number;
}

let obj: MyType = {
  'font-size': {
    'font-size': {
      'font-size': 14,
    },
  },
};

MyType이라는 타입을 만들었는데, ‘font-sze’속성은 MyType과 똑같이 생겼다고 타입을 만들어 준것이다.

Ex)

interface MyType {
  'font-size': number;
  [key: string]: number | MyType;
}

let obj = {
  'font-size': 10,
  secondary: {
    'font-size': 12,
    third: {
      'font-size': 14,
    },
  },
};

implements 키워드

|

코딩애플강의를 보고 정리했습니다.

class 타입을 확인할 때 implements 키워드를 사용해서 할 수 있다.
(interface도 필요함)

implements 키워드

interface CarType {
  model: string;
  price: number;
}

class Car implements CarType {
  model: string;
  price: number = 1000;
  constructor(a: string) {
    this.model = a;
  }
}
let 붕붕이 = new Car('morning');

이렇게 작성해주면, 이 class가 이 interface에 있는 속성을 다 들고있는지 체크해줄 수 있다.

implements는 타입지정 문법이 아님

interface에 들어있는 속성을 갖고있는지 확인만하라는 뜻이다.
class에다가 타입을 할당하고 변형시키는 일은 못한다.

interface CarType {
  model: string;
  tax: (price: number) => number;
}

class Car implements CarType {
  model; ///any 타입됨
  tax(a) {
    ///a 파라미터는 any 타입됨
    return a * 0.1;
  }
}

implements는 class의 타입을 체크하는 용도지 할당하는게 아님을 명심하자