일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 고양이 사진 검색기
- git 같은계정 다른 컴퓨터
- SCSS use
- 리스트 렌더링
- 프로그래머스 데브코스 프론트엔드
- SCSS extend
- vue 이벤트 수신
- 이벤트 수식어
- vue 지역 컴포넌트
- vuex map
- 쌓임맥락
- 폼 입력 바인딩
- 리액트
- postcss
- KDT 프로그래머스
- KDT 프로그래머스 데브코스 프론트엔드
- 다른컴퓨터에서 git사용
- react next
- 프로그래머스 데브코스
- Vue
- Spacer
- SCSS forward
- vue mixin
- intersection opserver
- nextjs사용법
- 프로그래머스 K_Digital Training
- netlify redirect
- flex
- SCSS import
- 프로그래머스 프론트엔드 데브코스
- Today
- Total
혼자 적어보는 노트
프로그래머스 데브코스 TIL - Day 39 본문
✅ 오늘의 학습
📌 Vue (6)
- 플러그인
- 믹스인
- Teleport
- Provide / Injext
- Store
- Vuex
플러그인 사용
app.config.globalProperties에 원하는 플러그인의 이름을 붙여주면
컴포넌트 내부에서 this로 접근할 수 있다.
[plugins/fetch.js]
export default {
install(app, option) {
// 첫번째 인자는 app 객체, 두 번째 인자는 option
app.config.globalProperties.$fetch = (url, opts) => {
return fetch(url, opts).then(res => res.json());
};
}
};
[main.js]
const app = createApp(App);
app.use(fetchPlugin);
app.mount('#app');
[컴포넌트]
created(){
this.init();
},
methods:{
async init(){
const res = await this.$fetch('https://jsonplaceholder.typicode.com/todos/1');
console.log(res);
}
mixin
재사용 가능한 기능을 미리 정의해두고 컴포넌트에서 불러와서 쓸 수 있는 기능.
같은 이름의 훅 함수는 병합되어 호출되며, 값을 요구하는 옵션의 객체에 중복된 키가 있다면
컴포넌트 쪽의 옵션이 우선순위를 갖게된다.
[mixins/sample.js]
export default {
data(){
return {
count:1,
msg: 'Hi~'
};
}
};
[App.vue]
<template>
<h1>
{{ msg }} /* HI!!! * 컴포넌트에 선언한 옵션이 출력 됨 */
{{ count }} /* 1 */
</h1>
</template>
<script>
import sampleMixin from './mixins/sample';
export default {
mixins: [sampleMixin],
data() {
return {
msg: 'HI!!!',
};
},
};
</script>
폴더 안의 컴포넌트 전부 불러오기
App 컴포넌트 내부에서 많은 컴포넌트들을 등록해야 할 때
일일히 import를 해주어야 하는 불편함이 있다
import TextField from '~/components/fields/TextField';
import SimpleRadio from '~/components/fields/SimpleRadio';
export default {
components:{
TextField,
SimpleRadio
},
...
💡 해결 방법!
컴포넌트들이 담긴 폴더에 index.js를 만들어준다.
export { default as TextField } from './TextField';
export { default as SimpleRadio } from './SimpleRadio';
다시 App.vue로 돌아와서 작성한다.
import * as FieldComponents from '~/components/fields/index.js';
export default {
components:{
...FieldComponents
},
Teleport를 활용한 Modal 만들기
모달 핸들링 구조
- 모달의 open/close를 결정하는 데이터는 모달 밖의 컴포넌트에 있다.
- 모달 안에서는 받은 값에 따라 open/close를 한다
- 모달 안의 이벤트를 통해 받은 값을 false로 바꿔주어 close를 한다.
[App.vue]
<template>
<Modal
v-model="isShow"
width="300px">
<template #activator>
<button>클릭 시 Modal Open!</button>
</template>
<h3>모달에 나타날 내용</h3>
</Modal>
</template>
export default {
data() {
return {
isShow: false
};
},
};
</script>
#activator를 통해 노출시킬 버튼의 영역을 지정하고
아래는 모달 클릭 시 나타나는 내용을 작성한다.
[Modal.vue]
<template>
<div @click="onModal">
<slot name="activator"></slot>
</div>
<teleport to="body"> <!-- 바디태그 내부로 순간이동 -->
<template v-if="modelValue">
<div
class="modal"
@click="offModal">
<div
:style="{width: `${parseInt(width, 10)}px`}"
class="modal__inner"
@click.stop>
<button
v-if="closeable"
class="close"
@click="offModal">
x
</button>
<slot></slot>
</div>
</div>
</template>
</teleport>
</template>
teleport안에 작성한 내용은 지정한 body태그 내부로 순간이동 하게 된다.
즉, Modal의 선언은 컴포넌트 내부에 했지만 버튼만 그 자리에 있고
Modal 내용은 body 하단에 위치하게 된다.
[Modal.vue 의 script부분]
<script>
export default {
props:{
width: {
type: [String, Number],
default: 400
},
closeable:{
type:Boolean,
default: false
},
modelValue:{
type:Boolean,
default: false,
}
},
watch: {
modelValue(newValue){
if(newValue){
window.addEventListener('keyup', this.keyupHandler);
} else{
window.removeEventListener('keyup', this.keyupHandler);
}
}
},
methods:{
keyupHandler(event){
if(event.key === 'Escape'){
this.offModal();
}
},
onModal(){
this.$emit('update:modelValue', true);
},
offModal(){
this.$emit('update:modelValue', false);
}
}
};
</script>
보통 컴포넌트에 등록된 이벤트는 컴포넌트가 언마운트 될 때 제거가 되지만
window에 등록한 이벤트는 직접 제거를 해주어야 한다.
$emit을 통해 상위로 값을 전달해준다. 나는 이 $emit 방식이 아직도 좀 헷갈린다.
물론 간단한 예제기 때문에 보면 알겠는데 좀 다른 방식으로 응용을 한다면
뚝딱 만들어내진 못할 것 같은 느낌..? 몇번 더 만들어 보면서 익혀야 될 듯 하다.
모달 전역 선언
[main.js]
import Modal from '~/components/Modal';
const app = createApp(App);
app.component('Modal', Modal);
app.mount('#app');
모달 같은 경우는 같은 형태를 여러 곳에서 사용할 수 있기 때문에
전역으로 등록해 놓으면 import 없이 사용할 수 있다,
Provide / inject
부모컴포넌트의 데이터를 뎁스가 깊은 하위 컴포넌트에 전달해야 할 때
props를 통해 일일이 전달하지 않고 한번에 전달할 수 있는 기능.
* 전달하는 데이터에 반응성이 제공되지 않기 때문에 따로 처리를 해야한다.
1. 부모 컴포넌트에서 provide 옵션 사용
전달할 데이터를 vue의 computed를 import해서 계산된 값으로 만들어서 사용한다
=> 그냥 사용 시 전달된 데이터는 반응성을 가지지 않는다.
<script>
import Parent from '~/components/Parent';
import { computed } from 'vue';
export default {
components:{
Parent,
},
provide(){
return{
msg: computed(()=> this.msg)
};
},
data(){
return{
msg: '전달할 데이터'
};
}
};
</script>
2. 전달 받을 값을 사용할 하위 컴포넌트에서 injext 옵션 사용
- computed로 계산되어 전달된 값을 사용할 경우 .value로 접근해서 사용해주어야 한다.
<template>
<h2>Child! / {{ msg.value }}</h2>
</template>
<script>
export default {
inject: ['msg']
};
</script>
store
store에서 공유된 데이터를 직접 변경해도 데이터가 변경된다.
하지만 직접 여러 곳에서 변경을 하게 된다면 이후 프로젝트의 규모가 커졌을 때
데이터를 추적하기가 어려워지기 때문에 함수를 통해서만 state의 변경을 해야한다.
[store.js]
import { reactive } from 'vue';
export const state = reactive({
msg: 'Hello Vue?!',
count: 1
});
export const getters = {
reversedMsg(){
return state.msg.split('').reverse().join('');
}
};
export const mutations = {
increaseCount() {
state.count += 1;
},
decreaseCount() {
state.count -= 1;
},
updateMsg(newMsg) {
state.msg = newMsg;
}
};
export const actions = {
async fetchTodo(){
const todo = await fetch('https://jsonplaceholder.typicode.com/todos/1').then(res => res.json());
mutations.updateMsg(todo.title);
console.log(todo);
}
};
함수를 통해 state를 변경하고 반응성을 적용하기 위해 vue의 reactive를 사용한다.
getters에 함수를 등록하여 변환된 값을 전달해 줄 수 있고
mutations를 사용하여 state를 변경하는 함수를 등록 할 수 있고
actions를 통해 비동기 함수를 구분하여 변경할 수 있다.
컴포넌트에서는 아래와 같이 store의 데이터를 사용할 수 있다.
<template>
<h1>Hello.vue</h1>
<div>{{ reversedMsg }}</div>
<div @click="increaseCount">
{{ count }}
</div>
<button @click="fetchTodo">
Get Todo
</button>
</template>
<script>
import { state, getters, mutations, actions } from '~/store';
export default {
data() {
return state;
},
computed:{
reversedMsg: getters.reversedMsg
},
methods:{
increaseCount: mutations.increaseCount,
fetchTodo: actions.fetchTodo
},
};
</script>
Vuex
: Vue에서의 상태 관리 패턴 + 라이브러리
바로 위에 작성했던 코드와 비슷한 형태로 조금 더 편하게 관리 할 수 있는 라이브러리이다.
사용 방법은 비슷하다
1. store 생성
[store/index.js]
import { createStore } from 'vuex';
export default createStore({
// 데이터는 항상 함수로
state() {
return {
msg: 'Hello vue',
count: 1
};
},
getters : {
reversedMsg(state){
return state.msg.split('').reverse().join('');
}
},
mutations: {
increaseCount(state) {
state.count += 1;
},
updateMsg(state, newMsg){
state.msg = newMsg;
}
},
actions: {
async fetchTodo({commit}){
const todo = await fetch('https://jsonplaceholder.typicode.com/todos/1').then(res=>res.json());
console.log(todo),
commit('updateMsg', todo.title);
}
}
});
createStore으로 저장소 생성 후 객체에 아래의 내용들을 선언할 수 있다.
state (상태)
: store에서 다루는 데이터
getters (계산된 데이터)
: 함수로 이루어진 계산된 데이터들이 담기는 곳. 첫 번째 인자로 현재 컴포넌트의 state 값을 제공한다.
mutations (상태 변경)
: 상태를 변경하는 함수들이 담기는 곳. 첫 번째 인자로 현재 컴포넌트의 state 값을 제공한다.
actions (비동기)
: 비동기 동작들을 다루는 곳. 첫 번째 인자는 context를 제공한다.
context에는 state, getters, commit, dispatch이 있고
commit은 mutaion을 실행할 때 사용되며
dispatch는 action을 실행할 때 사용된다.
2. app에 연결
store를 app에 연결한다.
import { createApp } from 'vue';
import App from './App.vue';
import store from '~/store';
const app = createApp(App);
app.use(store);
app.mount('#app');
3. store의 state/getter 사용하기
computed 내부에서 함수의 반환 값으로 데이터를 받아온다.
<template>
<h1>Hello 컴포넌트</h1>
<h1>{{ msg }}</h1>
<h1>{{ count }}</h1>
<h1>{{ reversedMsg }}</h1>
</template>
<script>
export default {
computed: {
// 계산된 값으로 사용
msg(){
return this.$store.state.msg;
},
count(){
return this.$store.state.count;
},
reversedMsg(){
return this.$store.getters.reversedMsg;
}
}
};
</script>
4. store의 mutaions/actions 사용
$store의 commit과 dispatch를 사용하여 변경을 요청한다.
<template>
<Hello />
<button @click="increaseCount">
increaseCount
</button>
<button @click="fecthTodo">
UpdateMSG
</button>
</template>
<script>
import Hello from '~/components/Hello';
export default {
components:{
Hello,
},
data(){
return {
msg: '안녕~'
};
},
methods: {
increaseCount() {
this.$store.commit('increaseCount'); // mutaions
},
fecthTodo(){
this.$store.dispatch('fetchTodo'); // actions
}
}
};
</script>
Vuex store 모듈화
state의 값이 많아지면 한 곳에서 관리하기가 어렵기 때문에
관련있는 state끼리 나누어서 모듈화를 시킬 수 있다.
store 모듈 추가
파일을 추가하고 namespaced를 true로 작성하고 내용을 작성해준다.
[store/message.js]
export default {
namespaced: true, // true를 설정해야 module로써 사용할 수 있다.
state() {
return {
msg: 'Store Module'
};
},
getters: {
reverseMsg(state){
return state.msg.split('').reverse().join('');
}
},
mutations : {
changeMsg(state){
state.msg = 'Change Message';
},
updateMsg(state, newMsg){
state.msg = newMsg;
}
},
actions: {
async fetchTodo({commit}){
const todo = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(res=>res.json());
commit('updateMsg', todo.title);
// 해당 module 안에 존재하기 때문에 경로 입력 X
}
}
};
index.js에서 mudule옵션을 추가하여 추가로 생성한 message 입력한다.
[store/index.js]
import { createStore } from 'vuex';
import message from './message';
export default createStore({
state() {},
getters : {},
mutations: {},
actions: {},
modules:{
message
// key와 value가 같기 때문에 합침
}
});
모듈화한 store의 state 다루기
state를 가져올 땐 $store.state.모듈명.데이터명으로 가져올 수 있고
getter을 가져올 땐 대괄호를 사용하여 모듈 명을 작성한다.
actions와 mutations를 가져올 땐 모듈 명과 해당 함수 명을 입력해주면 된다.
<template>
<h1>{{ storeMessage }}</h1>
<h1>{{ reversedMsg }}</h1>
<button @click="changeMsg">
클릭
</button>
<button @click="fecthTodo">
Get Todo
</button>
</template>
<script>
export default {
computed:{
storeMessage(){
return this.$store.state.message.msg;
},
reversedMsg(){
return this.$store.getters['message/reverseMsg']; // 일반 괄호가 아니라 []
}
},
methods: {
changeMsg(){
this.$store.commit('message/changeMsg');
},
fecthTodo(){
this.$store.dispatch('message/fetchTodo');
// action을 실행할 때는 dispatch('action이름')
}
}
};
</script>
솔직히 약간 일관성이 없어서 한번에 습득하기에는 조금 복잡하다고 느껴진다..
그래서인지 vuex에서도 추가 기능을 제공한다.
mapping을 통해 코드 개선
vuex에서 제공하는 map함수들로 코드를 좀 더 개선시킬 수 있다.
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
computed:{
...mapState('message',[
'msg'
]),
...mapGetters('message',[
'reverseMsg'
])
},
methods: {
...mapMutations('message',[
'changeMsg']),
...mapActions('message',[
'fetchTodo']),
}
};
</script>
전역 상태를 가져오려면 map의 첫번째 인자를 생략하면 된다.
✍ 느낀 점
몇가지 생략하면서 적었음에도 오늘은 내용이 꽤 많다.
아무래도 Vue를 처음 배우다보니 짧은 시간 내에 익혀야 할 것들이 많기도 하고
잘 적용할 수 있을 지는 모르겠다.. 과제 기간은 시작됐지만 과제에 적용할 내용들이
이후 강의들에 나와있어서 이번엔 강의를 잘 들으며 과제를 마칠 수 있을지 걱정이 많이 되지만
일단은 최선을 다해서 해봐야겠다🔥
'스터디' 카테고리의 다른 글
프로그래머스 데브코스 TIL - Day 43~47 복습기간 회고 (0) | 2022.05.25 |
---|---|
프로그래머스 데브코스 TIL - Day 40 (0) | 2022.05.14 |
프로그래머스 데브코스 TIL - Day 38 (0) | 2022.05.12 |
프로그래머스 데브코스 TIL - Day 37 (0) | 2022.05.11 |
프로그래머스 데브코스 TIL - Day 36 (0) | 2022.05.11 |