vue-cli 대신 create-vue 로 Vite 기반 프로젝트 생성
최근 Vite가 주목 받고 성능을 인정받아 React, Vue 등등 웹 프레임워크의 번들링 도구로 기존 프로젝트를 Vite로 마이그레이션 혹은 신규 프로젝트를 Vite로 사용하는 사례들이 종종 보이곤 했는데 실제로 직접 써보니까 엄청난 속도 차이를 보여줬습니다 이젠 Vue Core 팀에서도 Vite를 권장하고 있고 vue-cli 는 더이상 업데이트가 되지 않고 새롭게 vite기반의 프로젝트를 생성할수 있는 create-vue 가 나왔습니다 ## vue-cli 업데이트 중단 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/vue-cli%20%EB%8C%80%EC%8B%A0%20vue%20create%20%EB%A1%9C%20Vite%20%EA%B8%B0%EB%B0%98%20%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%20%EC%83%9D%EC%84%B1/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-12-20%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.26.30.png) vue-cli 공식 문서를 참고하면 더이상 업데이트는 지원하지 않으며 Vite기반 프로젝트 생성을 권장한다고 합니다 친절하게 마이그레이션 가이드 까지 있는 모습입니다 [https://github.com/vuejs/create-vue](https://github.com/vuejs/create-vue) ## create vue 로 프로젝트 생성 ```bash npm create vue@3 ``` 프로젝트를 생성할 경로로 이동하여 터미널에서 위 명령어로 프로젝트를 생성 할수 있습니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/vue-cli%20%EB%8C%80%EC%8B%A0%20vue%20create%20%EB%A1%9C%20Vite%20%EA%B8%B0%EB%B0%98%20%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%20%EC%83%9D%EC%84%B1/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-12-20%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.32.54.png) 기존 vue-cli 와 마찬가지로 초기 생성시 옵션으로 typescript, vue-router, jest, cypress, eslint 모두 지원 합니다 그리고 드디어 Vuex가 아닌 **Pinia**를 세팅 해주는 모습입니다 Pinia 릴리즈 이후 vue-cli 에서 Vuex 를 Pinia로 바꿔주지 않아서 Pinia를 따로 추가해줘야 하였는데 하는 번거로움이 사라졌습니다 ## 그래서 속도차이는 ? 현재 진행 중인 프로젝트를 Webpack -> Vite로 마이그레이션 해보았는데 실행 속도 차이가 어마어마 하였습니다 기존 Webpack 기반의 프로젝트 실행시 50초가 걸리던것이 Vite로 마이그레이션 시 엄청나게 빠른 속도를 경험할수 있었습니다 또한 Vite로 Build시 빌드 시간도 근소하게 단축됨을 확인했습니다 - Vite - 실행시간 0.25초 (콜드 스타트시 4~5초) - 빌드시간 45초 - Webpack - 실행시간 50초 - 빌드시간 55초 개발환경 에서 중요한 HMR(Hot Module Replacement) 또한 Vite도 잘 작동하며 기존 모듈들도 이상 없이 작동 하였습니다 추후 Vite에 관한 글과 Webpack -> Vite 로 마이그레이션 하였던 내용을 바탕으로 글을 작성하겠습니다

create-vue vue-cli

