반응형

분명 vue-cli는 깔아져있는데, npm run serve 를 실행하면 계속 

'vue-cli-service'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다.

이런 메시지가 떠서 아무리 생각해도 이해가 되지 않았다. 하지만 전에 배울 때, package.json 에는 dependency 들이 모두 버전정보와 함께 들어있는데, 거기에 정의되어있는 dependency 의 버전이 현재 설치되어있는 vue 에 대한 버전들이 다 다르면 이렇게 인식을 못한다고 들었던 기억이 났다.

!! 그래서 git 으로 서로 형상관리를 할 때는 package.json 은 올리지 말라고 했던 기억이 난다.

npm 캐시를 지워주고 다시 npm을 설치해주면, package.json 에 의존하는 모듈을 설치해준다는 것을 찾았다.

 

npm cache clean --force    #npm 캐시 삭제
npm install    #package.json 의존하는 모듈 설치

이렇게 해준 후 npm run serve 해줬더니 서버가 실행됬다!!!!!!

반응형
반응형

참고: https://www.bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/

BackEnd : JWT 를 위한 Spring Security , Spring Mysql, Mybatis
FrontEnd: Vuex, vee-validate

 

1. JWT (JSON Web Token) 란?

현재, JWT는 인증과 정보 교환에 매우 자주 사용된다. Session (Session-based Authentication)을 새로 생성하는 것 대신에, Server 가 데이터를 JSON Web Token 으로 encoding 해준 후에 Client 에게 보내준다 (3). 그러면  Client 는 JWT를 저장하고, Client 가 요청하는 모든 Request의 루트, 리소스를 보호하기 위해 JWT가 부착(4)되어있어야한다. (보통은 header에 붙어있다) 그 요청을 받은 Server는 그 JWT를 평가(5)한 후에 Response를 리턴(6)해줄 것이다. 

https://www.bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/

Session을 기반으로 하는 인증 (Cookie 에 session을 저장해야함)과 다르게,

JWT (Token-based Quthentication)의 큰 장점은, Token 을 Client sdie 에 저장한다는거다!!! (브라우저의 Local Storage, IOS 와 Android 의Keychain).

 

JWT 의 중요한 3가지

Header,    Payload,   Signature

https://velog.io/@jkijki12/Spirng-Security-Jwt-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

JWT 가 더 궁금하시다구용? 자세한 내용은 https://minah-workmemory.tistory.com/37 에 올렸으니 참고 하실 분 참고!

 

JWT 란? (JWT vs Session)

참조 : https://www.bezkoder.com/jwt-json-web-token/ 1. Authentication 이란? 어떠한 웹사이트를 사용할 때, 계정을 생성하고, 어떠한 특정한 기능을 사용하기위해 로그인하여 접근해야한다. 그러한 action 을..

minah-workmemory.tistory.com

 

2. Spring Boot Vue.js Authentication 예제!

  • 사용자는 계정을 하나 생성하고, 이름과 비밀번호로 로그인
  • Authorization  사용자의 권한 (admin, moderator, user)

                                  로그인, 회원가입의 Flow chart                              

https://www.bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/

 

 

                                  Back-end :  Spring Boot & Spring Security                              

https://www.bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/

gradle 에 dependency 추가

// JWT 라이브러리
implementation 'io.jsonwebtoken:jjwt:0.9.1'

// spring Security 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'

JWT secret String / 유효기간 application.properties 에 설정

 

 

                                  Front-end :  Vue.js & Vuex                              

https://www.bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/

 

반응형
반응형

 

1. main.js 에 넣어 기본적인 유효성 검사 실행해보기

먼저, vue 버전에 맞는 vee-validate를 다운로드해준다. 

나는 vue 2 에서 구현할 것이기때문에, 

※ vee validate를 다운로드 했는데, vee-validate 가 작동하지 않는다면, 버전이 잘못된것이다. 그래서 본인 vue에 맞는 vee-validate를 다운로드 해야한다. 

npm install vee-validate@"<3.0.0" --save

로 다운로드 해준다. 

main.js

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import VeeValidate from "vee-validate";

Vue.use(VeeValidate);
Vue.prototype.$axios = axios;
Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

이렇게 vee-validate를 추가해준다. 그러면, 모든 component 에서 사용이 가능해진다.

MemberRegisterView.vue

<template>
  <b-container fluid>
    <h1>Sign Up</h1>
    <b-form>
		<b-form-group
        label="Email"
        label-for="email">
        <b-form-input
          type="email"
          id="email"
          placeholder="Enter email address"
          v-model="user.email"
          v-validate="'required|email'"
          name="email" />
        <span class="warningText">{{ errors.first('email') }}</span>
      </b-form-group>
      <b-button
        variant="primary"
        @click="handleRegister">
        Sign Up
      </b-button>
    </b-form>
  </b-container>
</template>

<style scoped>
h1 {
  margin-bottom: 1.5rem;
}
.warningText {
  color: crimson;
}
</style>

