728x90

VueJS에서 form의 입력값의 유효성을 검증하는 방법에 대하여 알아보자.

library는 VeeValidate를 사용한다.

VeeValidate는 기존의 데이터입력 => rules.js 생성 => rule 생성 => validation => error, success, warning 을 모두 한번에 처리할 수 있다.

Setup

npm i vee-validate@next --save
or
yarn add vee-validate@next

Vue3 에서 VeeValidate를 사용할 수 있는 방법으로는 2가지가 있다.

Field Component Composition API
Simplest approach More control
Hard to customize / Only simple UI components Intuitive API
  Easy to use custom components

기본적인 사용법

import {useField,useForm} from 'vee-validate';

export default {
  setup(){
    function onSubmit(){
      alert('submit');
    }
    
    const validations = {
      email: value => {
        if(!value) return 'This field is required'; //error message
      
        const regex = /.../;
        if(!regex.test(String(value).toLowerCase())){
          return 'Please enter a valid email address';
        }
      
        return true;
      },
      password: value => {
        if(!value) return 'This field is required'; //error message
      
        return true;
      }
    }
    
    useForm({
      validationSchemal: validations
    })
    
    const email = useField('email');
    const password = useField('password');
    
//    useField만 사용할때
//    const email = useField('email',function(value){ // true 면 value 반환, false면 errorMessage 반환
//      if(!value) return 'This field is required'; //error message
//      
//      const regex = /.../;
//      if(!regex.test(String(value).toLowerCase())){
//        return 'Please enter a valid email address';
//      }
//      
//     return true;
//    })
    
    return{
      onSubmit,
      email: email.value,
      emailError: email.errorMessage,
      password: password.value,
      passwordError: password.errorMessage,
    }
  }
}
  • useField: single field validation
  • useForm: validating at form level

Advanced 사용법

<template>
  <div>
    <BaseCheckbox
      label="Catering"
      v-model="catering"
      :error="cateringError"
     />
     ...
  </div>
<template>

<script>
import {useField, useForm} from 'vee-validate'
export default{
setup(){
  const required = value => {
    const requiredMessage = 'This field is required';
    if(value === undefined || value===null) return requiredMessage;
    if(!String(value).length) return requiredMessage;
    
    return true;
  }
  
  const minLength = (number,value)=>{
    if(String(value).length < number ) return 'Please type at leat ' + number;
  }
  
  const anything = ()=> true;
  
  const validationSchema = {
    category:required,
    title: value => {
      const req = required(value);
      if(req!==true) return req;
      const min = minLength(3,value);
      if(min!==true) return min;
      return true;
    },
    description: required,
    location: undefined, // anything과 같은 효과
    pets: anything,
    catering: anything,
    music: anything
  }
  
  useForm({
    validationSchema
  });
  
  const {value: category, errorMessage: categoryError} = useField('category');
  const {value: title, errorMessage: titleError} = useField('title');
  const {value: description, errorMessage: descriptionError} = useField('description');
  const {value: location, errorMessage: descriptionError} = useField('location');
  const {value: pets, errorMessage: petsError} = useField('pets',undefined,{initialValue:1});
  const {value: catering, errorMessage: cateringError} = useField('catering',undefined, {initialValue:false});
  const {value: music, errorMessage: musicError} = useField('music',undefined,{initialValue:false});
  
  return{
    category,
    cateoryError,
    title,
    titleError,
    description,
    descriptionError,
    location,
    locationError,
    pets,
    petsError,
    catering,
    cateringError,
    music,
    musicError,
  }
  
}
}
</script>

위의 validation은 개별적으로 erorrMessage는 보여주지만 submit을 막지는 못한다.

 => 하나의 field라도 에러가 있다면 막아주는 함수가 바로 handleSubmit이다. 사용법은 다음과 같다.

 

또한 작업또한 굉장히 반복적이고 변수선언도 너무 많아진다.

 => useForm의 기능 (errors, initialValues) 로 해결할 수 있다.

 

 

<template>
  <form @submit="submit">
    <h1> Create an Event </h1>
    <BaseSelect
      label="Select a category"
      :options="categories"
      v-model="category"
      :error="errors.category"
    />
    ...
  </form>
</template>
<script>
import {useField, useForm} from 'vee-validate'
export default{
  setup(){
    ...
    const {handleSubmit, errors} = useForm({
      validationSchema,
      initialValues:{
        pets:1,
        catering:false,
        music:false
      }
    });
    const {value: category} = useField('category');
    const {value: title} = useField('title');
    const {value: description} = useField('description');
    const {value: location} = useField('location');
    const {value: pets} = useField('pets');
    const {value: catering} = useField('catering');
    const {value: music} = useField('music');
    
    const submit = handleSubmit(values=>{
      console.log('submit',values);
    })
    return{
      ..., // 개별적인 errorMessage는 모두 삭제
      submit,
      errors,
    }
  }
}
</script>

yup

validation rule을 생성하는것도 굉장히 귀찮은 일중에 하나이다. 이를 해결해 줄수 있는 라이브러리가 yup이다.