[Vue3] v-for 렌더링 최적화의 대한 고찰
최근 프로젝트의 규모가 커지고 대용량 데이터를 처리해야 하는 페이지를 만들면서 렌더링 최적화 도중 특이점을 찾았습니다 객체 배열 형태의 데이터를 v-for 를 사용해 리스트형식 으로 데이터를 뿌려주게 되는데 분명 변화가 없는 컴포넌트 여도 **리-렌더링**이 일어났습니다 겪은 문제와 리스트 형태의 컴포넌트의 렌더링 최적화 방법을 공유합니다 ## 개발 환경 - Vue 3.2.45 - Nodejs 16.17.0 ## 리-렌더링 문제 발생 코드 ```html // App.vue <template> <div class="container"> <TestItem v-for="(user,userIndex) in users" :key="user.name" :name="user.name" :age="user.age" @click="user.age += 1" /> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import TestItem from './components/TestItem.vue' const users = ref([ {name:'joon', age:20}, {name:'joon2', age:30}, {name:'joon3', age:40}, {name:'joon4', age:50}, {name:'joon5', age:60}, ]) </script> ``` ```html // TestItem.vue <template> <div class="item-container"> <h1>{{ name }}</h1> <h2>{{age}}</h2> </div> </template> <script setup lang="ts"> import { ref } from 'vue' defineProps<{ name: string, age: number }>(); </script> ``` 실무에서 흔히 볼수있는 객체 배열 형태의 데이터가 있고 반응형 변수에 담겨 있으며 v-for을 이용해 여러 아이템을 출력해주고 있습니다 자식 컴포넌트 에서는 name,age props를 전달 받아 템플릿에 데이터를 바인딩 하는 구조입니다 컴포넌트 클릭 시 해당 유저의 age를 증가시키는 클릭 이벤트를 만들었습니다 Vue의 렌더링 메커니즘에 의하면 변경이 일어난 부분만 리-렌더링이 이루어 질것으로 예상 되는데 아래의 사진처럼 v-for을 이용해 출력해주는 모든 컴포넌트가 리-렌더링 됩니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/%5BVue3%5D%20v-for%20%EB%A0%8C%EB%8D%94%EB%A7%81%20%EC%B5%9C%EC%A0%81%ED%99%94%EC%9D%98%20%EB%8C%80%ED%95%9C%20%EA%B3%A0%EC%B0%B0/v-for%EB%A6%AC-%EB%A0%8C%EB%8D%94%EB%A7%81.gif) > Vue Devtool 를 통해 리-렌더링 컴포넌트를 확인 할 수 있습니다 ## 무엇이 문제 인가? 개발자의 의도와는 다르게 변화가 일어난 컴포넌트 뿐만 아니라 모든 컴포넌트가 리-렌더링 되고 있는 모습입니다 Vue의 버그라고 생각하여 Github 이슈에 버그 리포트를 작성하였고 Vue Core 팀원에게 아래와 같은 **답변**을 받았습니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/%5BVue3%5D%20v-for%20%EB%A0%8C%EB%8D%94%EB%A7%81%20%EC%B5%9C%EC%A0%81%ED%99%94%EC%9D%98%20%EB%8C%80%ED%95%9C%20%EA%B3%A0%EC%B0%B0/image.png) 사실 Vue core 에 이해가 완벽히 되지 않았을 뿐더러 지금 당장은 답변이 이해되지 않았습니다 추후 **개선/수정** 이 이루어 질 가능성이 조금이라도 생겼다는 것에 조금 위안이 되는 답변 이였습니다 구글 번역기로 번역된 글이며 자세한 답변은 아래 링크를 통해 확인할 수 있습니다 [https://github.com/vuejs/core/issues/7291](https://github.com/vuejs/core/issues/7291) ## 해결방법 이러한 경우에 렌더링 최적화를 어떻게 해야할지 고민하였고 찾은 방법에 대해 공유합니다 ### v-memo 사용 답변에 의하면 이러한 경우에는 v-memo 사용을 권장 받았습니다 개인적으로 v-memo를 사용하지 않고 렌더링 최적화가 가능한 부분이기에 최선의 방법은 아닌것 처럼 느껴졌습니다 사용 방법은 [이전 글 ](https://joonlog.vercel.app/posts/vue3-vmemo) 을 참고 하시면 됩니다 ### 자식 컴포넌트에서 Click 이벤트 실행 및 props에 객체 주입 ```html // App.vue <template> <div class="container"> <TestItem v-for="user in users" :key="user.name" :user="user" :userAgeUp="userAgeUp " /> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import TestItem from './components/TestItem.vue' const users = ref<User[]>([ {name:'joon', age:20}, {name:'joon2', age:30}, {name:'joon3', age:40}, {name:'joon4', age:50}, {name:'joon5', age:60}, ]) const userAgeUp = (user:User) => { user.age += 1; } </script> ``` 위의 코드와 같이 부모 컴포넌트에서 props로 원시값이 아닌 객체 를 넘겨주게 되면 상위 부모인 App 컴포넌트 조차 렌더링 되지 않고 변화가 일어난 컴포넌트만 리-렌더링 이 됩니다 그러나 또 하나 중요한 점은 TestItem 컴포넌트에 직접 @click 이벤트를 주게 되었을 때 mutation 함수에서 변화를 일으키는 객체 혹은 객체 내부의 값을 참조하고 있다면 push.pop 등 **배열**에 변화를 일으키게 되면 모든 컴포넌트가 렌더링 됨으로 자식에게 mutation 함수를 props로 전달해서 자식 컴포넌트 내부에서 @click 이벤트 처리해야 합니다 ```html // Item.vue <template> <div class="item-container" @click="userAgeUp(user)" > <h1>{{ user.name }}</h1> <h2>{{ user.age }}</h2> </div> </template> <script setup lang="ts"> interface User { name: string, age: number, } defineProps<{ user: User, userAgeUp:(user: User) => void }>(); ``` 자식 컴포넌트 에서 props를 내려 받아 템플릿에 바인딩 합니다 @click 이벤트는 자식 컴포넌트 내부 에서 처리합니다 >위 예시 코드는 컨테이너 컴포넌트, 프레젠테이션 컴포넌트 패턴 기준으로 작성되었으며 > Vue 3.2.45 기준으로 작성되었으며 이후 버전 업데이트로 인해 변경될 수 있습니다 ## 문제 해결 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/%5BVue3%5D%20v-for%20%EB%A0%8C%EB%8D%94%EB%A7%81%20%EC%B5%9C%EC%A0%81%ED%99%94%EC%9D%98%20%EB%8C%80%ED%95%9C%20%EA%B3%A0%EC%B0%B0/v-for%EB%A6%AC-%EB%A0%8C%EB%8D%94%EB%A7%81_%EA%B0%9C%EC%84%A0.gif) ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/%5BVue3%5D%20v-for%20%EB%A0%8C%EB%8D%94%EB%A7%81%20%EC%B5%9C%EC%A0%81%ED%99%94%EC%9D%98%20%EB%8C%80%ED%95%9C%20%EA%B3%A0%EC%B0%B0/v-for%EB%A6%AC-%EB%A0%8C%EB%8D%94%EB%A7%81-push-pop.gif) 추후 패치를 통해 이러한 방법으로 구조를 가지지 않더라도 렌더링 최적화가 될 가능성은 있으나 현재까지는 이러한 방법으로 렌더링 최적화를 해야 할 것 같습니다 이러한 방법을 통해 프로젝트의 렌더링 최적화를 성공하였고 성능 향상을 크게 체감할 수 있었습니다 아직도 Vue에 대해 알아가야 할 내용들이 많은 것을 느꼈고 더 많이 생각하고 코드를 작성해야 할 것 같습니다

Vue3v-for렌더링