이렇게 input 박스에 v-validate="'required|email'" 을 넣어주고, name값에 email 유효성을 봐주고 만약, 그 email 유효성에 어긋난다면, 이메일 주소를 맞게 입력해달라는 메시지를 <span> 태그에 넣어줄 것이다

나는 span 에 class값을 주어서 빨간색으로 보이게 설정해뒀다. 그런데, 영어로 되어있으니, 한글로 바꿔보자!

vee-validate에 설정되어잇는 error message를 customize할 수 있는데, 유효성에 대한 데이터만 따로 빼고 싶어서, utils라는 폴더를 생성해 veevalidateUtils.js 파일을 따로 빼두어 main.js 에 추가해줬다.

main.js

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import VeeValidate from "vee-validate";
import '@/utils/veevalidateUtils'

Vue.use(VeeValidate);
Vue.prototype.$axios = axios;
Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

여기서, 아까 말한 이메일 형식에 어긋났을때의 메시지를 수정해 줄 것이다.

veevalidateUtils.js

import {Validator} from 'vee-validate';
import {email} from 'vee-validate/dist/rules.esm' // 기본적으로 제공되는 규칙을 모아둔 파일

// 존재하는 rule의 메세지를 바꾸기위해
Validator.extend('email', {
    ...email,
    getMessage: '이메일 형식이 아닙니다.',
})
// 새로운 rule 추가

존재하는 email 이라는 rule 의 메시지를 변경할 것인데, 여기서 ...email 은 email이라는 객체의 값을 각각 새로운 객체에 복제하는 코드이다. 참고 : https://joshua1988.github.io/es6-online-book/spread-operator.html

해주면, 

에러 메시지가 바뀐 것을 확인 할 수 있다.


2. 유효성검사에 어긋나면 다음으로 넘어가지않게 설정

참고: https://vee-validate.logaretm.com/v2/guide/components/validation-observer.html#scoped-slot-data

https://vee-validate.logaretm.com/v2/guide/events.html#disabling-events-validation

$validator 를 이용하자

veevalidateUtils.js

import {Validator} from 'vee-validate';
import {required, email} from 'vee-validate/dist/rules.esm' // 기본적으로 제공되는 규칙을 모아둔 파일

// 존재하는 rule의 메세지를 바꾸기위해
Validator.extend('required', {
    ...required,
    getMessage: '필수 입력 사항입니다.',
})
Validator.extend('email', {
    ...email,
    getMessage: '이메일 형식이 아닙니다.',
})
// 새로운 rule 추가
// 이름
const NAME_MIN = 2
const NAME_MAX = 16
const NAME_REGEX = new RegExp("^[가-힣a-zA-Z]{"+ NAME_MIN + "," + NAME_MAX +"}$")

Validator.extend('name', {
    validate(value){
        return NAME_REGEX.test(value)
    },
    getMessage: `이름은 한글과 영문 대 소문자를 사용하게요. (특수기호, 공백 사용 불가, ${NAME_MIN} ~${NAME_MAX}자)`,
})
// 비밀번호
const PW_MIN = 8
const PW_MAX = 16
const PW_REGEX = new RegExp("^(?=.*?[a-zA-Z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{" + PW_MIN + "," + PW_MAX + "}$")
Validator.extend('pw', {
    validate(value){
        return PW_REGEX.test(value);
    },
    getMessage: `비밀번호는 ${PW_MIN}~${PW_MAX}자 영문 대 소문자, 숫자, 특수문자 조합을 사용하세요.`
})

MemberRegisterView.vue

<template>
  <b-container fluid>
    <h1>Sign Up</h1>
    <b-form>
      <b-form-group
        label="Name"
        label-for="username">
        <b-form-input
          type="text"
          id="username"
          placeholder="Enter name"
          v-model="user.userName"
          v-validate="'required|name'"
          name="name" />
          <span class="warningText">{{ errors.first('name') }}</span>
      </b-form-group>

      <b-form-group
        label="Email"
        label-for="email">
        <b-form-input
          type="email"
          id="email"
          placeholder="Enter email address"
          v-model="user.email"
          v-validate="'required|email'"
          name="email" />
        <span class="warningText">{{ errors.first('email') }}</span>
      </b-form-group>
      <b-form-group
        label="Password"
        label-for="password">
        <b-form-input
          type="password"
          id="password"
          placeholder="Enter password"
          v-model="user.password"
          v-validate="'required|pw'"
          name="pw" />
          <span class="warningText">{{ errors.first('pw') }}</span>
      </b-form-group>
      <b-button
        variant="primary"
        @click.prevent="handleRegister">
        Sign Up
      </b-button>
    </b-form>
  </b-container>
</template>

<script>

export default {
  name: "MemberRegisterView",
  data() {
    return {
      user: {},
      submitted: false,
      successful: false,
      message: "",
    };
  },
  methods: {
   async handleRegister() {
      const validForm = await this.$validator.validateAll();
      console.log(this.$validator)
      console.log(validForm)
      if(!validForm) return alert('내용을 한번 더 확인해주세요')
      else{
        this.$store.dispatch("loginModule/signUp", this.user)

      }
    },
  },
};
</script>

