프론트/Vue

[Nuxt] 회원가입, 로그인 기능 만들기

ZestLee 2022. 2. 6. 19:54

2019-11 에 작성된 글입니다.

1. 회원가입

글 작성 등의 기능을 하기 위해서는 회원가입이 먼저 필요하겠죠? 일단, 페이지부터 만들어 주겠습니다.

├── pages/
   ├── recipes/
     └── signUp.vue
  └── index.vue

pages 폴더에 vue 파일을 생성하면, router 설정은 Nuxt가 알아서 해 줍니다. (와, 너무 쉽다! 🤭) 이렇게 해서 페이지 생성은 끝이고, 본인이 원하는 메뉴에 자동생성 된 router name을 추가해 주기만 하면 됩니다.

router :to="{name: 'users-signUp'}"

router name은 .nuxt > router.js 에서 확인할 수 있습니다!

이제 회원가입 폼을 만들어야 할 텐데요, 회원가입 폼은 Vuetify의 card를 이용하고, 그 안의 텍스트 인풋은 Vuetify의 text-field를 이용하여 만들어 보도록 하겠습니다.

이것 역시 많은 이야기를 하는 것보다 공식 문서를 보고 직접 하는 것이 더 빠를 것이기 때문에, 위 링크의 공식 홈페이지를 꼭 확인해 주세요.

회원가입 페이지를 전부 만들었다면, 미리 만들어 둔 백앤드 서버와 연결 시켜 회원가입을 할 수 있도록 해야 합니다.

서버에 API 통신을 할 수 있는 방법은 많지만, 저는 그 중 axios 방식을 통해 통신하도록 하겠습니다.

먼저, nuxt axios 모듈을 설치해 주세요.

npm install -s @nuxtjs/axios

설치가 완료됐으면, nuxt.config.js로 이동하여 방금 설치한 axios 모듈을 추가해 주고, basseURL 설정을 해 주겠습니다.

baseURL 설정은 필수는 아니지만, axios 를 사용할 때마다 주소를 하드 코딩 하지 않도록 하는 작업입니다.

modules: [
  '@nuxtjs/axios'
],
axios: {
  baseURL: "http://record.byit.co.kr/api"
}

그럼, 회원가입 페이지로 다시 돌아가 볼까요?

 

회원가입 버튼의 클릭 이벤트에 submitUser라는 회원가입 함수를 바인딩 해 주겠습니다.

submitUser() {
 this.memberObj = {
  username: this.memberObjLocal.username,
  first_name: this.memberObjLocal.first_name,
  last_name: this.memberObjLocal.last_name,
  ... // 생략
 };
 
 const config = {
   headers: { "Content-Type": "multipart/form-data" }
 };
 
 const formData = new FormData();
 
 for (let data in this.memberObj) {
  formData.append(data, this.memberObj[data]);
 }
 
 try {
  let response = await this.$axios.$post("/users/", formData, config);
  this.$router.push("/");
 } catch (e) {
  console.log(e.response);
 }
}

 

 

 

a. 이전 페이지에서 넘어온 정보 + 현재 페이지에서 작성한 데이터를 객체 하나에 넣음

b. 반복문 돌려서 formData에 넣음

c. axios로 백엔드와 통신

d. 완료 시 / 로 이동

 

2. 로그인

그 다음으로는 로그인 페이지를 만들어 줍니다. 저는 alert창을 Vuetify로 띄워 줄 텐데요, 아래의 공식 홈페이지를 참고해 주세요. (웹 브라우저의 기본 alert을 사용하더라도 무방합니다!)

로그인은 card 형태로 만들었습니다.

 

작성이 완료되었다면, 이제 로그인 버튼에 연결시켜 줄 로그인 함수를 만들어야 하겠죠?

 

로그인 함수는 다른 함수들과 다르게 vuex로 작성해 줄 것입니다.

 

그 이유는, vue 자체는 컴포넌트들로 이루어졌기 때문에, 로그인 할 경우에 정보들을 컴포넌트 간에 EventBus 혹은 props로 공유를 해야 합니다.

하지만 그 작업이 매우 불편하고, 또한 EventBus가 남발이 될 수가 있기 때문에 하나의 저장관리소인 store를 통해 로그인을 해 주는 것입니다.

Nuxt는 Vuex의 생김새가 Vue 프로젝트 기본 vuex와 조금 다릅니다. 여기에서 애를 먹었는데, Nuxt는 Vue와 다르게 store.js 파일이 없이 store라는 폴더만 존재했습니다. 검색을 해 보니, nuxt는 모듈 모드로 동작을 시켜야만 했습니다. (사실 store 폴더의 index.js에 기존 Vue 방식과 같이 클래식 모드로 바로 작성하는 법도 있지만, 이는 실행 시에 Nuxt의 다음 버전에서 제거될 모드라 표출되기 때문에, 되도록 모듈 모드로 만들어 주는 게 좋습니다.) 지금 생각해 보면, 프로젝트가 커지면 코드가 중구난방으로 변할 위험이 큰데, 모듈이 그걸 잘 잡아 주는 것 같습니다.