Vue3 v-memo 를 통한 렌더링 최적화
Vue 3.2+ 버전부터 **v-memo** 라는 directive가 추가 되었는데 컴포넌트를 조건부로 렌더링 할 수 있습니다 해당 기능을 통해 렌더링 최적화를 할 수 있습니다 일반적인 사용법은 다음과 같습니다 ## 간단한 사용 예시 ```html <template> <ListItem v-memo="[valueA, valueB]" :mutatingState="mutatingState" /> </template> <script> import ListItem from './components/ListItem.vue'; const valueA = ref(1); const valueB = ref(100); const mutatingState = ref('나는 변하는 값'); </script> ``` Vue는 하위 컴포넌트 에게 전달해주는 props의 값이 변경될 때 마다 재 렌더링이 일어나게 되는데 v-memo 를 적용할 컴포넌트 안에 의존성 배열 안에 값들을 넣어주게 되면 해당 값(valueA, valueB) 이 변하지 않는 이상 렌더링을 건너뛰게 됩니다 즉 위의 코드에서 ListItem 컴포넌트 에게 props로 넘겨주는 **mutatingState** 의 값이 변경되어도 ListItem 컴포넌트는 재 렌더링이 일어나지 않습니다 ## 어느 상황에서 사용하면 효과적일까? 일반적으로 v-for 를 통해 반복적으로 컴포넌트 를 렌더링 하게 될 때 큰 효과를 볼 수 있습니다 자세하게 어떤 상황에 자주 쓰이게 되는지 간단한 예시를 통해 알아보겠습니다 ```html <template> <ListItem v-for="num in arr" :key="num" :class="{selected: selectedNum === num}" :num="num" @click="setSelectedNum(num)" /> </template> <script setup lang="ts"> import { ref } from 'vue'; import ListItem from './components/ListItem.vue'; const arr = ref(Array(100).fill(0).map((_, index) => index)); const selectedNum = ref<number>(1); const setSelectedNum = (value: number) => { selected.value = value; }; </script> <style> .selected { background-color:rgb(146, 76, 187); } </style> ``` 예시로 0부터 99까지 담겨있는 배열을 생성해 v-for을 통해 컴포넌트를 100개 렌더링 하였습니다 class binding을 통해 선택된 컴포넌트는 selected 라는 class명이 붙게 되게끔 하였습니다 click 이벤트를 통해 컴포넌트를 클릭 할때마다 **selectedNum** 의 상태값이 변경됩니다 ListItem 컴포넌트는 selectedNum의 값이 변경될 때마다 재 렌더링이 일어납니다 즉 100개의 컴포넌트가 selectedNum 라는 상태값이 변경될 때마다 재 렌더링이 일어나게 되는데 실제로 변경이 필요한 컴포넌트는 기존에 선택된 컴포넌트, 새롭게 선택된 컴포넌트 단 2개의 컴포넌트인데 전체가 재 렌더링이 일어나게 됩니다 비효율적인 재 렌더링이 일어나고 있는데 이것을 방지할수 있게 하는게 **V-memo** 입니다 ## v-memo 적용하기 ```html <template> <ListItem v-for="num in arr" :key="num" v-memo="[selectedNum === num]" :class="{selected: selectedNum === num}" :num="num" @click="setSelectedNum(num)" /> </template> ``` 템플릿 단 에서 v-memo 의존성 배열에 값을 넣어주게 되면 **selectedNum === num** 값이 변하지 않으면 재 렌더링이 일어나지 않습니다 기존에 선택된 컴포넌트 는 selectedNum 과 num 의 값이 같으니 true -> false 로 값이 변하고 새롭게 선택된 컴포넌트 는 true -> false 로 v-memo의 의존성 값이 변경되고 나머지 컴포넌트는 동일하게 false 이기 때문에 업데이트가 이루어지지 않습니다 ### vue devtool 을 사용해 어떤 차이가 있는지 살펴보겠습니다 ## v-memo 적용 전 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3+v-memo+%EB%A5%BC+%ED%86%B5%ED%95%9C+%EB%A0%8C%EB%8D%94%EB%A7%81+%EC%B5%9C%EC%A0%81%ED%99%94/v-memo%EC%A0%81%EC%9A%A9%EC%A0%84.gif) v-memo를 적용하기 전에는 **selectedNum** 값이 변경됨에 따라 모든 컴포넌트가 재 렌더링이 일어납니다 ## v-memo 적용 후 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3+v-memo+%EB%A5%BC+%ED%86%B5%ED%95%9C+%EB%A0%8C%EB%8D%94%EB%A7%81+%EC%B5%9C%EC%A0%81%ED%99%94/v-memo%EC%A0%81%EC%9A%A9%ED%9B%84.gif) v-memo를 적용하였더니 v-memo의 의존성 값이 변경되지 않은 컴포넌트는 재 렌더링을 건너뛰는 모습입니다 ## 그래서 성능은 어느정도 차이가 나는데? ### v-memo 적용 전 재렌더링 시간 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3%20v-memo%20%EB%A5%BC%20%ED%86%B5%ED%95%9C%20%EB%A0%8C%EB%8D%94%EB%A7%81%20%EC%B5%9C%EC%A0%81%ED%99%94/v-memo%EC%A0%81%EC%9A%A9%EC%A0%84.PNG) ### v-memo 적용 후 재렌더링 시간 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3%20v-memo%20%EB%A5%BC%20%ED%86%B5%ED%95%9C%20%EB%A0%8C%EB%8D%94%EB%A7%81%20%EC%B5%9C%EC%A0%81%ED%99%94/v-memo%EC%A0%81%EC%9A%A9%ED%9B%84.PNG) - v-memo 적용 전 **27ms** - v-memo 적용 후 **0.7ms** **약 38배** 의 시간차이가 걸렸습니다 지금은 간단한 div 태그지만 내용이 크고 이미지가 들어가 있는 컴포넌트 라면 큰 성능 차이를 체감 할수 있습니다 실무에서 프로젝트가 거대해지고 성능 이슈를 겪을 때 v-for를 사용한 컴포넌트를 최적화 하여 큰 성능 향상을 체감하였습니다 상황에 따라서 필요한 상황에 적절하게 사용한다면 크게 도움이 될 것 같습니다

Vue3v-memo

Vue3 Script setup 에서 defineProps import 생략하기
Vue3 script setup 에서는 더 이상 defineProps() & defineEmits() 의 import 가 필요하지 않습니다 ## 사용 환경 - Script setup ## 예시 ```html <script setup> const props = defineProps({ foo: String }) const emit = defineEmits(['change', 'delete']) // setup code </script> ``` 그저 함수를 사용하기만 하면 됩니다 ! Vue가 점점 더 편해지고 있는 것 같습니다 ## Eslint 에러 해결 ```js env: { 'vue/setup-compiler-macros': true, }, ``` import 하지 않고 defineProps() & defineEmits() 를 사용하게 되면 Eslint 에서 정의되지 않은 함수라고 에러를 잡게 되는데 .eslintrc.js 에 해당 코드를 추가하면 됩니다

Vue3defineProps

Vue3 props 자동완성 카멜케이스 적용 및 .value 자동 with Volar
Vue3 프로젝트에서 자식 컴포넌트에 넘겨주는 props는 기본적으로 케밥케이스를 지원하는데 사내에서 정한 코딩 컨벤션은 props 이름을 카멜케이스 로 사용하기로 하였고 Vscode 자동완성 에도 더이상 케밥케이스가 아닌 카멜케이스가 자동완성 되어야 하였습니다 다행히 친절하게도 Volar 에서 이를 지원하고 있었습니다 또한 Volar 에서 ref 변수에 접근할 때 자동으로 .value 를 붙여주는 아주 편리한 기능도 지원하고 있었습니다 적용 방법은 아주 쉽습니다 ## 사용 환경 - Script setup - Typescript - Composition API ## 적용 방법 ```json //setting.json "volar.autoCompleteRefs": true, // ref 변수 접근시 .value 자동 입력 "volar.completion.preferredAttrNameCase": "camel" // props 카멜케이스 적용 ``` Vscode 내 setting.json 에 해당 코드를 추가 하고 Vscode를 다시 시작하게 되면 이제부터 defineProps로 정의한 props는 카멜케이스로 자동완성 됩니다 > setting.json 접근 방법은 windows 기준 컨트롤+쉬프트+p 를 누른뒤 open user settings 입력하여 진입 가능합니다 ## camelCase 적용 전 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3%20props%20%EC%9E%90%EB%8F%99%EC%99%84%EC%84%B1%20%EC%B9%B4%EB%A9%9C%EC%BC%80%EC%9D%B4%EC%8A%A4%20%EC%A0%81%EC%9A%A9%20%EB%B0%8F%20.value%20%EC%9E%90%EB%8F%99%20with%20Volar/before.PNG) ## camelCase 적용 후 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3%20props%20%EC%9E%90%EB%8F%99%EC%99%84%EC%84%B1%20%EC%B9%B4%EB%A9%9C%EC%BC%80%EC%9D%B4%EC%8A%A4%20%EC%A0%81%EC%9A%A9%20%EB%B0%8F%20.value%20%EC%9E%90%EB%8F%99%20with%20Volar/after.PNG) ## volar.autoCompleteRefs 적용 후 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3%20props%20%EC%9E%90%EB%8F%99%EC%99%84%EC%84%B1%20%EC%B9%B4%EB%A9%9C%EC%BC%80%EC%9D%B4%EC%8A%A4%20%EC%A0%81%EC%9A%A9%20%EB%B0%8F%20.value%20%EC%9E%90%EB%8F%99%20with%20Volar/autoRef.gif) test 변수에 접근 할 때마다 .value 가 자동으로 입력되는게 보이시나요 ? 저 같은 경우는 가끔 ref 변수에 접근할때 .value 를 빼 먹는 실수를 하곤 하는데 이를 방지해주는 아주 편리한 기능입니다