npm install yup
import {useField, useForm} from 'vee-validate';
import * as yup from 'yup';
export default{
  setup(){
    const validationSchema = {
      category: yup.string().required(),
      title:yup.string().required('A cool title is required').min(3),
      description:yup.string().required(),
      location:yup.string(),
      pets: yup.number(),
      catering: yup.boolean(),
      music: yup.boolean(),
    }
    const {handleSubmit, errors} = useForm({
      validationSchema,
      initialValues:{
        pets:1,
        catering:false,
        music:false
      }
    });
    const {value: category} = useField('category');
    const {value: title} = useField('title');
    const {value: description} = useField('description');
    const {value: location} = useField('location');
    const {value: pets} = useField('pets');
    const {value: catering} = useField('catering');
    const {value: music} = useField('music');
    
    const submit = handleSubmit(values=>{
      console.log('submit',values);
    })
    return{
      ..., // 개별적인 errorMessage는 모두 삭제
      submit,
      errors,
    }
  }
}

Lazy Validation

input 이 focus를 잃은 후 입력값의 validation을 체크하는 것이 옳다. 이를 위해 validation은 change event에 trigger 되어야한다.

<template>
  <BaseInput
    label="Email"
    type="email"
    :error="emailError"
    :modelValue="email"
    @change="handleChange"
  >
</template>

<script>
  setup(){
    ...
    const {setFieldValue} = useForm({
      validationSchema: validations
    })
    
    const {value:Email, errorMessage:emailError} = useField('email');
   
    const handleChange = (event) =>{
      setFieldValue('email',event.target.value);
    }
    return{
      onSubmit,
      email,
      emailError,
      handleChange
    }
  }

</script>

'VueJS' 카테고리의 다른 글

Vue3 google SEO 등록하기  (0) 2022.04.15
Vue Authentication  (0) 2022.01.08
AWS 에 Jenkins와 Nginx 이용하여 vue project 올리기  (0) 2022.01.02
Vue 3 - 5. Props With Types  (0) 2021.12.28
Vue3 - 4.Custom type의 데이터  (0) 2021.12.28
728x90

Token-Based Authentication

JSON Web Tokens(JWT)을 이용하여 front-end auth solution을 만드는 방법을 알아보자.

사용할 모듈은 Router, Vuex, Axios가 되겠다.

 

Authentication 이 작동하는 방식

  1. Client가 Server에 Login 요청
  2. UserId와 password가 일치한다면 Server는 JWT 토큰 생성하여 Client에 반환
  3. Client는 Server로 부터 받은 JWT 토큰은 local storage에 저장
  4. 추후 Client에서 API 호출 시 HTTP request header에 localstorage에 저장된 JWT 토큰 실어서 전송
  5. Client가 로그아웃한다면 로그아웃 API 호출 및 localstorage에서 JWT 토큰 제거.

JWT 토큰 구성

  • 헤더 : Type(JWT), Hasing algorithm
  • Payload : Info we're transmitting. ex.) iss(issuer: 'verlopert.com'), exp(expiresIn: '7d', admin(admin:'false')
  • Signature : Hash of Header + Payload + Secret

VueJS Authentication 구현 과정

  1. Register users
  2. Login users
    • /register와 /login api는 userData({token,email,name})을 반환한다.
  3. Vuex
    • userData를 State에 저장한다.
    • userData를 localStorage에 저장한다.
    • token을 axios header default 설정으로 더해준다.
  4. Access protected data
  5. Logout
    • Vuex State로부터 userData를 지운다
    • localStorage로부터 userDAta를 지운다
    • axios header default 설정에 token을 지워준다

User Registration

과정

  1. RegisterUser.vue를 생성한다.
    • 유저 가입 정보(name,email,password)를 입력받는다.
    • //RegisterUser.vue
      ...
      methods:{
        register(){
          this.$store.dispatch('register',{
            name:this.name,
            email:this.email,
            password:this.password
          })
          .then(()=>{
            this.$router.push({name:'dashboard'});
          })
        }
      }
  2. Vuex
    • 유저 가입 정보를 서버로 전송한다. (/register api)
    • 반환된 userData(token,email,name)을 State와 localStorage에 저장한다.
    • Axios header에 토큰을 default 상태로 더해준다.
    • //store.js
      ...
      export default new Vuex.Store({
        state: {
          user:null
        },
        mutations:{
          SET_USER_DATA(state,userData){
            state.user = userData;
            localStorage.setItem('user',JSON.stringify(userData));
            axios.defaults.headers.common['Authorization'] = `Bearer ${userData.token}`
          }
        },
        actions:{
          register({commit}, credentials){
            return axios.post('//localhost:3000/register',credentials).then(
            ({data})=>{
              commit('SET_USER_DATA',data);
            })
          }
        }
      })

Login

과정

  1. RegisterUser.vue를 생성한다.
    • 유저 email, password를 입력받는다.
    • //LoginUser.vue
      ...
      methods:{
        login(){
          this.$store.dispatch('login',{
            email:this.email,
            password: this.password
          })
          .then(()=>{
            this.$router.push({name:'dashboard'})
          })
        }
      }
  2. Vuex 
    • 유저 가입 정보를 서버로 전송한다. (/login api)
    • 반환된 userData(token,email,name)을 State와 localStorage에 저장한다.
    • Axios header에 토큰을 default 상태로 더해준다.
    • //store.js
      ...
      export default new Vuex.Store({
        state: {
          user:null
        },
        getters:{
          loggedIn(state){
            return !!state.user;
          }
        }
        mutations:{
          ...
        },
        actions:{
          ...,
          login({commit}, credentials){
            return axios.post('//localhost:3000/login',credentaials)
            .then(({data})=>{
              commit('SET_USER_DATA',data);
            })
          }
        }
      })