그럼, store 폴더의 index.js 에 다음과 같이 state, mutations, actions를 export 해 줍니다. 

export const state = () => ({
});

export const mutations = {
}

export const actions = {
}

그리고 login 함수를 넣어 줄 user.js를 store 폴더에 만들어 주고, 다음과 같이 state, mutations, actions를 export 시키겠습니다.

export const state = () => ({
  userInfo: null,
  isLogin: null,
  isLoginError: null
});
export const mutations = {
 loginSuccess(state, payload) {
  state.isLogin = true
  state.isLoginError = false
  state.userInfo = payload
 },
 loginError(state) {
  state.isLogin = false
  state.isLoginError = true
 },
 logout(state) {
  state.isLogin = false
  state.isLoginError = false
  state.userInfo = null
 }
}

state는 컴포넌트 간 공유될 data입니다. 로그인 할 시 아이디 등의 정보가 들어갈 userInfo, alert과 로그인 상태를 알아 볼 때 쓰일 isLogin, isLoginError를 선언해 줍니다.

mutation은 상태 변이 함수를 작성해 줄 곳입니다. 여기에는 login, loginError, logout 함수를 적어 줄 겁니다. 이곳에는 간단하게 상태 변이하는 코드만 작성해 주고, 서버 통신 등의 복잡한 비동기 로직은 Actions에 적어 주겠습

export const actions = {
    login({ dispatch }, loginObj) {
      this.$axios
       .$post("/api-token-auth/", loginObj)
       .then(res => {
            let username = res.user_info[0].username
            let token = res.token
            sessionStorage.setItem("access-token", token)
            sessionStorage.setItem("username", username)
            dispatch('getMemberInfo')
            $nuxt.$router.push({ name: 'index' })
       })
       .catch((e) => {
            console.log(e)
            console.log(loginObj)
            this.commit("user/loginError")
       });
    },
    logout({ commit }) {
     sessionStorage.removeItem("access-token")
     sessionStorage.removeItem("username")
     commit("logout")
     $nuxt.$router.push({ name: 'index' })
    },
    getMemberInfo({ commit }) {
     if (process.browser) {
      let token = sessionStorage.getItem("access-token")
      let config = {
       headers: {
          "Authorization": token
         } 
       } 
       if (token != null) {
        this.$axios
         .$get("/users/" + sessionStorage.getItem("user_idx"), config)
         .then(response => {
           let userInfo = {
           idx: response.id,
           nickname: response.nickname,
           name: response.last_name + response.first_name
          }
           commit('loginSuccess', userInfo)
    	  })
          .catch((e) => {
           this.commit("user/loginError")
          })
       }
      }
    }
}

코드 정렬을 수동으로 하다 보니, 괄호 부분은 정렬이 잘 안 맞네요. 😂 양해 부탁드립니다.

 

코드를 간단하게 해석해 보겠습니다.

우선 login 함수는 loginObj를 가지고 호출하여 /api-token-auth/ api에 post 요청을 하고, 회원 정보들과 토큰을 가져오는 것입니다. 그 정보를 세션에 넣어 브라우저가 켜져 있을 동안 정보를 저장하고, 멤버 정보를 가져오는 getMemberInfo 함수를 호출한 후, 그 함수가 정상적으로 실행이 됐을 경우 index로 돌아가는 형태입니다.

 

logout은 로그인할 때 저장한 세션 정보를 삭제해 주고 mutations 에서 만든 logout을 실행한 후 (state를 바꾸는 것) index로 돌아가는 형태입니다.

 

getMemberInfo는 token이 있을 시에 user api에서 user 정보를 가져오고 그 정보를 userInfo에 저장한 후 isLogin 상태를 true 로 만들어 주는 형태입니다.

 

여기까지 오셨다면 mutaions와 actions를 나눈 이유가 궁금하실 텐데요, 그 이유를 설명해 드리겠습니다.

 

위에서 설명드렸다 시피, 저는 로그인을 한 후에 로그인 정보를 컴포넌트끼리 주고 받는 과정이 불편하여 Vuex를 사용하였습니다. 근데 만약, Mutations 안에 비동기 처리와 복잡한 로직들이 포함되고, 또한 여러 컴포넌트에 변경을 요청을 한다면, 컴포넌트 변경 순서 파악이 어지럽거니와, Vuex를 도입하기 이전처럼 그 과정이 불편해집니다. 컴포넌트 변경 순서가 어지러워지지 않도록, 순서가 예측되지 않는 서버 통신 같은 로직들은 Actions에 적어 주면 됩니다.

공식 홈페이지에 따르면, Mutations에는 상태 변이를 위한 로직을 적어 주고, Actions에는 액션을 적어 줍니다.

여기에서 getMemberInfo 함수에 대해서도 궁금하신 분들이 계실 겁니다. 함수로 빼지 않고 login 함수에 그대로 적어도 될 것을, 굳이 나눈 이유는 뭘까? 그 이유는, 다음 포스트에 적을 로그인 적용과 관련되어 있습니다.

다음 포스트로는 로그인 적용과 글 작성을 가지고 오겠습니다.