Vue3camelCaseVolar

vue-cli 를 통한 Vue3 Typescript, Eslint 개발환경 셋팅
Vue3도 이제는 Typescript를 완전히 지원 합니다 약간의 사소한 버그(?)들이 남아있지만 그럼에도 불구하고 사용하면 너무나도 많은 장점들이 있습니다 vue-cli 를 통해 Vue 프로젝트를 쉽게 생성하고 개발환경 까지 쉽게 구축하는 방법을 글로 남깁니다 ## 개발환경 - vue-cli 5.0.4 - Visual Studio Code ## @vue-cli 설치 ```bash npm i -g @vue-cli ``` Vue3 프로젝트를 생성하기 위해 npm 글로벌 환경에 @vue-cli 를 설치해줍니다 ## Vue 프로젝트 생성 ```bash vue create projectName ``` 터미널을 열고 프로젝트를 생성할 경로에 이동해서 위의 명령어를 입력합니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3%20Typescript,%20Eslint%20%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%20%EC%85%8B%ED%8C%85/1.PNG) 프로젝트에 타입스크립트 설정을 추가하기 위해 Manually select features 를 선택합니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3%20Typescript,%20Eslint%20%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%20%EC%85%8B%ED%8C%85/2.PNG) 프로젝트에 필요한 설정과 Typescript 를 체크한 뒤 Enter를 눌러 설정을 완료합니다 옵션 선택은 스페이스바 또는 a 를 눌러 체크할 수 있습니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Vue3%20Typescript,%20Eslint%20%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%20%EC%85%8B%ED%8C%85/3.PNG) 뒤에 추가되는 설정에서 위와 같이 Eslint 까지 설정을 완료하면 vue 프로젝트가 생성됩니다 위와 같은 옵션을 선택하였다면 프로젝트 내 **.eslintrc.js, tsconfig.json** 가 생성되어 있습니다 ## Vscode 필수 익스텐션 설치 ### Volar 설치 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/vue-cli%20%EB%A5%BC%20%ED%86%B5%ED%95%9C%20Vue3%20Typescript,%20Eslint%20%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%20%EC%85%8B%ED%8C%85/volar.PNG) Vue3 부터 Vetur 가 아닌 반드시 Volar 를 사용하여야 합니다 ### Eslint 설치 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/vue-cli%20%EB%A5%BC%20%ED%86%B5%ED%95%9C%20Vue3%20Typescript,%20Eslint%20%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%20%EC%85%8B%ED%8C%85/eslint.PNG) Eslint 포맷팅을 위해 설치해줍니다 ## Vscode Setting json 설정 ```json // setting.json "[vue]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint", "editor.codeActionsOnSave": { "source.fixAll.eslint": true } }, "[typescript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint", "editor.codeActionsOnSave": { "source.fixAll.eslint": true } }, ``` Setting.json 접근 방법은 Windows 기준 컨트롤+쉬프트+p 를 누른뒤 open user settings 입력하여 진입 가능합니다 Prettier 와 코드 포맷팅이 충돌나지 않게 코드 포맷팅을 Eslint 로 설정합니다 ## 타입 추론 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/vue-cli%20%EB%A5%BC%20%ED%86%B5%ED%95%9C%20Vue3%20Typescript,%20Eslint%20%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%20%EC%85%8B%ED%8C%85/autocomplate.PNG) 임의의 객체를 만들어서 template 에 바인딩 하여 프로퍼티 접근시 타입을 추론하여 프로퍼티를 자동완성 합니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/vue-cli%20%EB%A5%BC%20%ED%86%B5%ED%95%9C%20Vue3%20Typescript,%20Eslint%20%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%20%EC%85%8B%ED%8C%85/typeError.PNG) 함수 또한 마찬가지로 인수에 설정한 타입이 아닌 다른 타입의 값을 넣을 시 Vscode 에서 문제를 알려주게 됩니다 ## defineProps ```ts interface Props { name:string, age?:number, fn:(str:string) => void, } defineProps<Props>(); ``` Vue 에서 타입스크립트 를 사용할 때 defineProps 함수의 제네릭 인수에 선언한 타입스크립트 Interface 를 넣어줍니다 동일하게 props 를 Template 영역에 바인딩 하여 사용할 때 타입 추론이 잘 이루어집니다 > 현재 Vue 3.2 버전에서는 타입스크립트 interface를 외부 파일에서 import 해서 defineProps 제네릭 인수로 직접 사용할 시 인식이 되지 않습니다 ## 타입스크립트 는 이제 필수? 프로젝트 규모가 커지고 여러 인원들이 협업을 하게 되면 코드 양이 상당히 많아지게 되는데 런타임 이전 컴파일 단계에서 흔히 저지를 수 있는 휴먼 에러를 상당히 많이 잡아줍니다 서버에서 API 호출을 통해 받아온 데이터를 미리 interface를 선언해주고 자동완성 의 지원을 받을수 있는 점 또한 매우 매력적입니다 각각 타입에 맞는 내장메소드 자동완성 등등.. 이제는 프론트엔드 영역에서 Typescript는 선택이 아닌 필수가 되어버린 것 같습니다

Vue3TypescriptEslint