Logout

  1. logout button과 logout method를 생성한다.
    • //AppNav.vue
      ...
      methods{
        logout(){
          this.$store.dispatch('logout')
        }
      }
  2. VueX
    • //store.js
      ...
      export default new Vuex.Store({
        state: {
          user:null
        },
        getters:{
          ...
        }
        mutations:{
          ...,
          CLEAR_USER_DATA(state){
            state.user = null;
            localStorage.removeItem('user');
            axios.default.headers.common['Authorization'] = null
          }
        },
        actions:{
          ...,
          logout({commit}){
            commit('CLEAR_USER_DATA')
          }
        }
      })
  3.  protected data(dashboard route) 접근 제한
    • logout 되더라도 dashboard page에 여전히 남아있다.
    • route에 Navigation Guard를 추가함으로써 해결할 수 있다.
    • //router.js
      const router = [
        ...
        {
          path:'/dashboard',
          name:'dashboard',
          component: Dashboard,
          meta: { requiresAuth: true},
        },
        ...
      ];
      
      router.beforeEach((to,from,next)=>{
        const loggedIn = localStorage.getItem('user');
        
        if(to.matched.some(record=>record.meta.requiresAuth) && !loggedIn){ // to.matched는 이동할 route와 match되는 route들
          next('/') // redirect to homepage
        }else{
          next() // direct to to
        }
      })
      
      export default router;

Error Handling

어떤 에러가 발생할 수 있을까?

  • User가 유효하지않은 id/password로 로그인 시도
  • 회원가입 시 이미 가입된 Email일때
  • 회원가입 시 password의 format이 유효하지않을때

Login Error Handling

//LoginUser.vue
...
methods:{
  login(){
    this.$store.dispatch('login',{
      email:this.email,
      password:this.password
    })
    .then(()=>{
      this.$router.push('dashboard');
    })
    .catch(err=>{
      this.error = err.response.data.error;
    })
  }
}

Register Error Handling

//RegiserUser.vue
...
methods:{
  register(){
    this.$store.dispatch('register',{
      name:this.name,
      email:this.email,
      password:this.password
    })
    .then(()=>{
      this.$router.push({name:'dashboard'});
    })
    .catch((err)=>{
      this.error = err.response.data.error
    })
  }
}

 

  • Client-side form validation 을 하고 싶다면 다음 Post에서 볼 수 있다.
  • component내에서 error handling을 유지하도록 하기위해 VueX내에서 error handling을 하지 않는다
    • 만약 여러 컴포넌트에서 사용하는 재사용 기능이라면 action내에서 state change를 위해 error handling을 해야할 것이다.
      만약 너의 에러가 컴포넌트에서 UI상 보여지는게 목적이라면 컴포넌트내에서 error handling을 해야할 것이다.

Automatic Login

페이지가 refresh 되더라도 로그인 상태를 유지하게 만들어보자.

//main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import state from './vuex/store'

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  created(){ // created lifecycle hook 생성
    const userString = localStorage.getItem('user');
    if(userString){
      const userData = JSON.parse(userString);
      this.$store.commit('SET_USER_DATA',userData);
    }
  },
  render:h => h(App)
}).$mount('#app');

web 이 생성되면 localStorage를 보고 vuex에 다시 저장한다.

 

만약 누군가가 localStorage에 임의의 token을 집어넣어서 권한이 없는데도 접근하면 어쩌지?

localStorage에 강제로 값을 집어넣었을때 이를 detect할수 있는 client-side 보안 기능을 만들어보자

//main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import state from './vuex/store'
import axios from 'axios'

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  created(){ // created lifecycle hook 생성
    const userString = localStorage.getItem('user');
    if(userString){
      const userData = JSON.parse(userString);
      this.$store.commit('SET_USER_DATA',userData);
    }
    axios.interceptors.respose.use(
      response=> response,
      error =>{
        if (error.response.status == 401){
          this.$store.dispatch('logout');
        }
        return Promise.reject(error)
      }
  },
  render:h => h(App)
}).$mount('#app');

localStorage의 token값이 잘못된 상태로 서버에 protected api를 요청하면 401에러를 받는데

401에러를 받으면 강제로 로그아웃 시켜버린다.

'VueJS' 카테고리의 다른 글

Vue3 google SEO 등록하기  (0) 2022.04.15
VueJS 3.0 form validating  (0) 2022.01.08
AWS 에 Jenkins와 Nginx 이용하여 vue project 올리기  (0) 2022.01.02
Vue 3 - 5. Props With Types  (0) 2021.12.28
Vue3 - 4.Custom type의 데이터  (0) 2021.12.28
728x90

Overview

AWS에 Jenkins와 Nginx를 이용하여 vue project를 올리는 방법에 대하여 알아보자

이 때 git repository에 project commit이 push 될때 자동으로 빌드가 이루어지도록한다 (CD)

  1. EC2에 linux (or window) 인스턴스 생성
  2. EC2 인스턴스에 Security Group 설정
  3. git webhook 설정
  4. git credential token 발급
  5. EC2에 에 Jenkins 및 daemonize 문제 해결
  6. Jenkins 설정 변경 (git credential, item 생성)
  7. EC2에 nginx 설치
  8. nginx 설정 변경(conf.d)
  9. 빌드!

1. EC2에 linux 인스턴스 생성

관련 내용은 쉽고 넷상에 많이 나오므로 링크로 생략

https://victorydntmd.tistory.com/61

 

[AWS] EC2 (1) - EC2 인스턴스 생성하기