<style scoped>
h1 {
  margin-bottom: 1.5rem;
}
.warningText {
  color: crimson;
}
</style>

script 부분에,

this.$validator 를 콘솔에 찍을걸 확인해보면,

이런식으로, ErrorBag 이라는 객체가 들어있는데 그 객체는 길이가 3인 Array 로 되어있다.

이런식으로 validate 가 false 이면, alert창이 뜬다. 

반응형
반응형

흥미로운 아이를 발견함.

loginModule.js

import router from "@/router";
import axios from "axios";

const loginModule = {
    namespaced: true,    
      actions: {
        signUp(params){
          console.log('signup action activated')
          console.log(params)
           
        },
      },
}
export default loginModule;

MemberRegisterView.vue

<template>
  <b-container fluid>
    <h1>Sign Up</h1>
    <b-form>
      <b-form-group
        label="Name"
        label-for="username">
        <b-form-input
          type="text"
          id="username"
          placeholder="Enter name"
          v-model="user.username"
          v-validate="{required : true ,min:3, max:20}" />
      </b-form-group>

      <b-form-group
        label="Email"
        label-for="email">
        <b-form-input
          type="email"
          id="email"
          placeholder="Enter email address"
          v-model="user.email"
          v-validate="{required : true , max:50}" />
      </b-form-group>
      <b-form-group
        label="Password"
        label-for="password">
        <b-form-input
          type="password"
          id="password"
          placeholder="Enter password"
        
          v-model="user.password"
          v-validate="{required : true , min: 6 ,max:40}" />
      </b-form-group>
      <b-button
        variant="primary"
        @click="handleRegister">
        Sign Up
      </b-button>
    </b-form>
  </b-container>
</template>

<script>
export default {
  name: "MemberRegisterView",
  data() {
    return {
      user: {},
    };
  },
  methods: {
    handleRegister() {
      this.$store.dispatch("loginModule/signUp", this.user)
    },
  },
};
</script>

그래서 console을 확인해보면!!!

그래서 찾아보니,

Action handlers receive a context object which exposes the same set of methods/properties on the store instance, so you can call context.commit to commit a mutation, or access the state and getters via context.state and context.getters

이렇게 되어있다.

Action 은 context 객체를 받아야하는거다.

그 context 객체 안에는 commit, dispatch, getters  등이 들어있는거다! 

그래서 바꾼거!

loginModule.js

import router from "@/router";
import axios from "axios";

const loginModule = {
    namespaced: true,    
      actions: {
        signUp(context, params){
          console.log('signup action activated')
          console.log(params)
           
        },
      },
}
export default loginModule;

이렇게 actions 에 받는아이에 context를 넣으면, 

이렇게 콘솔창에 잘 뜬다!!!

 


회원가입으로 다시 돌아가면, 

axios 를 실행한 후에 backend 에서 쿼리를 실행하다 오류가 발생했을때, actions에서 아무것도 안하고 그냥 console에 에러만 발생하더라. 그래서 위에서 배운 try catch 를 넣어봤다. (에러 처리를 위해서)

loginModule.js

import router from "@/router";
import axios from "axios";

const loginModule = {
    namespaced: true,    
      actions: {
        async signUp(context, params){
          try{
            const res = await axios.post('/api/signUp/action',params);
            if(res.data.isSuccess == true){
              alert('회원가입이 완료되었습니다.');
            }else{
              alert('화원가입을 진행중 오류가 발생했습니다. 관리자에게 문의해주세요.');
            }
          }catch{
            alert('화원가입을 진행중 오류가 발생했습니다. 관리자에게 문의해주세요.');
          }
        },
      },
}
export default loginModule;

이렇게 했더니, 

 

alert창이 잘 떳다.

그런데 이제 생각해보니, 회원가입 전에 있는 이메일주소인지를 확인을 하고 넣어야하는데...? 라는 생각이 들었다.

그래서 그과정을 넣어보도록하겠다.

java에서 미리 확인해서 보내는 과정을 넣었다. 

// 회원가입
	@PostMapping(value = "/signUp/action")
	@ResponseBody
	public HashMap<String, Object> signUpAction(@RequestBody HashMap<String, Object> paramMap){
		HashMap<String, Object> returnMap = new HashMap<>();
		System.out.println(paramMap);
		if(paramMap != null) {
			// 일단 입력한 이메일주소가 이미 등록되었는지 확인하기
			int emailDupCheck = service.getCountMember(paramMap);
			if(emailDupCheck > 0) {
				returnMap.put("isSuccess", false);
				returnMap.put("errorMsg", "emailDup");
			}else {
				int result = service.addNewMemeber(paramMap);
				if(result == 1) {
					returnMap.put("isSuccess", true);
				}else {
					returnMap.put("isSuccess", false);
				}
			}
		}
		
		return returnMap;
	}

loginModule.js

import router from "@/router";
import axios from "axios";

