171 key, package.json scripts, skeleton ui, vue3 suspense component, element ui (is-esm), 임시저장 기능 구현, 트러블 슈팅 경험, 페이지 진입속도 해결, 프로젝트 진행하며 어려웠던 점, 앞으로 적용하고 싶은 것, 로딩 컴포넌트 제어, axios / swr
source: categories/study/vue-experiance/vue-experiance_9-99_72.md
171 key, package.json scripts, skeleton ui, vue3 suspense component, element ui (is-esm), 임시저장 기능 구현, 트러블 슈팅 경험, 페이지 진입속도 해결, 프로젝트 진행하며 어려웠던 점, 앞으로 적용하고 싶은 것, 로딩 컴포넌트 제어, axios / swr
package.json scripts 커스텀 명령어 사용시 주의
{
"name": "vue2-development-environment",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --mode developmentlocal",
"build": "vue-cli-service build",
"build-test": "NODE_ENV=production vue-cli-service build --mode developmenttest",
"build-prod": "NODE_ENV=production vue-cli-service build --mode developmentprod",
"lint": "vue-cli-service lint",
"pre-commit": "lint-staged",
"prepare": "husky install",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"lint-staged": {
"./src/**/*.{js,ts,tsx,vue}": [
"eslint --quiet --fix",
"git add"
],
"./src/**/*.{css,scss,vue}": [
"stylelint --fix",
"git add"
]
},
"dependencies": {
"@types/kakao-js-sdk": "^1.39.1",
"@types/overlayscrollbars": "^1.12.1",
"@types/vue2-datepicker": "^3.3.1",
"@vue/composition-api": "^1.4.9",
"@vueuse/core": "^8.3.1",
"axios": "^0.26.1",
"compressorjs": "^1.1.1",
"core-js": "^3.6.5",
"element-ui": "^2.15.6",
"moment": "^2.29.1",
"overlayscrollbars": "^1.13.1",
"overlayscrollbars-vue": "^0.2.2",
"swiper": "^5.4.5",
"vee-validate": "^3.4.14",
"vue": "^2.6.11",
"vue-awesome-swiper": "^4.1.1",
"vue-browser-detect-plugin": "^0.1.18",
"vue-cookie": "^1.1.4",
"vue-daum-postcode": "^0.10.0",
"vue-dompurify-html": "^2.5.0",
"vue-kakao-sdk": "^0.2.5",
"vue-router": "^3.2.0",
"vue-signature-pad": "^2.0.5",
"vue2-datepicker": "^3.10.4",
"vue2-timepicker": "^1.1.6",
"vuetify": "^2.6.0",
"vuex": "^3.4.0",
"vuex-composition-helpers": "^1.1.0",
"vuex-persistedstate": "^3.2.1"
},
"devDependencies": {
"@babel/core": "^7.17.5",
"@socheatsok78/storybook-addon-vuetify": "^0.1.9",
"@storybook/addon-actions": "^6.4.19",
"@storybook/addon-essentials": "^6.4.19",
"@storybook/addon-links": "^6.4.19",
"@storybook/vue": "^6.4.19",
"@types/lodash-es": "^4.17.6",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.15",
"@vue/cli-plugin-eslint": "~4.5.15",
"@vue/cli-plugin-router": "~4.5.15",
"@vue/cli-plugin-typescript": "~4.5.15",
"@vue/cli-plugin-vuex": "~4.5.15",
"@vue/cli-service": "~4.5.15",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"babel-loader": "^8.2.3",
"eslint": "^6.7.2",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-storybook": "^0.5.7",
"eslint-plugin-vue": "^6.2.2",
"husky": "^7.0.4",
"lint-staged": "^12.3.4",
"lodash-es": "^4.17.21",
"prettier": "^2.5.1",
"sass": "~1.32.0",
"sass-loader": "^10.0.0",
"string-replace-loader": "^3.1.0",
"stylelint": "^13.13.1",
"stylelint-config-sass-guidelines": "^9.0.1",
"stylelint-config-standard": "^24.0.0",
"typescript": "~4.1.5",
"vue-cli-plugin-vuetify": "~2.4.7",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.7.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-spritesmith": "^1.1.0"
}
}
- build 명령어를 커스텀해야될 때가 있음
- 위와 같이 NODE_ENV 변수에 production 값을 할당해야 vue-cli로 생성한 프로젝트의 웹팩이 해당 명령어를 실행했을 때 production 용으로 build해줌, 안 그러면 development 모드로 build됨
- 이 사실 인지하지 못하고 build해서 테스트 서버, 운영서버 배포했다가 페이지 로딩 속도 엄청 느려짐
key property
- key 값 줄 때, 정~말 고유값. 겹칠 가능성이 0.0000000000001도 없는 방법?
- key에 대한 개념에 대해 더 정확하게 알고가자
- 반복문을 돌릴 때 사용하는 key property
<template>
<div>
<custom-multi-file-immediately-upload-list-item
v-for="(item, i) in alreadyExistFile"
:key="Math.random() + i"
:item="item"
:index="i"
@click:deleteFile="deleteFile"
/>
</div>
</template>
- key 프로퍼티에 단순히 index 값을 주면 안되는 경우가 있다.
- 랜더링 최적화 말고도 다른 이유가 있다.
- 위 컴포넌트는 멀티파일 업로드 컴포넌트이다.
- 파일을 첨부하면 첨부한 파일들이 화면에 리스트 형태로 뜬다.
- 각 리스트에는 ‘삭제' 버튼이 있다.
- ‘삭제' 버튼을 누르면 삭제 API를 호출하고 API 응답값이 올때까지 Skeleton UI가 등장한다.
- 그리고 삭제가 완료되면 해당 리스트는 화면에서 없어진다.
- 그런데 key 프로퍼티에 단순히 Index 값을 부여하면, 위 동작이 꼬이게된다.
- 예를 들어, 3번째 항목의 삭제버튼을 클릭하면 삭제 API가 호출되면서 skeleton UI가 등장한다. 그리고 삭제가 완료되면 3번째 항목은 사라진다. 그러면서 4번째 항목이 3번째 항목이 된다. 그럼 4번째 항목에 부여됐던 key 프로퍼티의 값이 삭제된 3번째 항목의 Key값과 동일해진다. 그렇게되면 삭제된 3번째 항목과 동일하게 인식하면서 skeleton UI를 지속해서 노출하게된다.
- 이러한 현상을 방지하기위해 위와 같이 Key 값을 난수생성해서 부여했다.
-
이러한 특징으로 유추해봤을 때, 꼭 이런 상황이 아니라도 key값엔 단순한 index보단 좀 더 고유한 값을 부여하는게 맞는것 같다.
- 아직 key 프로퍼티에 대한 이해도가 낮다. 이 부분은 조금 더 리서치해야될 것 같다.
skeleton UI
- 스켈레톤 UI는 사용성을 높여주는 로딩컴포넌트의 새로운 한 방식이다.
- 예시: https://vuetifyjs.com/en/components/skeleton-loaders/#usage
- API 호출을 각 컴포넌트별로 제어해야될 때, 해당 컴포넌트의 내용이 로딩중이란 것을 이런 skeleton UI로 해결하면 아주 좋을 것 같다. 실제로 이러한 skeleton UI를 사용해서 해당 페이지의 접속자 수가 확 늘었다는 통계도 있다고한다.
Vue3 Suspense Component
- vue 3에 새로 내재된 컴포넌트
- 특정 컴포넌트에 api 호출로 인해 응답된 데이터가 뿌려진다면, api 응답이 오기 전까지 다른 fallback 컴포넌트를 띄워준다.
- vue 2에선 suspense 컴포넌트가 없어 v-if 로 분기처리해서 구현했다.
Element UI framework (feat. is-esm의 중요성)
- 이번 프로젝트 때 사용한 UI 프레임워크
- 트리쉐이킹이 안된다는 치명적인 단점이 있음
- lodash 라이브러리처럼 import {a} from ‘'; 이런식으로 가져올 수는 있지만, 라이브러리 자체가 ES6 module 문법으로 구현안되어있나봄
-
is-esm 라이브러리로 확인해본결과 element ui 프레임워크도 트리쉐이킹 안되는 것으로 확인
- 프론트엔드 개발자라면 라이브러리 / 프레임워크를 가져다 사용할 때 이런 것들을 반드시 확인하자
- 최적화 하고 안하고 성능 차이가 아주 크다.
프론트엔드로 현재 8개월 조금 넘게 일하면서 느낀점
- 일정산정에 대한 어려움
- 왜 이정도 일정이 걸리는지 프론트엔드에 대해 모르는 사람에게 납득시키기 어려움
- 백엔드는 오래걸린다하면 Ok하고 지나가나 프론트엔드한텐 왜?라는 질문을 던지는 경향이있음
- 프론트엔드가 간단하다고 생각하는 경향이 큰건지..그 이유는 잘 모르겠음
임시저장 기능 구현
-
PC: 입력 발생 후 몇 초 후에 자동으로 임시저장 API 호출
또는 브라우저 닫힐 때를 이벤트로 감지, 그때 임시저장 API 호출
참고 URL : https://developer.mozilla.org/ko/docs/Web/API/Window/beforeunload_event -
M: 모바일은 브라우저 닫히는거 감지 못함
그럼 어떻게 구현해야 효율적일까? 너무 자주 보내는것도 좋지 못할거같아서.. -
내 생각..
- 임시저장 .. 어떤 간격으로 호출할까..
- 입력값 바뀐 후 즉시, 임시저장 호출.
- 변수 임시저장 = true
- 10초 후 다시 변수 임시저장 false
- 임시저장 변수가 true이면,
- 임시저장 API 호출 안함
- 임시저장 목록 불러오기 changedTime 인가 여튼 이걸로 최신꺼 불러오기
트러블 슈팅 경험 (프로젝트 설정 관련 경험)
기존 vue 프로젝트에 vuetify 라이브러리 추가
vuetify를 추가했던 이유
- 직접 개발하기 어려운 컴포넌트 (ex. range slider component)
- 예시: https://vuetifyjs.com/en/components/range-sliders/
발생 이슈 1. 기존 클래스에 정의된 CSS를 오버라이딩하는 이슈 발생
- 기존 클래스에 정의된 css를 오버라이딩하는 이슈 발생
해결
- 웹팩 postcss-filter-rules 플러그인 사용
// vue.config.js
css: {
sourceMap: true,
loaderOptions: {
postcss: {
plugins: [
filterRules({
filter: selector => {
// const re = new RegExp(/^(html|body|\*|ul|ol|select|small)(\W|$)/, 'i')
const exception = '.v-application';
const exception1 = '.v-application--wrap';
const exception2 = '.v-main';
const exception3 = '.v-main__wrap';
const exception4 = '.v-tooltip';
// return !re.test(selector) || !selector.includes(exception)
return (
!selector.includes(exception) &&
!selector.includes(exception1) &&
!selector.includes(exception2) &&
!selector.includes(exception3) &&
selector !== exception4
);
},
keepAtRules: true,
}),
autoprefixer,
],
},
},
},
- 위와 같은 식으로 vuetify가 컴파일 될 때 css에서 위 클래스명을 갖고있는 것은 컴파일하지 않도록함
- 오버라이딩 이슈 해결
발생이슈 2. vuetify 적용 전에 다른 라이브러리로 적용한 컴포넌트가 vuetify 적용 이후 IE11에서 안 나오는 이슈 발생
- vuetify 적용 이전 시점으로 되돌려서 원인 파악
- 확실한 원인은 못찾았으나 전역 변수로 설정된 기존 라이브러리 컴포넌트와 vuetify의 특정 변수가 겹쳐 무한으로 해당 컴포넌트를 불러오는 현상으로 파악
- 해당 컴포넌트를 지역 컴포넌트로 등록 및 사용 / 해결
기존 vue 프로젝트에 storybook 추가
vue의 @ 단축어로 인한 문제 발생
- vue에선 다른 파일을 import 해올 때, 경로가 길어지는걸 방지하기 위해 @ 경로가 존재한다.
- import a from ‘@/components/a.vue' 이런식으로 적으면 여기서 @는 ./src를 뜻한다.
- 하지만 storybook이 빌드될 땐 이 @ 경로를 vue처럼 인식 못하는 문제가 발생했다.
- 이를 해결하기위해 storybook에 @경로에 대한 정보를 전달했다
// .storybook/main.js
webpackFinal: async (config) => {
// ...
config.resolve.alias = {
...config.resolve.alias,
'@': path.resolve(__dirname, '../src'),
};
return config;
}
.storybook/main.js 설정 파일에 위와 같이 alias 정보를 전달함으로써 해당 문제를 해결했다.
storybook 절대경로 인식 못하는 문제 발생
/* noto-sans-kr-100 - korean */
@font-face {
font-family: 'Noto Sans KR';
font-style: normal;
font-weight: 100;
src: url('assets/fonts/noto-sans-kr-v13-korean-100.eot'); /* IE9 Compat Modes */
src: local(''),
url('assets/fonts/noto-sans-kr-v13-korean-100.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('assets/fonts/noto-sans-kr-v13-korean-100.woff2') format('woff2'), /* Super Modern Browsers */
url('assets/fonts/noto-sans-kr-v13-korean-100.woff') format('woff'), /* Modern Browsers */
url('assets/fonts/noto-sans-kr-v13-korean-100.svg#NotoSansKR') format('svg'); /* Legacy iOS */
}
위의 assets을 ..으로 인식하게끔 해야됐다.
아래와 같이 string-replace-loader 로더를 사용해 .storybook/main.js에 assets을 ..으로 인식하게끔해 이 문제도 해결했다.
// .storybook/main.js
webpackFinal: async (config) => {
// Make whatever fine-grained changes you need
config.module.rules.push(
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
{
loader: 'string-replace-loader',
options: {
search: 'assets',
replace: '..',
flags: 'g'
}
},
'sass-loader',
],
include: path.resolve(__dirname, '../'),
},
);
// ...
return config;
}
페이지 진입속도 해결
- 페이지 진입 속도가 너무 느리다는 불만사항이 올라옴
- 원인파악 1. package.json 커스텀 명령어
- 페이지 용량이 너무 큼 / 큰 프로젝트도 아니었는데, 10mb정도?
- 원인 : package.json의 커스텀 명령어를 통해 도커가 자동 빌드 및 배포를 해주는데, 커스텀 명령어는 NODE_ENV를 development로 넘김
- 즉, 배포용인데도 개발용으로 빌드가되어 sourcemap이 추가돼고 minify가 안되어 용량이 컸던 것
- 커스텀 명령어 NODE_ENV에 production값을 할당해 해결
- 원인파악 2. tree shaking 안되는 라이브러리 제거
- lodash 라이브러리 -> lodash-es 라이브러리로 교체
- element ui 프레임워크 삭제
- 불필요한 api 호출 제거
- api 로직 재정비함으로써 api 호출 줄임
- 원인파악 1. package.json 커스텀 명령어
결과: 페이지 진입속도 개선 5~8초 -> 1~2초
프로젝트 진행하며 어려웠던 점
- 다같이 개발하는 프로젝트 - 코드 일관성 깨짐
-
자바스크립트 러프함으로 인해 a 변수에 number 타입만 들어와야되는데 어느순간 string이 들어와있고.. 이러한 문제 발생
- 이러한 점을 최소화하고자 lint 기능 강화 / git hook 기능 도입 / 타입스크립트 사용
앞으로 적용하고 싶은 것
TDD 개발을 도입하고 싶다.
jest 라이브러리를 통해 테스트 코딩을 작성하며 개발하고 싶다.
그 이유는?
테스트 코딩을 작성하기 위해선 코드 리팩토링을 수시로 해야된다고 들었다.
테스트 코딩을 작성하기 위해 어쩔 수 없이 그런 방식으로 강제한다고 들었는데, 이런 이유로 코드까지 깔끔해지면서 테스트도 미리 해볼수 있는 1석2조의 효과를 얻을 수 있을거같아 도입하고 싶다는 생각을 했다.
로딩 컴포넌트 제어
사용자 사용성을 위해 로딩 컴포넌트에 대한 고민이 제일 컸다.
제일 고민이었던 부분
로딩 컴포넌트 하나로 api 호출 / 응답 제어
api 호출 시작과 끝 지점을 정확하게 파악하기 어려움
api 호출 코드가 각 컴포넌트에 나뉘어 있었기 때문
이 api 호출 코드를 한 컴포넌트로 통일화 -> 한 컴포넌트의 데이터 처리 코드가 너무 길어진다는 문제 발생
그리고 이렇게 처리하면 api 시작점과 끝지점을 정확히 알 수 있지만, 로딩 컴포넌트가 오래 떠있다는 단점 발생
각 데이터가 뿌려지는 컴포넌트마다 로딩컴포넌트를 띄울까 하였지만, 이는 디자인적으로 그렇게 좋지 않아보였음
이런 고민하던 와중에 skeleton ui 개념에 대해 알게됨
이 기법을 써서 사용성을 끌여올린 사례도있다고 함
이 기법을 사용해 이 고민 해결
axios / swr
https://swr.vercel.app/ko
https://github.com/Kong/swrv
https://www.vuemastery.com/blog/data-fetching-and-caching-with-swr-and-vuejs/
https://swr.vercel.app/ko/docs/revalidation
https://swr.vercel.app/ko/docs/mutation
api 테스트 : https://kr.api.blizzard.com/hearthstone/cards?locale=ko_KR&access_token=USL47VBpfYviA4bdk8IfzD7diu0kWIiVir