2020.02.06 수정 1. 서비스 소개 AWS EC2는 간단하게 가상서버라고 생각할 수 있으며, 몇 가지 주요 특징은 다음과 같습니다. 클릭 몇 번 만으로 가상 컴퓨팅 환경을 제공받을 수 있음 원하는 만큼 가

victorydntmd.tistory.com

2. EC2 인스턴스에 Security Group 설정

aws console EC2 > 사용할 인스턴스 Detail로 페이지 > Security tab > secity groups 클릭

Edit inbound rules > 다음 개방 포트 추가 

 

8080은 Jenkins가 작동할 포트이다. Jenkins 설정 변경을 위해 접속할 일이 있으니 열어두도록하자. 단, 진짜 서비스의 경우 개방 범위를 0.0.0.0/0으로 하면 모든 사람이 들어올 수 있으니 이 때는 제한하도록 하자.

포트 3000이나 80 둘중 하나만 열어도 된다. 웹서버 nginx가 web application을 띄울 포트를 열도록한다. 모든 사람이 접속할 수 있어야 하므로 개방 범위를 0.0.0.0/0으로 설정하자.

 


3. github webhook 설정 및 developer authentication 생성

GitHub Webhook

  • Webhook을 사용하면 GitHub Apps 또는 OAuth Apps와 같은 integration을 빌드하거나 설정할 수 있다. 
  • 이러한 이벤트(예: commit code) 중 하나가 트리거되면 HTTP POST Payload를 webhook에 설정된 URL로 보낸다. 
  • webhook을 이용해 CI 빌드를 트리거하거나 production 서버에 배포할 수 있다.

vue project 의 repository > setting 탭 > webhook 탭

  • Payload URL : "EC2에서 Jenkins 서비스 URL"/github-webhook/
    • webhook POST request를 받을 서버의 URL 정의. jenkins의 경우 뒤에 github-webhook/ path를 명시해줘야 한다.
  • Content type: application/json
    • webhoook을 어떤 형태로 받을지에 대한 정의. 여기서는 json으로 받음
  • Just the push event
    • push event가 발생했을 시에만 webhook이 trigger되도록 함

4. git personal access token 발급

내 프로필 > settings > Developer settings 탭 > Personal access tokens 탭 > Generate new token > repo 체크

 

발급된 personal access token를 꼭 기억하도록 한다

 


git에서의 setting이 모두 끝났다. 이제 Jenkins 설치 및 설정 과정에대해 알아보자.

5. EC2에 Jenkins 및 Git 설치

Jenkins 설치

Jenkins 설치 방법은 넷상에 널려 있으니 이 또한 링크로 대체

https://bokyung.dev/2021/03/17/jenkins-install/

 

Amazon Linux2에 Jenkins 설치하기

로컬 도커환경에서 Jenkins를 테스트 하던 중에 에러가 나는 경우가 있어서 검증을 위해서 Amazon Linux2에 Jenkins를 설치 해 보았습니다. 설치한 Jenkins 환경 Amazon Linux2 AMI ID : ami-0f27d081df46f326c AMI 이름 :

bokyung.dev

만약 설치 중간에 다음의 에러가 발생한다면

$ sudo yum install jenkins
...
Error: Package: jenkins-2.319.1-1.1.noarch (jenkins)
           Requires: daemonize
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest

다음의 명령어를 실행 시킨후 jenkins를 설치한다

$ sudo amazon-linux-extras install epel -y
$ sudo yum install jenkins

Git 설치

https://gamoo12.tistory.com/205

 

[AWS] EC2 인스턴스에서 git 설치 방법

AWS EC2에서 git 설치 AWS의 EC2 인스턴스 중 Amazon Linux 2에 git을 설치하는 방법을 알아보았습니다. Yum을 이용하면 쉽게 git 설치가 가능합니다. Yum은 Yellow dog Updater, Modified의 약자로 RPM 기반의..

gamoo12.tistory.com

6. Jenkins git 연결 및 item 생성 (git credential, item 생성)

nodejs 환경 생성

시작하기 전 nodejs 환경을 만들어주기 위해 플러그인을 설치해주자.

jenkins 관리 > 플러그인 관리 > 설치 가능 탭 > nodejs 검색

설치 후 뒤늦은 캡처를 해버렸다.. 하핳..

설치가 완료되었다면 (jenkins service restart까지) jenkins 관리 > global tool configuration > NodeJs 환경 생성

git personal access token credential 저장

이전에 설정했던 github webhook을 설정하기 위해 jenkins에 git personal access token credential을 저장하도록하자.

jenkins 관리 > Manage Credentials > (global) > Add Credentials > 내용 입력

 

  • Kind:  Username with password
  • Scope : Global
  • Username : git username
  • password : 이전에 발급받은 git personal access token
  • ID : 임의로 지은 식별자

Item 생성

새로운 Item > 아이템 이름 지정 및 Freestyle project 선택 > 소스코드 관리에서 Git radio button 체크 > 다음 내용 입력

  • Repository URL : vue project git HTTPS 주소
  • Credential : 이전에 생성한 git personal access token credentail 선택

GitHub hook trigger for GITScm polling 체크 (webhook trigger) > Provide Node & npm bin/ folder to PATH 체크 > 이전에 만든 nodejs 환경 선택

 

Build Execute shell 선택 및 해당 내용 입력

asdfeeeee 는 내가 만든 Jenkins Item 이름이다. 각자의 이름에 맞게 변경토록하자.