const loginModule = {
    namespaced: true,    
      actions: {        
        async signUp(context, params){
          try{
            const res = await axios.post('/api/signUp/action',params);
            if(res.data.isSuccess == true){
              alert('회원가입이 완료되었습니다.');
            }else{
              if(res.data.errorMsg != null && res.data.errorMsg == 'emailDup'){
                alert('이미 등록된 이메일 주소입니다. 다시 입력해주세요.');  
              }else{
                alert('화원가입을 진행중 오류가 발생했습니다. 관리자에게 문의해주세요.');
              }
            }
          }catch{
            alert('화원가입을 진행중 오류가 발생했습니다. 관리자에게 문의해주세요.');
          }
        },
      },

}
export default loginModule;

이렇게 했는데.... 말이 안되는 것 같다. 왜냐면, 일단 vue 를 쓰는건 결국엔 frontend와 backend를 완전히 구분지어서 작업하는게 가능하게 한것인데. 결국엔 backend 개발자가 front를 건드리는 느낌..... 아니면 axios는 결국에는 backend의 작업이니 이게 맞는건가.... 잘모르겟다.

좀더 깔끔하게 사용하기 위해서, loginModule.js 에서는 확실하게 Vuex에 관한 것만 넣어놓고 싶기때문에, alert처럼 에러나 action에 대한 각각의 처리는 vue에서 하는게 맞는것같다 ( 이미 만들어져 있는 프로젝트를 참고해보면 ) -> 물어보고 시도해보겟다. 왜냐면, backend 에서 error로 넘겨줘야하는데... 잘모르겟다.

 

 

반응형
반응형
Actions

Actions 는 mutations 와 비슷하다. 하지만 다른점이 있다면,

  • Instead of mutating the state, actions commit mutations.
  • Actions can contain arbitrary asynchronous operations.

그래서 Actions 에 로그인한 정보를 DB에서 비교하며 맞는지를 확인하는 비동기작업을 axios 를 이용해서 구현해보겠다.

 

여기서 전부터 의문이 생겼던 부분은, 비동기적 특성상 코드가 짜여진 순서대로 진행이 되지 않기 때문에 이를 통제하기 위해, Promise 객체의 resolve/reject 를 사용할 것이냐? async/await을 사용할 것인가이다.

참조: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

아직도 완벽하게 Promise라는 객체가 이해가 되지는 않았다. 하지만 일단 내가 이해한 바로 설명해보면,

Promise
객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

대기 중인 프로미스는 값과 함께 이행할 수도, 어떤 이유(error)로 인해 거부될 수도 있습니다. 이행이나 거부될 때, 프로미스의 then 메서드에 의해 대기열(큐)에 추가된 처리기들이 호출됩니다. 이미 이행했거나 거부된 프로미스에 처리기를 연결해도 호출되므로, 비동기 연산과 처리기 연결 사이에 경합 조건은 없습니다.

Promise.prototype.then()
 및 Promise.prototype.catch() 메서드의 반환 값은 새로운 프로미스이므로 서로 연결할 수 있습니다.

Promise.reject(reason): 주어진 사유로 거부하는 Promise 객체를 반환합니다.

Promise.resolve() : 주
어진 값으로 이행하는 Promise 객체를 반환합니다. 이때 지정한 값이 then 가능한(then 메서드를 가지는) 값인 경우, Promise.resolve()가 반환하는 프로미스는 then 메서드를 "따라가서" 자신의 최종 상태를 결정합니다. 그 외의 경우, 반환된 프로미스는 주어진 값으로 이행합니다.

※어떤 값이 프로미스인지 아닌지 알 수 없는 경우, 보통 일일히 두 경우를 나눠서 처리하는 대신 Promise.resolve()로 값을 감싸서 항상 프로미스가 되도록 만든 후 작업하는 것이 좋습니다.


참조: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise
async function

async function 선언은 AsyncFunction 객체를 반환하는 하나의 비동기 함수를 정의합니다. 비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise 를 사용하여 결과를 반환합니다. 그러나 비동기 함수를 사용하는 코드의 구문과 구조는, 표준 동기 함수를 사용하는것과 많이 비슷합니다.

async 함수의 실행을 일시 중지하고 전달 된 Promise의 해결을 기다린 다음 async 함수의 실행을 다시 시작하고 완료후 값을 반환합니다.

async/await함수의 목적사용하는 여러 promise의 동작을 동기스럽게 사용할 수 있게 하고, 어떠한 동작을 여러 promise의 그룹에서 간단하게 동작하게 하는 것이다.
promise가 구조화된 callback과 유사한 것 처럼 async/await또한 제네레이터(generator)와 프로미스(promise)를 묶는것과 유사하다.


async function foo() {
    return 1
}​

위 코드는 아래와 같습니다.

 

function foo() {
    return Promise.resolve(1)
}​


async 함수의 본문은 0개 이상의 await 문으로 분할된 것으로 생각할 수 있습니다. 첫번째 await 문을 포함하는 최상위 코드는 동기적으로 실행됩니다. 따라서 await 문이 없는 async 함수는 동기적으로 실행됩니다. async function의 반환값이 암묵적으로 Promise.resolve 로 감싸지기 때문이다. 하지만 await 문이 있다면 async 함수는 항상 비동기적으로 완료됩니다. 