Vue3 Composables 사용하기
Vue 에서 서로 다른 컴포넌트 혹은 동일 컴포넌트 내 에서 자주 사용하는 상태 논리 로직이 있다면 **Composables** 패턴을 사용해서 간결하고 아름답게 로직을 처리할수 있습니다 Composables 는 리액트 세계에서 **Custom Hooks** 라고 불리는 것과 매우 유사합니다 Composables 패턴은 **Vue3 Composition API** 에서 사용 가능 합니다 ## Composables 이란? Vue 응용 프로그램의 컨텍스트 에서 "Composables"은 Vue의 구성 API를 활용하여 상태 저장 논리 를 캡슐화하고 재사용하는 기능입니다 . 프론트엔드 애플리케이션을 구축할 때 일반적인 작업에 로직을 재사용해야 하는 경우가 많습니다. 예를 들어 여러 위치에서 날짜 형식을 지정해야 할 수 있으므로 이를 위해 재사용 가능한 함수를 추출합니다. 이 포맷터 함수는 상태 비저장 논리 를 캡슐화 합니다. 일부 입력을 받고 즉시 예상 출력을 반환합니다. 상태 비저장 논리를 재사용하기 위한 많은 라이브러리가 있습니다. 예를 들어 lodash 및 date-fns 는 들어본 적이 있을 것입니다. 이와 대조적으로 상태 저장 논리에는 시간이 지남에 따라 변경되는 상태 관리가 포함됩니다. 간단한 예는 페이지에서 마우스의 현재 위치를 추적하는 것입니다. 실제 시나리오에서는 터치 제스처 또는 데이터베이스 연결 상태와 같은 더 복잡한 논리가 될 수도 있습니다. > Vue 공식 문서에서는 Composables 를 위와 같이 정의하고 있습니다 > 어떤 상황에서 사용해야 하는지 아래 예시 코드를 살펴보며 설명하겠습니다 > 예시 코드는 모두 Vue3 Script Setup 문법을 사용 하였습니다 ## 일반적인 코드 ```html // src/App.vue <script setup> import {computed, ref} from "vue"; const count = ref(0); const doubleCount = computed(() => count.value * 2); const plusOne = () => { count.value += 1 }; </script> ``` count 상태를 관리하는 로직 이 있습니다 만약 이러한 로직 이 여러 곳에서 사용되거나 동일한 컴포넌트 내에서 반복되어 사용해야 되는 경우 Composables 패턴 사용을 고려 할 수 있습니다 ## ❌ ```html // src/App.vue <script setup> import {computed, ref} from "vue"; const count = ref(0); const doubleCount = computed(() => count.value * 2); const plusOne = () => { count.value += 1 }; const count2 = ref(0); const doubleCount2 = computed(() => count2.value * 2); const plusOne2 = () => { count2.value += 1 }; const count2 = ref(0); const doubleCount2 = computed(() => count2.value * 2); const plusOne2 = () => { count2.value += 1 }; </script> ``` 극단적인 예시로 동일한 로직을 수행하는 여러 상태 값 을 관리해야 하는 상황이 오면 이렇게 동일한 코드가 반복되게 됩니다 Composables 패턴 사용을 통해 캡슐화 하여 재사용성이 쉽게 만들 수 있습니다 ## Composables 함수 구현 ```js // src/composables/useCounter.js import { computed, ref } from "vue"; export function useCounter() { const count = ref(0); const doubleCount = computed(() => count.value + 1); const plusOne = () => { count.value += 1; }; return { count, doubleCount, plusOne, }; } ``` 우선 Composables 함수를 선언하고 내부에 재사용할 상태 저장 로직을 구현합니다 ## ✅ ```html // src/App.vue <script setup> import useCounter from './composables/useCounter.js' const { count, doubleCount, plusOne } = useCounter(); </script> ``` useCounter 를 import 해와서 인스턴스화 합니다 여러 컴포넌트 에서 count 상태 저장 로직을 사용할 수 있습니다 ```html // src/App.vue <script setup> import useCounter from './composables/useCounter.js' const { count:count1, doubleCount:doubleCount1, plusOne:plusOne1 } = useCounter(); const { count:count2, doubleCount:doubleCount2, plusOne:plusOne2 } = useCounter(); const { count:count3, doubleCount:doubleCount3, plusOne:plusOne3 } = useCounter(); </script> ``` 동일 컴포넌트 내에서 사용해야 하는 경우 별칭을 사용할 수 있습니다 useCounter 함수를 호출할 때마다 새로운 count 인스턴스를 생성하기에 각각의 상태는 독립적입니다 plusOne1 을 호출하여도 count2, count3 은 영향을 받지 않습니다 ## 잡담 회사에서 Vue3 를 사용하며 아무런 생각 없이 중복된 상태 저장 로직을 구현하였는데 이를 모두 Composables 패턴으로 변경 하였습니다 동일한 로직을 복사 붙여넣기 하여 사용하는것 보단 Composables 패턴을 사용해서 재사용성이 높은 코드를 구현해보는게 어떨까요? 같이 협업중인 동료들과 매우 만족하며 사용중입니다!

Vue3Composables