npm run build는 만약 build 명령어가 다르다면 해당 명령어로 입력하면 된다.


7. nginx 설치

https://velog.io/@ant-now/AWS-%EC%84%9C%EB%B2%84%EC%97%90-Nginx-%EC%84%A4%EC%B9%98

 

AWS 서버에 AMI Nginx 설치

sudo yum install nginx 로 안됨. 왜냐면 아마존 리눅스에서 사용하는 엔진엑스가 따로 있기 때문sudo amazon-linux-extras install nginx1 로 설치하자.Amazon Linux 2 FAQs(https://aws.amaz

velog.io

8. nginx build file 경로 지정 및 서비스 포트 설정

$ cd /etc/nginx/conf.d
$ vim default.conf //아무 이름이나 만들어도 된다.

// default.conf에 다음내용을 입력한다.

server {
        listen 80       default_server;
        listen  [::]:80 default_server;

        root    /var/lib/jenkins/workspace/asdfeeeee/dist;
        index   index.html      index.htm;
        server_name     _;

        location/{
                try_files	$uri    $uri/   /index.html;
        }
}

//설정 완료후
$ sudo service nginx restart

 

  • 웹 페이지를 80번 포트에서 서비스 하기위해 80번 포트를 열었다.
  • root의 경로를 build 시 build 파일이 생성되는 "jenkins item 경로"/dist 로 지정했다.
  • 입력 한 conf값 저장후 nginx 웹서버를 재시작한다.

※만약 wq! 로 저장이 안된다면 현재 ec2-user linux 계정이 저장할 권한이 없는 것이다. 미리 sudo su 로 권한을 root로 변경하던지 저장할때 wq! 대신 w !sudo tee % > /dev/null 로 저장한다. 


이제 public ip로 접속하면 만들어진 화면이 나옴을 볼 수 있다.

'VueJS' 카테고리의 다른 글

VueJS 3.0 form validating  (0) 2022.01.08
Vue Authentication  (0) 2022.01.08
Vue 3 - 5. Props With Types  (0) 2021.12.28
Vue3 - 4.Custom type의 데이터  (0) 2021.12.28
Vue3 - 3.Type Fundamentals  (0) 2021.12.28
728x90
<script>
import {EventItem,PropType} from '../types' // PropType: helper method

export default defineComponent({
  props:{
    event:{
      type: Object as PropType<EventItem>, // type: EventItem 또는 type: Object as EventItem으로 쓰면 에러가 발생
                                           // Props에서는 Vue에서 지정한 방법으로 타입을 정의해야한다.
      required: true
    }
  }
}) 
</script>

 

Generic 이란?

function createList(item:number) : number[] { // number뿐만 아니라 다른 타입에서도 작동하도록 resuable하게 만들자
  const newList : number[] = [];
  newList.push(item);
  return newList;
}

위의 함수를 number뿐만아니라 string또는 Boolean과 같은 다른 타입의 리스트를 반환하도록 만들고 싶다.

Genericc은 함수에서 사용되는 변수의 type을 Dynamic하게 정의할 수 있도록 해준다.

fucntion createList<T>(item:T):T[]{
  const newList: CustomType[] = [];
  newList.push(item);
  return newList;
}

const numberList = createList<number>(123);
const stringList = createList<string>("Hello");

 

 

'VueJS' 카테고리의 다른 글

Vue Authentication  (0) 2022.01.08
AWS 에 Jenkins와 Nginx 이용하여 vue project 올리기  (0) 2022.01.02
Vue3 - 4.Custom type의 데이터  (0) 2021.12.28
Vue3 - 3.Type Fundamentals  (0) 2021.12.28
Vue3 - 2. Vue3 + Typescript  (0) 2021.12.28
728x90

VSCode 상에 VueDX를 설치하고 시작하자. vue에서 typescript editor service를 제공한다.

  • interface을 정의하는 type을 하나의 파일에서 관리토록한다.

// ./src/types.ts

export interface EventItem {
  id : number
  category : string
  title : string
  description : string
  location: string
  date: string
  time: string
  organizer: string
}
  • single component file에서 다음과 같이 불러온다. reactive data의 type 지정은 타입 단언으로 설정한다.
<script>
import {EventItem} from '../types'

export default defineComponent({
  data(){
    return{
      event: {} as EventItem // Type Assertion
    }
  }
})
</script>

'VueJS' 카테고리의 다른 글

AWS 에 Jenkins와 Nginx 이용하여 vue project 올리기  (0) 2022.01.02
Vue 3 - 5. Props With Types  (0) 2021.12.28
Vue3 - 3.Type Fundamentals  (0) 2021.12.28
Vue3 - 2. Vue3 + Typescript  (0) 2021.12.28
Vue3 - 1. Composition API  (0) 2021.12.11
728x90

Vue3-3.Type Fundamentals

Javascript의 대표적인 타입(Primitive + NonPrimitive) : String, Number, Boolean, Array, Function, Object, Null, Undefined, ...

Typescript는 Javascript보다 더 많은 타입을 가지고 있다

  • any : 모든 타입을 허용하며 type checking을 비활성화 시킨다.
  • tuple : 미리 설정된 타입의 데이터들을 가진 고정된 길이의 배열.
  • enum : 값의 집합에 친숙한 이름을 짓게 해준다.
    • enum ArrowKeys {
        Up, //1
        Down, //2
        Left=10, // 10
        Right=5, //4
      }
  • unknown, never, ... 등등이 있음
  • Object 타입 정의 방법
    • 직접 정의
    • let person : { name: string, age: number, activeAvenger: boolean, powers: string[], } = { name: 'Peter Parker', age: 20, activeAvenger: true, powers:['wall-crawl', 'spider-sense'] }

Type and Interface

type 은 데이터가 어떤 구조를 가져야 할지 정의해준다.

  • type buttonType = 'primary' | 'secondary' | 'success' | 'danger' let buttonStyles: buttonType = 'primary' // O let buttonStyles : buttonType = 'error' // X

interface object의 타입을 정의하기 위해 사용한다.

  • interface Hero {
      name : string;
      age : number;
      activeAvenger : boolean;
      powers : string[];
      universe : ComicUniverse; // type ComicUniverse = 'Marvel' | 'DC'
    }
    
    let person:Hero = {
      name: 'Peter Parker',
      age: 20,
      activeAvenger : true,
      powers: ['wall-crawl', 'spider-sense'],
      universe : 'Marvel'
    }
    Object를 제외한 데이터 타입 정의에는 Type을 쓰도록하자.

'VueJS' 카테고리의 다른 글

Vue 3 - 5. Props With Types  (0) 2021.12.28
Vue3 - 4.Custom type의 데이터  (0) 2021.12.28
Vue3 - 2. Vue3 + Typescript  (0) 2021.12.28
Vue3 - 1. Composition API  (0) 2021.12.11
vue3 - 0.주요 특징  (0) 2021.12.10
728x90

Vue3 + Typescript 프로젝트 생성 방법

  1. vue cli 설치 (버전: 4.5.8)
    • npm install -g @vue/cli
  2. Vue3 프로젝트 생성
    • vue create vue3-and-typescript
    • Babel, Typescript, 3.x, no class style 설정

기존 Vue3 프로젝트에 Typescript 설정하는 방법

vue add typescript
- vue/cli upgrade
- Babel, no class style 설정
- convert all .js file to .ts
- skip type checking to all declaration files

프로젝트 구조

  • main.ts : 기존 javascript로 구성된 main.js와 동일
  • shims-vue.d.ts : Typescript의 Helper file이다. vue 파일에서 일어나는 일들에 대해 정의가 내려져 있다. 수정 불필요

Component에 Typescript 적용하는법

<script lang="ts">
  import {defineComponent} from 'vue' // import vue helper function
  export default defineComponent{
    data(){
      return {}
    },
  }
</script>
  • defineComponent : typescript를 컴파일 할 수 있는 component를 정의해준다. single file component에 설정.

'VueJS' 카테고리의 다른 글

Vue3 - 4.Custom type의 데이터  (0) 2021.12.28
Vue3 - 3.Type Fundamentals  (0) 2021.12.28
Vue3 - 1. Composition API  (0) 2021.12.11
vue3 - 0.주요 특징  (0) 2021.12.10
array / object prototype  (0) 2021.06.27
728x90

Option API

export default{
  data(){
    return{
    
    }
  },
  methods:{
  
  },
  computed:{
  
  },
  mounted(){
  
  }
}
  • 같은 종류의 일을 한다고 하더라고 코드가 data, mothods, computed, mounted에 떨어져 있을 수 있다. 즉, 응집성이 떨어진다

Composition API

export default{
  setup(){
    //data
    
    //methods
    
    //computed
    
    //lifecycle hooks
  }
}
  • 비즈니스 로직과 데이터를 setup() 이라는 함수에서 통합 관리할 수 있다. 응집성이 올라간다.

setup

  • 복잡한 로직에서 사용. react의 custom hook와 비슷하다.
<template>
<div  class="home">
  home
  <p ref="p"> My name is {{name}} and age is {{age}} </p>
  <button @click="handleClick">click me</button>
</div>

</template>

<script>
import {ref} from 'vue';

export default{
  setup(){
    console.log('set up');
  
    const p = ref(null);
  
    let name = 'mario';
    let age = 30;
  
    const handleClick = () =>{
      p.value.classList.add('test');
      p.value.textContext = 'hello';
    }
  
    return{
      name,
      age,
      p,
      handleClick,
    }
  }
}
</script>
  • created() 보다 먼저 실행된다
  • 반환 데이터 reactive하게 변경하는 방법
    1. ref 사용. 변수를 ref를 이용하여 선언하면 reactive 해진다. 해당 변수값 참조시 setup() 내에서는 .value를 붙이고 바깥에서는 해당 변수명을 그대로 참조하면된다.
      • <template>
        <div  class="home">
          home
          <p ref="p"> My name is {{name}} and age is {{age}} </p>
          <button @click="handleClick">click me</button>
        </div>
        
        </template>
        
        <script>
        import {ref} from 'vue';
        
        export default{
          setup(){
          
            let name = ref('mario'); // ref 내부 값은 초기값을 의미한다
            let age = ref(30);
          
            const handleClick = () =>{
              name.value = 'luigi';
              age.value = 35;
            }
          
            return{
              name,
              age,
              handleClick,
            }
          }
        }
        </script>
    2. reactive사용. 변수를 reactive를 이용하여 선언하면 reactive해진다. ref와 사용법은 유사하지만 setup()내에서 .value를 이용하지 않고도 변수값을 참조할 수 있다는점, primitive type이 초기값으로 주어져서는 안된다는 점이 유일하게 다르다
      • <template>
        <div  class="home">
          home
          <p ref="p"> My name is {{ninja.name}} and age is {{ninja.age}} </p>
          <button @click="handleClick">click me</button>
        </div>
        
        </template>
        
        <script>
        import {reactive} from 'vue';
        
        export default{
          setup(){
          
            const ninja = reactive({name:'mario',age:30});
          
            const updateNinja = () =>{
              ninjaOne.age=40;
            }
          
            return{
              ninja,
              updateNinja,
            }
          }
        }
        </script>
  • 내부에서 computed를 사용할 수 있다.
    • <template>
      <div  class="home">
        <input type="text" v-model=search/>
        <ul v-for="name in names" :key="name">
          <li>{{name}}</li>
        </ul>
      </div>
      
      </template>
      
      <script>
      import {computed,ref} from 'vue';
      
      export default{
        setup(){
          
          const search = ref('');
          const names = ['mario','yoshi','luigi','toad','bowser','koopa','peach'];
          
          const filteredNames = computed(()=>{
            return names.filter((name)=>name.includes(search.value));
          })
          
          return{
            search,
            names:filteredNames
          }
        }
      }
      </script>
  • watch, watchEffect를 사용할 수 있다.
    • watch: Vue2에서 일반적으로 사용하던 watch와 동일. observe할 value를 등록후 해당 값이 변경되면 callback함수 실행
    • watchEffect: callback 함수 내부 변수의 dependency를 찾아 해당 dependency 변수값이 변경되면 callback함수가 자동으로 실행된다. watchEffect 의 반환값을 대입 받은 변수를 실행하면 watchEffect는 destroy된다.
    • // 다음은 search 값이 변경되면 자동으로 watch, watchEffect의 callback 함수를 실행하게 만든 코드이다.
      // handleClick 실행시 watch와 watchEffect는 메모리를 반환한다.
      <script>
      import {watch,watchEffect,ref} from 'vue';
      
      export default{
        setup(){
          
          const search = ref('');
          const names = ['mario','yoshi','luigi','toad','bowser','koopa','peach'];
          
          const stopWatch = watch(search,()=>{
            console.log('watch function ran');
          })
          
          const stopEffect = watchEffect(()=>{
            console.log('watchEffect function ran',search.value);
          })
          
          const handleClick = ()=>{
            stopWatch();
            stopEffect();
          }
          
          return{
            search,
          }
        }
      }
      </script>
  • props를 받을 수 있다.
    • // PostList.vue
      // posts props가 넘어온 상황
      <script>
      export default{
        props: ["posts"], //props를 선언해줘야한다.
        setup(props){
          console.log(props.posts)
        }
      }
      </script>
  • vue lifecycle을 이용할 수 있다. setup 밖의 동일한 lifecycle보다 먼저 실행된다.
    • <script>
      export default{
        setup(props){
          onMounted(()=>console.log("component Mounted"));
          onUnMounted(()=>console.log("component UnMounted"));
          onUpdated(()=>console.log("component Updated"));
        }
      }
      </script>

componentAPI에서 data fetch

setup()에 async를 선언않고 setup() 내부에 async function을 생성해 이를 호출한다.

<template>
  <div class="Home">
    <div v-if="error">{{error}}</div>
    <div v-if="posts.length">
      <PostList :posts="posts"/>
    </div>
    <div v-else>
      Loading...
    </div>
  </div>
</template>

<script>
import {ref} from 'vue'
export default{
  setup(){
    const posts= ref([]);
    const error = ref(null);
    
    const load = async() =>{
      try{
        let data = await fetch('asdf');
        if(!data.ok){
          throw new Error('no data available'
        }
        posts.value = await data.json(); 
      }catch(e){
        error.value = e.message;
        console.log(error.value);
      }
    }
  }
}
</script>

Reusable Composables

여러 컴포넌트에서 같은 로직의 setup()을 사용할 경우 코드를 반복해서 사용하는것은 비효율적이다.

다른 파일로 떼어놓고 필요할때마다 호출하여 사용할 수 있다.

//composables/getPosts.js
import {ref} from 'vue';

const getPosts = () =>{
  const posts = ref([]);
  const error = ref(null);
  
  const load = async()=>{
    try{
      let data = fetch('asdf')
      if(!data.ok){
        throw new Error('no data available');
      }
      posts.value = await data.json();
    }catch(e){
      error.value = e.message;
      console.error(error.value);
    }
  }
  //load(); //넣지않는다
  
  return {posts,error,load}
}

export default getPosts;
//Home.vue
<script>
export default{
  import getPosts from '../composables/getPosts'
  setup(){
    const {posts,error,load} = getPosts(); // 처음에 posts와 error은 비어있는 상태. 뒤에 load가 호출되면 값이 채워진다.
    load() // getPosts에 집어넣지 않는다.
    return {posts,error}
  }
}
</script>

Vue3 + Typescript 설정 방법

npm install -g @vue/cli

 

vue --version 4.5.8

 

vue create vue-3-and-typescript

 

Babel

Typescript

3.x

 

no class style component

 

eslint+prettier

 

lint on save

 

구조

main.ts : equivalent to main.js

shims-vue.d.ts : Helper file for Typescript. No need to modify it. give better definition showing what's going on vue files

tsconfig.json : configure options for ts compiler

 

add ts to existing vue 3 project

vue add typescript -> vue/cli upgrade

use class-style component syntax ? no

use babel alongside typescript? yes

convert all .js files to .ts ? yes

skip type checking of all declaration files ? yes

 

 

# creating components with Typescript

<script lang="ts">

import {defineComponent } from 'vue' : import Vue helper function

define component that works well with typescript

single file component에 설정할 것 전부이다.

 

# Type Fundamentals

Common Javascript types often encountered : String, Number, Boolean, Array, Function, Object

Typescript provides additional types to Javascript 

- Any : essentially disables any type checking

- Tuple : fixed length arrays with predefined data types

- Enum : allows you to define friendly names to a set of values

enum ArrowKeys {

  Up, // 1

  Down, //2

  Left= 0 , // 0

  Right = 5 //4

}

등등 더있음

 

Object 타입 정의방법

let person : {

  name : string

  age : number;

  activeAvenger: boolean;

  powers : string[];

} = {

  name: 'Peter Parker',

  age: 20,

  activeAvenger : true,

  powers: ['wall-crawl', 'spider-sense']

}

 

Limitaions of Predefined Types

 

The need for custom types is a common one. -> Type and Interface

 

What is Type

Type allows you to define alias how the data should be shaped.

 

type buttonType = 'primary' | 'secondary' | 'success' | 'danger'

let buttonStyles: buttonType = 'primary' // O

let buttonStyles : buttonType = 'error' // X

 

What is Interface

type for object

 

interface Hero {

  name : string;

  age : number;

  activeAvenger : boolean;

  powers : string[];

  universe : ComicUniverse; // type ComicUniverse = 'Marvel' | 'DC'

}

 

let person:Hero = {

  name: 'Peter Parker',

  age: 20,

  activeAvenger : true,

  powers: ['wall-crawl', 'spider-sense'],

  universe : 'Marvel'

}

 

Use Type except for an Object

 

# Data With Custom Types

Install VSCode extension VueDX : provide edtior service

 

src 폴더 밑에 types.ts 생성

export interface EventItem {

 id : number

 category : string

 title : string

 description : string

 location: string

 date: string

 time: string

 organizer: string

}

 

//Eventdetail.vue

import {EventItem} from '../types'

 

data(){

  event : {} as EventItem // Type Assertion

}

 

# Props With Types

 

import {EventItem, PropType} from '../types' // PropType: helper method

 

props : {

  event: {

   type: Object as PropType<EventItem>, // type : EventItem , type : Object as EventItem쓰면 에러뜬다. Props에서는 Vue에서 지정한 방법으로 타입을 지정해야한다

   required: true

  }

}

 

What is Generic?

function createList (item: number) : number[]{ // createNumberList가 아니야 reusable하게 다른 타입에서도 작동하게 만들자 => Generic

 const newList : number[] = [];

 newList.push(item);

 return newList;

}

 

const numberList = createList(123);

 

Generic은 함수에서 사용되는 변수의  type을 Dynamic하게 정의할 수 있도록 해준다

function createList<CustomType>(item: CustomType) : CustomType[]{

  const newList: CustomType[] = [];

  newList.push(item);

  return newList;

}

const numberList = createList<number>(123)

const stringList = createList<string>("Hello");

 

# Custom Methods with CustomTypes

 

1. Computed

For computed properties, focus on what type is returned

 

data(){

  return {

   events: [] as EventItem[]

  }

},

computed:{

  firstEvent(): EventItem {

   return this.events[0]

  }

}

 

2. Methods

For methods, add types for the parameters and return value if applicable

 

data(){

  return {

   events: [] as EventItem[]

  }

},

methods:{

  addEvent(newEvent: EventItem) {

   this.events.push(newEvent)

  },

  secondEvent(): EventItem{

   return this.events[1]

  }

}

 

# Composition API with Typescript

data: () => ({

  newTask : {

   label: '',

   type: 'personal',

  } as TodoItem,

  taskItems: [] as TodoItem[],

  listFilter: 'all'

}),

computed: {

  filteredTasks() : TodoItem[] {

   if(this.listFilter == 'complete'){

    return this.taskItems.filter((item:TodoItem) => item.isComplete === true)

   }else if(this.listFilter === 'incomplete'){

    return this.taskItems.filter((item.TodoItem)=>item.isComplete === false)

   }else{

     return this.taskItems

   }

  }

},

methos:{

  addTask() {

   this.taskItems.push({

     ...this.newTask,

     isComplete:false

   })

  }

}

 

Refactor this in composition api

 

setup(){

  const state = reactive({

  newTask : {

   label: '',

   type: 'personal',

  } as TodoItem,

  taskItems: [] as TodoItem[],

  listFilter: 'all'

});

 

  const filteredTasks = computed(()=> { // computed 내부의 return 타입을 보고 타입추론을 해준다

   if(state.listFilter == 'complete'){

    return state.taskItems.filter((item:TodoItem) => item.isComplete === true)

   }else if(state.listFilter === 'incomplete'){

    return state.taskItems.filter((item.TodoItem)=>item.isComplete === false)

   }else{

     return state.taskItems

   }

  })

 

  const addTask = ()=> {

   this.taskItems.push({

     ...this.newTask,

     isComplete:false

   })

  }

  return {

    ...toRefs(state),

    addTask, 

    filteredTasks

  }

 

}

'VueJS' 카테고리의 다른 글

Vue3 - 3.Type Fundamentals  (0) 2021.12.28
Vue3 - 2. Vue3 + Typescript  (0) 2021.12.28
vue3 - 0.주요 특징  (0) 2021.12.10
array / object prototype  (0) 2021.06.27
VueJs Project Architecture  (0) 2021.01.28

+ Recent posts