참조: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction

 

요약해보면,

axios.post 는 return type이 Promise 이다.

Promise는 비동기적인 처리를 위해 사용되는 콜백함수의 에러, 예외처리의 어려움을 해결해준다.
그래서 Promise 객체는 resolve와 reject를 받아 처리한다. 
Promise 다음에는 then() 과 catch()를 사용한다 .
then() : 사용한 Promise 객체에서 인수로 전달한 resolve가 호출되면 실행된다.
catch() : 생성한 Promise 객체에서 인수로 전달한 reject가 호출되면 실행된다.

※ async & await 
- 비동기식 코드를 동기식으로 표현하여 간단하게 나타내는것.
- 코드가 장황환 Promise를 보완하기 위해 도입됨.
- Promise 객체를 반환한다.

- function 앞에 async 를 붙이면 해당 함수는 항상 Promise를 반환
- await 키워드를 만나면 Promise가 처리(settled)될 때까지 기다리고, 결과는 그 이후 반환

※ async & await  예외 처리
- try{ } catch(e){ } 를 사용함.

참조: https://hi-zini.tistory.com/entry/%EB%B9%84%EB%8F%99%EA%B8%B0%EC%A0%81-%EB%B0%A9%EC%8B%9D-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95-Callback-Promise-async-await

결국은 Promise 객체가 이렇게 할거야 라고 해주는 객체. 그래서 약속을 지켜보는데, await를 써주면, 그 약속이 지켜지는지 모고 그 다음줄이 실행된다. 

나의 코드의 문제


 

actions 안에 getters 사용해보기

loginModule.js

import router from "@/router";
import axios from "axios";

const loginModule = {
    namespaced: true,
    state: {
        isLogin: false,
        loginEmail: "",
        loginName: "",
      },
    getters:{
      getUserInfo: state => state.isLogin,
    },
      mutations: {
        updateLoginStatus(state, { loginEmail, loginName }) {
          state.isLogin = true;
          state.loginEmail = loginEmail;
          state.loginName = loginName;
          localStorage.setItem("isLogin", true);
          localStorage.setItem("userName", loginName);
        },
    
      actions: {
        
        async signIn({ commit }, { loginEmail, loginPw }){
          // httpheader 에 authrization에 저장
          const res = await axios.post('/api/login/action',{ loginEmail, loginPw });
          if(res.data.isSuccess == true){
            var userName = res.data.userInfo.NAME
            await commit("updateLoginStatus", { loginEmail, loginName:userName });
            if(this.getters['loginModule/getUserInfo'] == true){
              router.push("/user/main")
            }
          }else{
            console.log('실패');
          }
        },
      },
}
export default loginModule;

store.js

import Vue from "vue";
import Vuex from "vuex";
import loginModule from "./loginModule";

Vue.use(Vuex);

export default new Vuex.Store({
   modules:{
    loginModule,   
  }, 
});
반응형

LoginView.vue

<template>
  <b-container fluid>
    <b-form @submit.prevent="loginAction()">
      <h1>Sign In</h1>
      <b-form-group
        label="Email"
        label-for="loginEmail">
        <b-form-input
          type="text"
          id="loginEmail"
          placeholder="Enter email address"
          v-model="loginEmail" />
      </b-form-group>
      <b-form-group
        label="Password"
        label-for="loginPw">
        <b-form-input
          type="password"
          id="loginPw"
          placeholder="Enter password"
          v-model="loginPw" />
      </b-form-group>
      <b-button
                
        variant="primary"
        @click="loginAction">
        Sign in
      </b-button>
      <p>
        Don't have an account?
        <router-link to="/signup">
          Sign up here
        </router-link>
      </p>
    </b-form>
  </b-container>
</template>
<script>
export default {
  name: "LoginView",
  data() {
    return {
      loginEmail: null,
      loginPw: null,
    };
  },
  computed: {
    loginStatus() {
      return this.$store.state.loginStatus;
    },
  },
  methods: {
    loginAction() {
      this.$store.dispatch("loginModule/signIn", {
          loginEmail: this.loginEmail,
          loginPw: this.loginPw,
        });
    },
  },
};
</script>

CommonLayout.vue

<template>
  <div>
    <nav class="header bg-warning">
      <div class="nav-right">
        <p>{{ loginName }}</p>
        님
      </div>
    </nav>
  </div>
</template>

<script>
export default {
  name: "BasicLayout",
  computed: {
    loginName() {
      return localStorage.getItem("userName");
    },
  },
};
</script>

 

내가 DB에 저장해둔 email로 로그인하면, 이렇게 DB에 저장해준 해당하는 이름이 출력된다!

 

반응형
반응형

참고: https://vuex.vuejs.org/guide/state.html#getting-vuex-state-into-vue-components

참조: https://v3.vuex.vuejs.org/guide/getters.html

VuexPractice.vue

<template>
  <div>
    <b-button @click="clickAction">
      메롱
    </b-button>
  </div>
</template>

<script>
export default {
  
  methods: {
    clickAction(){
      this.$store.commit('increment')
      console.log(this.$store.state.count)
    }
  },

}
</script>

 

VuexPractice.store.js

const vuexPraticeModule ={
    state: {
        count:0
    },
    mutations: {
        increment(state){
            
            state.count++
            
        }
    }
}

export default vuexPraticeModule

 

store.js

//import router from "@/router";

import Vue from "vue";
import Vuex from "vuex";
import loginModule from "./loginModule";
import vuexPraticeModule from "./VuexPratice.store"

Vue.use(Vuex);

// store는 페이지 새로고침시 데이터가 초기화된다. -> localStorage를 이용

export default new Vuex.Store({
  modules:{
    loginModule,
    vuexPraticeModule,
  }
});

근데 console창에 undefined 가 뜬다

this.$store.state 로 찍었는데 저렇게 안에 잇는데도... 나오지않는다... 모르겟다.. 그러니 일단 넘어가보자!

 

내생각엔, module화를 함에 있어서 뭔가 내가 잘못한 부분이 있는 것 같다. 그래서 그냥 store.js 에 module화 시켰던 부분을 그냥 넣었더니 잘 작동하더라!
//import router from "@/router";

import Vue from "vue";
import Vuex from "vuex";
import loginModule from "./loginModule";


Vue.use(Vuex);

// store는 페이지 새로고침시 데이터가 초기화된다. -> localStorage를 이용

export default new Vuex.Store({
  modules:{
    loginModule,
    
  },
  // 모듈화로 뺏던 부분을 넣었다
  state:{
    count:0
  },
  mutations: {
    increment(state){
        
        state.count++
        
    }
}
});

그리고, 역시나 새로고침하면 count는 다시 시작된다

뒤에까지 모두 공부한 후에 왜 안됬는지 공부해야함!!!!

When using a module system, it requires importing the store in every component that uses store state, and also requires mocking when testing the component. 여기에 아마 키가 있지 않나 싶다. 하지만 잠시 넘어가자


전역으로 저장해서 사용해보기

main.js

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import axios from "axios";

new Vue({
  router,
  store,
  render: (h) => h(App),
  computed:{
    count(){
      return this.$store.state.count
    }
  }
}).$mount("#app");

VuexPractice.vue

<template>
  <div>
    {{ count }}
    
  </div>
</template>

<script>
export default {
 
}
</script>

하지만 App 에는 count 가 존재하지않았다.

그래서, 다시 생각해낸 방법 -> 전역으로 가지고 있는게 아닌것같으니 vuex를 전역으로 가지게해보자

main.js

import Vue from "vue";
import App from "./App.vue";
import router from "./router";

import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
  state:{
    count:0
  }
})