Express 성능개선 사례 (Gzip, NODE_ENV)
Express 프레임워크 에서 성능을 개선할 수 있는 몇몇 사례들이 있습니다 **Static** 파일을 Express에서 응답하고 있는 경우 **Gzip** 압축을 사용하여 압축된 내용을 클라이언트에 보내게 되면 크기를 크게 줄일수 있습니다 또한 프로덕션 환경에서 Express를 배포할때 **NODE_ENV** 환경 변수를 production 으로 변경함으로 인해 성능 개선을 할 수 있습니다 ## 개발환경 * node - 16.13.1 * express - 4.17.2 * compression - 1.7.4 ## 정적파일 Gzip 적용 Express에서 Gzip 압축을 쉽게 할수 있게 도와주는 **Compression** 라이브러리가 있습니다 많은 트래픽이 발생하는 애플리케이션 같은 경우 Express 에서 압축을 진행하는 것 보다 Nginx 에서 gzip 압축을 진행하는 것이 좋다고 합니다 리버스 프록시를 사용하고 있지 않고 Express 내에서 정적파일 을 제공하는 경우 아래와 같은 방법을 사용하시면 됩니다 ### compression 설치 ```bash npm i compression ``` ### 미들웨어 적용 ```js const compression = require('compression') const express = require('express') const app = express() app.use(compression()) ``` > 제가 서비스중인 앱에서는 Express 에서 React 에서 Build 한 정적파일을 제공하기 때문에 위와 같은 방법을 이용하고 있습니다 ### 적용 전 Gzip 압축을 적용하기 전의 크기입니다 **6KB** 의 용량입니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Express%20성능개선%20사례%20%28Gzip,%20NODE_ENV%29/compression미적용.PNG) ### 적용 후 Gzip 압축을 적용한 후 의 크기입니다 **2.7KB** 의 용량입니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Express%20성능개선%20사례%20%28Gzip,%20NODE_ENV%29/compression적용.PNG) 비율로 비교하면 무려 파일의 크기가 **55%** 나 줄어든것 을 확인 하실 수 있습니다 지금은 6KB의 용량이지만 크기가 점점 커질수록 효율이 증가하는 것을 기대할 수 있습니다 하단의 Response Header 를 보면 **Content-Encoding: gzip** 으로 명시 되어있는것을 보실 수 있습니다 이렇게 압축된 형식을 서버에서 보내주게 되면 브라우저는 이를 확인해 압축을 해제합니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Express%20성능개선%20사례%20%28Gzip,%20NODE_ENV%29/gzip.PNG) > 정확한 측정을 위해 브라우저 캐시는 끄고 측정 하였습니다. ## NODE_ENV = production 적용 공식 문서에 따르면 NODE_ENV 환경 변수를 production 로 설정하게 되면 **앱의 속도가 3배**가 향상된다고 합니다 프로덕션 배포 환경에서는 필수적으로 설정해주어야 하며 이유는 아래와 같다고 합니다 * View 템플릿 캐싱 * Css 캐싱 * 간결한 에러메시지 생성 각 운영체제 별 환경변수를 설정하는 방법은 아래와 같습니다 ### Window 환경변수 설정 ```bash set NODE_ENV=production ``` ### Linux, Max 환경변수 설정 ```bash export NODE_ENV=production ``` ### node 실행시 환경변수 설정 아래와 같이 node 실행시 환경변수를 넣어줄수도 있습니다 ```bash NODE_ENV=production node app ``` ### 추천하는 방법 저는 아래와 같이 프로덕션,개발,테스트 환경마다 다른 **NODE_ENV** 환경변수 를 설정 해두었습니다 nodemon 는 자동으로 파일변경을 감지하여 node를 재실행 해주는 라이브러리 입니다 ```json "scripts": { "start": "set=NODE_ENV=production&nodemon app", "dev": "set NODE_ENV=dev&nodemon app", "test": "set NODE_ENV=test&jest --watchAll" }, ``` ### 적용 후 적용 전과 적용후의 차이점을 찾아보고자 하였으나 큰 차이점을 느끼지 못했습니다 크롬 개발자 도구에서 페이지가 로드되는 리소스, 시간 등등 차이점이 없었습니다 또한 공식문서의 설명처럼 css 캐시를 확인해보았으나 적용 전과 후의 차이점이 1도 없었습니다 분명 3배의 속도 향상을 기대할수 있다고 하였는데...? 서칭을 해보니 실제로 실험까지 진행한 아주 유익한 글이 있기에 링크합니다 [Express의 NODE_ENV 설정은 성능에 영향을 줄까?](https://changjoopark.medium.com/express%EC%9D%98-node-env-%EC%84%A4%EC%A0%95%EC%9D%80-%EC%84%B1%EB%8A%A5%EC%97%90-%EC%98%81%ED%96%A5%EC%9D%84-%EC%A4%84%EA%B9%8C-5447d08948d3) 위 글에서 내려진 결론은 성능 차이는 거의 없다고 보입니다 저 또한 성능 차이를 경험하지 못했습니다 그래도 Express 공식문서 에서 권장하고 있는 설정이니 꼭 설정해두는 편이 좋아보입니다 Express에서 성능 향상을 할수 있는 사례들은 위의 방법 뿐만 아니라 다른 여러 방법들이 있습니다 개인적으로는 Gzip 압축이 꽤나 큰 성능 향상을 경험했습니다 정적파일 의 크기를 무려 55%나 줄여주는 효과가 있었습니다 > [https://expressjs.com/ko/advanced/best-practice-performance.html](https://expressjs.com/ko/advanced/best-practice-performance.html) > 익스프레스 공식문서의 성능 개선 사례를 살펴보시면 다른 사례들도 많으니 > Express 프레임워크를 사용하고 계시다면 꼭 한번 살펴보시는걸 추천드립니다

Express성능개선

Express 프레임워크 Layerd 아키텍처 적용기
Express 프레임워크 로 구성된 토이 프로젝트의 아키텍처를 개선하였습니다 가볍고 최대한 빠르게 구현하고자 하여서 아키텍처 구조를 신경쓰지 않고 한곳에 모든 로직들을 모아서 관리하였었는데 여러 문제점이 있었습니다 * 기존의 코드를 알아보기가 힘듬 * 버그 발생시 추적이 어려움 * 기존 기능 수정시 너무 복잡함 유지보수가 점점 힘들어졌고 프로젝트를 리팩터링 겸 아키텍처 구조를 개선하였습니다 ## 기존 폴더구조 Mongoose 의 스키마 관련 파일들을 제외하곤 모두 main.js 파일에 모든 코드가 있었습니다 ```bash . |-- backup.js |-- comment.js |-- img.js |-- log.js |-- app.js ``` 기존에는 이렇게 모든 코드를 한곳에 모아서 작성하였습니다 (심히 반성해야 될 부분..) 기능들이 하나하나 추가 될 때마다 점점 코드는 읽기 힘들어졌고 오타를 찾기도 힘들며 버그가 발생해도 추적이 어려웠습니다 #### app.js ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Express%20프레임워크%20Layerd%20아키텍처%20적용기/img1.PNG) ## Layerd 아키텍처 적용 폴더구조 3 Layered Architecture 을 적용해 3개의 계층으로 나누어서 구조를 만들었습니다 * routes - 클라이언트와의 통신을 담당하는 컨트롤러 * services - 비즈니스 로직을 담당하는 계층 * repositories - 데이터베이스와 통신을 위한 계층 ```bash . |-- api | |-- loaders # 라우팅 및 시작프로세스 | |-- routes # 컨트롤러 |-- models # Mongoose 스키마,모델 |-- options |-- repositories # 데이터 엑세스 |-- services # 비즈니스 로직 |-- app.js ``` ## Routes (컨트롤러) 클라이언트와의 통신만을 담당하며 비즈니스 로직을 분리하였습니다 ```js // api/routes/comments.js const express = require("express"); const router = express.Router(); const CommentService = require("../../services/comment"); router.post("/", async (req, res) => { if (!req.body.name || !req.body.content) return res.status(400).send({ error: "name or content is null" }); try { const comment = await CommentService.create(req); res.json(comment); } catch (err) { res.status(400).json({ error: err.message }); } }); ``` ## Services (비즈니스 로직) 모든 비즈니스 로직은 Services 에서 담당합니다 ```js // services/comment.js const CommentRepository = require("../repositories/comment"); class CommentService { async create(req) { const { name, content } = req.body; const result = await CommentRepository.create({ name, content, ip: req.ip, }); return result; } } module.exports = new CommentService(); ``` ## Repositories (데이터 엑세스) 데이터베이스 와 통신 만을 담당합니다 ```js // repositories/comment.js const Comment = require("../models/comment"); class CommentRepository { async create({ ...commentData }) { const comment = new Comment({ ...commentData, }); const result = await comment.save(); return result; } } module.exports = new CommentRepository(); ``` > 각각 계층은 **독립적이며** 정해진 역할이 명확히 분리되어 있습니다 ## 어떠한 장점이 있을까? * 재사용성 - 독립적인 Repositories 및 Services 는 다른 계층에서 재사용이 용이합니다 * 확장성 항샹 - 모든 계층은 필요에 따라 다른 계층과 독립적인 확장 가능 * 안정성 - 각각 독립적인 계층이기에 다른 계층에 영향을 끼치지 않음 * 가독성 향상 > 실제 토이 프로젝트에 적용한 경험을 토대로 작성되었으며 부족한 설명이 많을 수 있습니다 > 모든 코드는 [https://github.com/Joon1313/Cam-loa-server](https://github.com/Joon1313/Cam-loa-server) 에서 참고 가능합니다