new Vue({
  router,
  store,
  render: (h) => h(App),
   computed:{
     count(){
      
       return this.$store.state.count
     }
   }
}).$mount("#app");

App과 Root에 모두 count가 찍혓는데.... 왜 도대체가 VuexPractice 컴포넌트에는 안찍히는거니.. 혹시 props로 주고받는거해야하니? 근데.. 공식문서에서는 됫자나!!!!!!!!!!

 

Vuex provides a mechanism to "inject" the store into all child components from the root component with the store option (enabled by Vue.use(Vuex)):

 

성공한거!!!! >>>> By providing the store option to the root instance, the store will be injected into all child components of the root and will be available on them as this.$store.

여기서 힌트를 얻엇다!!!main.js

import Vue from "vue";
import App from "./App.vue";
import store from "./store";
import Vuex from 'vuex'

Vue.use(Vuex);

const Counter = {
  template: `<div>{{count}}</div>`,
  computed:{
    count(){
      return store.state.count
    }
  }
}
new Vue({
  router,
  store,
  components: {Counter},
  template: `<div class="app"><counter></counter></div>`,
  render: (h) => h(App),
}).$mount("#app");

store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state:{
    count: 12
  }, 
});

 

VuexPractice.vue

<template>
  <div>
    {{ count }}
    <b-button>
      메롱
    </b-button>
  </div>
</template>

<script>
export default {
  computed:{
    count(){
      return this.$store.state.count
    }
  }
}
</script>

성공했다ㅏㅏㅏㅏ

아무리 Root에 있다고 하지만, 그래도 state에 접근해있는 변수를 가져오는 것이기때문에 $store에 접근해서 가져와야한다!


mapState helper

state에 너무 많은 변수들이 선언되어있을 때 쉽게 사용하기 위함이라고한다.

mapState helper which generates computed getter functions for us, saving us some keystrokes:

 


Getters
Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. Like computed properties, a getter's result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed.
Getter는 컴포넌트의 computed와 비슷한 역할을 수행한다. 즉, Vuex Store로부터 상태 값을 읽어올 때 바로 읽어오는 것이 아니라 해당 상태 값을 활용해 계산된 속성으로 읽어오게 된다. 예를 들면, 특정 배열 상태 값이 있다고 할 때 해당 상태 값의 길이를 읽어오는 Getter를 생성해 사용할 수 있다.

store.js

import Vue from "vue";
import Vuex from "vuex";
import axios from 'axios'

Vue.use(Vuex);

export default new Vuex.Store({
  state:{
    user: [
      {userName: '박민아', userRole: '팀원'},
      {userName: '김지아', userRole: '팀원'}
  ],
    isLogin: false,
  },
  getters:{
    getUserByName: (state)=>(userName)=>{
      return state.user.find(user => user.userName === userName)
    }
  },  
});

VuexPractice.vue

<template>
  <div>
    <b-input />
    <b-button @click="clickEvent">
      메롱
    </b-button>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  methods:{
    clickEvent(){
      console.log(this.$store.getters.getUserByName('박민아'))
    },
  }
}
</script>

 

잘찍혓다!

mapGetters

store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state:{
    count: 12,
    user: [
      {userName: '박민아', userRole: '팀원'},
      {userName: '김지아', userRole: '팀원'}
  ],
    isLogin: false,
  },
  getters:{
    getUserByName: (state)=>(userName)=>{
      return state.user.find(user => user.userName === userName)
    }
  },
});

VuexPractice.vue

<template>
  <div>
    <b-button @click="clickEvent">
      메롱
    </b-button>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed:{
    ...mapGetters(['getUserByName']),
  },
  methods:{
    clickEvent(){
      console.log(this.getUserByName('박민아'))
    },
  }
}
</script>

그러면 , this. 으로 getters에 있는 값에 접근이 가능하다! 

잘찍혓구만


mutations
One important rule to remember is that mutation handler functions must be synchronous. Why?

Now imagine we are debugging the app and looking at the devtool's mutation logs. For every mutation logged, the devtool will need to capture a "before" and "after" snapshot of the state. However, the asynchronous callback inside makes that impossible: the callback is not called yet when the mutation is committed, and there's no way for the devtool to know when the callback will actually be called - any state mutation performed in the callback is essentially un-trackable!

반응형

store.js

import Vue from "vue";
import Vuex from "vuex";
import axios from 'axios'


Vue.use(Vuex);

export default new Vuex.Store({
  state:{
    count: 12,
    user: {userName: null, userRole: '팀원'},
  },
  getters:{
    getUserInfo: state => state.user,
    
  },
  mutations:{
    setUserName: (state, payload) => state.user.userName = payload
  },
  actions:{
    getUserinfo({ commit }, loginEmail){
      return axios.post('/api/getUserInfo',{ userEmail:loginEmail })
      .then(function(response){
     	console.log(response)
        commit("setUserName", response.data.NAME);
      })
      .catch(function(){
        console.log('실패');
      })
    },
  }
  
});

VuexPractice.vue

<template>
  <div>
    <b-input v-model="loginEmail" />
    <b-button @click="clickEvent">
      메롱
    </b-button>
    {{ loginUserName }}님
  </div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex'

export default {
  data(){
    return{
      loginEmail: "",
      loginUserName: "",
    }
  },
  computed:{
    ...mapGetters(['getUserByName', 'getUserInfo']),
    ...mapMutations(['setIsLogin']),
  },
  methods:{
    clickEvent(){
      this.$store.dispatch("getUserinfo", this.loginEmail)
      console.log('axios 후에 user' + this.$store.state.user)
      this.loginUserName = this.getUserInfo.userName
    },

  }
}
</script>

 

 

여기서 발견한 신기한 점!!!! >>>> console을 지금 dispatch 후에 찍어두고, axios 의 then 에 찍어뒀는데!!!! 놀라운 사실, console에 찍힌 순서를 보면 먼저 dispatch 후에 찍혀야하는 그 console이 먼저 찍혔다!

이건 비동기의 문제라고 생각한다! 전에 배운것처럼 언제 뭐가 먼저 실행될지 모르는 것이기때문에, 먼저 axios가 완료되면 그 다음으로 넘어가게 해줄 수 있게 설정해줘야한다고 배웠다!

 

첫번째, 그래서 VuexPractice.vue 에서 axios 실행되는 dispatch 부분을 수정했다.

async 와 await 를 이용해서 보완해봤다!!! async/await

먼저 함수 내에서 await 을 사용하기 위해 함수앞에 async 를 넣어준다. await 을 사용하여 요청을보내면 응답받은 값을 같은 스코프 내에서 처리가 가능해져서 훨씬 보기도 편하고, 코드도 간결해지고 콜백 지옥 현상도 발생하지 않는다. 

methods:{
    async clickEvent(){
      await this.$store.dispatch("getUserinfo", this.loginEmail)
      console.log('axios 후에 user' + this.$store.state.user.userName)
      this.loginUserName = this.getUserInfo.userName
    },
  }

순서대로 console이 찍힌 것을 볼 수가 있다!

하지만, 여전히 state의 user는 setting 되어있지 않다. 그렇다는 말은 action안에서도 순서대로 실행될 수 있도록 설정해주면 바뀌는지 확인해보자!