Express아키텍처

Nodejs 에서 Sentry 를 이용한 Error Tracking
현재 토이 프로젝트로 시작하여서 실제로 상용 서비스 중인 웹 애플리케이션 서비스가 2개 있다 최근에 크게 업데이트는 하지 않았으나 생각해보니 **에러**에 대한 대응이 없었다 개발에만 몰두하였고 유지보수 는 뒷전으로 생각했던걸까.. 그래서 에러를 트래킹 할수있는 툴을 찾아보니 **Sentry** 라는 좋은 서비스가 있었다 ! 거의 모든 언어를 지원하며 너무나 간편하게 사용이 가능했다 **Nodejs**에서 어떻게 사용하는지 튜토리얼을 살펴보자 ![sentry lang](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Nodejs%20에서%20Sentry%20를%20이용한%20Error%20Tracking/image2.PNG) 현재 **101개의 언어**를 지원하고 있는 모습 > [Sentry 공식사이트](https://sentry.io/) 를 참고하자 언어별 모든 튜토리얼을 확인할수 있다 ## Sentry 설치 ```bash npm i @sentry/node @sentry/tracing ``` ## Nodejs 에서 사용하기 ### Sentry 옵션 설정 및 초기화 ```js import * as Sentry from "@sentry/node"; import * as Tracing from "@sentry/tracing"; Sentry.init({ dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", tracesSampleRate: 1.0, }); ``` **dsn 키**는 회원가입후 프로젝트 생성시 발급 받을수 있다 ### 에러 캡쳐 ```js try { // ... } catch (e) { Sentry.captureException(e); } ``` **catch** 문 안에 Sentry.captureException() 메서드를 통해 에러를 캡쳐 할수 있다 ### 강제 에러 발생 ```js const main = () => { try { foo(); } catch (e) { Sentry.captureException(e); } }; main(); ``` try 문 안에서 존재하지않는 foo 함수를 호출하여 에러를 강제로 발생시켰다 Sentry 에 접속하여 생성한 프로젝트의 Issue 를 확인해보면 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Nodejs%20에서%20Sentry%20를%20이용한%20Error%20Tracking/image.png) 이렇게 발생한 에러 객체와 내용까지 모두 확인이 가능하다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Nodejs%20에서%20Sentry%20를%20이용한%20Error%20Tracking/error.PNG) 또한 어디서 에러가 발생하였는지 추적까지 가능하다 ## 요금제 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Nodejs%20에서%20Sentry%20를%20이용한%20Error%20Tracking/price.PNG) 요금제는 현재 이렇게 되어있다 **무료 요금제**도 있기 때문에 개인이 토이 프로젝트 에 사용해도 부담이 없습니다 [ 무료 요금제 스펙 ] * 5천건의 에러 * 1만건의 트랜젝션 * 1GB 첨부파일 * 30일 데이터 보존 ## 알림 에러에 발생 시 이메일 알림 설정까지 가능하니 보다 쉽고 빠르게 에러에 대응할 수 있고 쉬운 추적이 가능합니다 알림 설정 시 아래 사진처럼 이메일이 전송됩니다 다른 알림 방식으로 커스텀도 가능합니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Nodejs%20에서%20Sentry%20를%20이용한%20Error%20Tracking/email.PNG)

NodejsSentry

React 에서 디바운스, 스로틀 구현 (with Lodash)
기존에 진행했던 프로젝트 중 메모장 기능을 구현하였는데 Input의 Value 값을 Local Storage에 저장하여야 했고 **onChange** 이벤트를 통해 변경을 감지하였는데 생각해보니 사용자가 글자를 하나하나 입력할 때 마다 함수가 호출되는 로직은 매우 불필요한 자원을 낭비 하는것 같았다 찾아보니 **디바운스**, **스로틀** 이라는 아주 좋은 프로그래밍 기법이 있다는걸 알게 되었고 매우 만족할만한 결과를 얻게 되었다 자바스크립트로도 구현이 가능하긴 하지만 **Lodash** 라는 좋은 라이브러리를 굳이 안쓸 이유가 없었다 ## Lodash 설치 ```bash npm i lodash ``` 먼저 예시 코드부터 보면 이해가 빠를거라 생각합니다 먼저 Lodash를 npm 패키지를 통해 설치합니다 ## 기본, 디바운스, 스로틀 비교 ```js import _ from "lodash"; export default function App() { const 기본 = (e) => { console.log('기본 상태 변경 이벤트!'); }; const 디바운스 = _.debounce((e) => { console.log('디바운스 상태변경 이벤트!'); },500); const 스로틀 = _.throttle((e) => { console.log('스로틀 상태변경 이벤트!'); },500); return ( <div className="App"> <p>일반 상태변경</p> <input type="text" onChange={기본} /> <p>디바운스 상태변경</p> <input type="text" onChange={디바운스} /> <p>스로틀 상태변경</p> <input type="text" onChange={스로틀} /> </div> ); } ``` 예시 코드를 보자 각 Input 마다 onChange 로 상태변경 을 감지하여 함수를 호출하게끔 하였다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/React%20에서%20디바운스,%20쓰로틀%20구현%20%28with%20Lodash%29/image1.PNG) 모두 동일하게 Text를 입력하였다 각각 몇번이나 호출되었을까? ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/React%20에서%20디바운스,%20쓰로틀%20구현%20%28with%20Lodash%29/image2.PNG) * 기본 - 27번 * **디바운스** - 1번 * **스로틀**- 7번 이렇게 큰 차이를 보여주었다 만약 Input의 상태값을 **State** 로 관리하고 있다고 가정하면 Input의 상태값이 변경 될때마다 디바운스, 스로틀 기법을 적용하지 않았더라면 불필요한 **렌더링**이 27번이나 이루어진 셈이다 (React는 State 값이 변경될 때 마다 리렌더링 됨) 만약에 Input값이 변경될 때 마다 **AJAX 와 같은 서버와의 통신**을 한다고 하면 서버에 불필요한 부하도 줄것이다 불필요한 렌더링 + 함수 호출 으로 리소스 낭비가 이루어지고 있으니 상태값 이 자주자주 바뀌는 상황에는 이런 기법들을 적용 하는게 현명하다 ## 디바운스 디바운스는 짧은 시간 간격으로 이벤트가 연속으로 발생하면 이벤트 핸들러를 호출하지 않고 일정시간 딜레이 이후에 한번에 호출된다 짧은 시간동안 발생한 이벤트를 그룹화하여 **1번만** 호출되게끔 한다 Lodash 라이브러리 를 사용하지않고 자바스크립트 코드로만 작성한다고 하면 대략 아래의 코드로 구성된다고 보면 된다 ```js const 디바운스 = (callback, delay) => { let timerId; return () => { if(timerId) clearTimeout(timerId); timerId = setTimeout(callback, delay); }; }; document.getElementById('input').addEventListener('input', 디바운스(()=>{ console.log('이벤트 호출!!') }, 2000)); ``` 예시 코드를 살펴보자 Input이벤트가 최초로 발생하면 **setTimeout**을 호출하여 2000ms(2초) 뒤에 콜백 함수가 실행된다 만약 2000ms(2초) 가 지나기 전에 이벤트가 호출된다면 클로저로 기억하고있는 **timerId** 를 **clearTimeout** 로 이전 타이머를 지워주고 다시 2초의 타이머를 재설정 한다 이렇게 Input 이벤트, 버튼 중복클릭 방지, 필드 자동완성 등등 에 사용하면 불필요한 자원낭비를 줄여주는 효과가 있다 ## 스로틀 스로틀은 짧은 시간 간격으로 이벤트가 발생하더라도 일정 시간 간격으로 이벤트 핸들러가 그룹화 하여 **최대 1번**만 호출되게끔 한다 디바운스와 차이점을 알아보자 ```js const 스로틀 = (callback, delay) => { let timerId; return () => { if(timerId) return; timerId = setTimeout(()=>{ callback(); timerId = null; }, delay); }; }; document.getElementById('input').addEventListener('input', 스로틀(()=>{ console.log('이벤트 호출!!') }, 2000)) ``` 디바운스와 다르게 최초 이벤트 발생 후 정해진 딜레이 시간 이전에 호출되게 되면 함수가 **return** 된다 정해진 시간이 지난뒤 콜백함수가 호출될때 **timerId = null;** 로 변수값에 null을 할당 하기때문에 재호출 시점에 return 되지않고 타이머가 설정된다 즉 정해진 딜레이 안에 이벤트 가 호출되어도 타이머는 설정되지 않는다 콜백함수가 실행된 시점 이후로만 타이머가 설정된다 **Scroll 이벤트**와 같이 짧은시간 호출되는 과도한 이벤트를 제어할떄 유용하게 사용할수 있다 ## 정리 만약 Input 이벤트에 **스로틀** 기법을 사용하게 되면 지정해둔 딜레이 안에 Input 이벤트가 발생하였을때 return 되기 때문에 마지막으로 호출한 값을 참조하지 못하는 경우가 발생할 수 있다 반대로 Scroll 이벤트에 **디바운스** 기법을 사용하면 지정해둔 Delay 만큼 주기적으로 호출되어야 하는 콜백함수가 호출되지 않게 되니 각각 상황에 맞는 기법들을 사용해야 한다 > 이러한 기법을 통해 불필요한 리소스를 줄일수 있으니 꼭 활용하자 !

React Lodash

Nextjs 블로그에 utterances 적용하기
깃허브 저장소를 이용해 블로그 게시물에 댓글을 구현 할 수 있는 utterances 를 적용하였다 저장소 이슈를 이용한 방식이다 한마디로 저장소 가 댓글의 데이터베이스 라고 보면 된다 쉽게 구현이 가능하고 가벼워서 요즘 많은분들이 이용하고 있는것 같다 ## 저장소 생성 및 앱설치 공개범위 Public 저장소를 하나 **생성**해줍니다 이후 [utterances 앱](https://github.com/apps/utterances) 에 접속하여 설치하고 저장소 접근 권한을 설정하여 줍니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Nextjs%20블로그에%20utterances%20적용하기/image.png) > 설치된 앱은 https://github.com/settings/installations 에 접속하면 확인가능 합니다 ## 스크립트 생성 [utteranc 공식 사이트](https://utteranc.es/) 에 접속한뒤 안내에 따라 스크립트를 생성합니다 ```js <script src="https://utteranc.es/client.js" repo="저장소이름" issue-term="pathname" theme="github-dark" crossorigin="anonymous" async> </script> ``` ## 코드 구현 ```js // components/comment.js import React, { useEffect, useRef } from "react"; export default function Comment({ repo }) { const containerRef = useRef(); useEffect(() => { const utterances = document.createElement("script"); const attributes = { src: "https://utteranc.es/client.js", repo, "issue-term": "pathname", label: "comment", theme: "github-dark", //다크 테마 crossorigin: "anonymous", async: "true", }; Object.entries(attributes).forEach(([key, value]) => { utterances.setAttribute(key, value); }); containerRef.current.appendChild(utterances); }, []); return <div id="comment" ref={containerRef} />; } ``` 컴포넌트를 생성해준다 div 에 appendChild 로 스크립트 태그를 자식에 삽입해주어야 합니다 ```js // pages/posts/[id].js import Comment from "../../components/comment"; export default function Post(){ return( <Comment repo="저장소 이름" /> ) } ``` 이제 원하는 위치에 가져와서 사용해주면 됩니다 **useEffect**안에서 로직들이 처리가 되는데 이유는 **ComponentDidMount**시점에 호출 되어야 합니다 만약 **useEffect**를 사용하지 않고 구현하게 되면 브라우저 API인 **Document**객체를 찾을수 없어서 에러가 발생합니다 ![img](https://camlogs3.s3.ap-northeast-2.amazonaws.com/Nextjs%20블로그에%20utterances%20적용하기/image2.PNG) 또한 next/scrpit 를 이용하여 구현이 가능할까 하여 실험해본 결과 댓글창이 구현은 되는데 원하는 위치에 생성되지 않고 HTML 문서 하단에 생성되는 문제가 있었습니다 지금으로선 이 방법에서만 작동되는 것 같습니다

Nextjs utterances