그래서 수정한 main.js 의 actions 부분

 actions:{
    async getUserinfo({ commit }, loginEmail){
      return await axios.post('/api/getUserInfo',{ userEmail:loginEmail })
      .then(function(response){
        console.log(response)
         
        commit("setUserName", response.data.NAME);
        
      })
      .catch(function(){
        console.log('실패');
      })
    },
  }

성공!!!

떴다!!!!

 

 

 

 

반응형
반응형

TableComponent.vue 를 여러 화면에서 가져오는데, 매번 "등록" 이라는 버튼에 path가 변경되게 변수로 주고받으려고 한다.

TableComponent.vue 를 먼저 살펴보자

<template>
  <div>
    <vue-good-table
      theme="polar-bear"
      :columns="columnInfo"
      :rows="rowsInfo"
      :line-numbers="true"
      :select-options="{
        enabled: true,
      }"
    >
      <div slot="selected-row-actions">
        <button @click="deleteAction">삭제</button>
      </div>
      <div slot="table-actions">
        <router-link to="/user/projects/register">등록</router-link>
      </div>
    </vue-good-table>
  </div>
</template>

<script>
import "vue-good-table/dist/vue-good-table.css";
import { VueGoodTable } from "vue-good-table";
export default {
  name: "my-component",
  props: {
    columnInfo: {
      type: Array,
    },
    rowsInfo: {
      type: Array,
    },
  },

여기서 주목해야할 부분은 <router-lint> 부분이다!!!  이렇게 TableComponent 에 박아버리면, 다른 곳에서 테이블을 사용할 때도 저 '등록' 이라는 버튼은 저기로 갈 것이니까 우선, 변수로 어떻게 처리할까 고민하다가

생각보다 쉽더라, 일단 to 를 변수로 받을 것이니까 v-bind 를 이용해주면 된다. 약어인  :to 로 해준다. 그런 후에 부모 컴포넌트에 변수를 설정해 테이블 컴포넌트에 보내주면 된다.ProejctListView.vue 가져다가 쓸 부모 컴포넌트를 살펴보자. 

<template>
  <div>
    <TableComp
      :columnInfo="columnInfo"
      :rowsInfo="rowsInfo"
      :toUrl="toUrl"
    ></TableComp>
  </div>
</template>
<script>
import TableComp from "@/components/TableComponent.vue";
export default {
  components: {
    TableComp,
  },
  data() {
    return {
      columnInfo: [
       --- 생략
      ],
      rowsInfo: [
       --- 생략
      toUrl: "/user/projects/register",
    };
  },
};
</script>

이렇게 toUrl 이라는 변수를 선언해서 url 을 지정해주고

반응형

TableComponent.vue 에서

<template>
  <div>
    <vue-good-table
      theme="polar-bear"
      :columns="columnInfo"
      :rows="rowsInfo"
      :line-numbers="true"
      :select-options="{
        enabled: true,
      }"
    >
      <div slot="selected-row-actions">
        <button @click="deleteAction">삭제</button>
      </div>
      <div slot="table-actions">
        <router-link :to="toUrl">등록</router-link>
      </div>
    </vue-good-table>
  </div>
</template>

<script>
import "vue-good-table/dist/vue-good-table.css";
import { VueGoodTable } from "vue-good-table";
export default {
  name: "my-component",
  props: {
    columnInfo: {
      type: Array,
    },
    rowsInfo: {
      type: Array,
    },
    toUrl: { type: String },
  },
  methods: {
    deleteAction() {
      alert("삭제하시겠습니까?");
    },
  },
  components: {
    VueGoodTable,
  },
};
</script>

해주면 끄읏!

반응형
반응형

vue는 편이하게 router 에서 children 을 지정하여 특정 컴포넌트들이 동일한 레이아웃을 가져가야할때 사용할 수 있는 유용한 기능이 있다.

const routes = [
  {
    path: "/user",
    component: BasicLayout,
    children: [
      {
        path: "/user/main",
        component: DashboardView,
      },
      {
        path: "/user/projects",
        component: ProjectListView,
      },
      {
        path: "/user/projects/register",
        component: ProjectRegisterView,
      },
    ],
  },
  {
    path: "/",
    name: "login",
    component: LoginView,
  },
  {
    path: "/signup",
    component: MemberRegisterView,
  },
];

이런식으로 아래의 /user 라는 path를 가지는 모든 아이들은 BasicLayout을 가지고 있게 하려고 지정해둔것이다. 근데 여기서 내가 만난 오류는

반응형
const routes = [
  {
    path: "/user",
    component: BasicLayout,
    children: [
      {
        path: "/main",
        component: DashboardView,
      },
      {
        path: "/projects",
        component: ProjectListView,
      },
      {
        path: "/projects/register",
        component: ProjectRegisterView,
      },
    ],
  },
];

이런 식으로 했을 때, 당연히 /user/main 이 Dashboardview를 갈 줄 알았는데. 아니였다. 계속 그 컴포넌트를 가져가지 않길래, 믿져야 본전이지 라는 마음으로 맨 위와같이 /user/main 이라고 지정했더니 그제서야 DashboardView 컴포넌트를 타더라. 유의하자!

반응형

+ Recent posts