[DAY-39] Vue (6)
π ν·κ°λ Έλ & λͺ°λλ λΆλΆλ€λ§ μ 리νλ λλ§μ TIL
π― λͺ¨λ κ°μ λ΄μ©μ μ μ§ μμμ!
μ€λμ μκ°μ?
Vuex λκ² νΈλ¦¬νλ€μ.
λ°μ΄ν°λ€μ μ»΄ν¬λνΈ λ΄λΆμμ μ μΈνμ§ μκ³
μ μμ μΌλ‘ μ°μ΄λ λ°μ΄ν°λ€μ νλ²μ λ¬Άμ΄ κ΄λ¦¬ν μ μλ€λ.
κ²λ€κ° λͺ¨λνλ κ°λ₯νλ€λ..!
[1] νλ¬κ·ΈμΈ
νλ¬κ·ΈμΈμ μμ±ν¨μΌλ‘μ¨ Vueμ μ μ κΈ°λ₯μ λ§λ€μ΄μ μ¬μ©ν μ μμ΅λλ€.
export default {
install: (app, options) => {
// νλ¬κ·ΈμΈμ λ΄λΆ λ΄μ©μ install λ©μλ λ΄λΆμ μμ±ν©λλ€.
});
}
// fetch νλ¬κ·ΈμΈμ λ§λλλ€.
// $fetchμ κ°μ΄ $κ° λΆλ κ²½μ°λ thisλ‘ μ κ·Όν΄μ μ¬μ©ν μ μλ€λ Vueμ ν΅μμ μΈ μλ―Έμ
λλ€.
export default {
install(app, component) {
app.config.globalProperties[options.pluginName || "$fetch"] = (
url,
opts
) => {
return fetch(url, opts).then((res) => res.json());
};
},
};
import { createApp } from "vue";
import App from "./App.vue";
import fetchPlugin from "~/plugins/fetch";
const app = createApp(App);
app.use(fetchPlugin. {
pluginName: '$myname'
});
// useλ‘ μ¬μ©ν΄μ λ±λ‘ν©λλ€.
// νΉμ μ΄λ¦μ μ§μ ν μ μμ΅λλ€.
app.mount("#app");
methods: {
async init() {
const res = await this.$myname('url');
console.log(res);
}
}
[2] λ―Ήμ€μΈ
λ―Ήμ€μΈμΌλ‘ Vue μ»΄ν¬λνΈμ μ¬μ¬μ© κ°λ₯ν κΈ°λ₯μ λ§λ€μ΄μ μ¬μ©ν μ μμ΅λλ€.
λ―Ήμ€μΈμ νλμ μ»΄ν¬λνΈμ μ μ¬ν ꡬ쑰λ₯Ό μμ±ν μ μμ΅λλ€.
mixins: [myMixins]; // λ―Ήμ€μΈμ μ΄λ¦μ propsμ λμΌν ννλ‘ λΆλ¬μ μ¬μ©ν©λλ€.
κ°μ μ΄λ¦μ ν ν¨μ(λΌμ΄νμ¬μ΄ν΄)λ λͺ¨λ νΈμΆλ μ μκ² λ°°μ΄μ λ³ν©λ©λλ€.
λν methods, data λ±λ ν¨κ» μ¬μ©ν μ μκ² μ μ νκ² λ³ν©λ©λλ€.
νμ§λ§ λ§μ½ λ―Ήμ€μΈ λ΄ μ΅μ λ€κ³Ό μ»΄ν¬λνΈμ μ΅μ λ€μ΄ μ€λ³΅λ κ²½μ°, μ»΄ν¬λνΈμ μ΅μ μ΄ μ°μ λμ΄ μ μ©λ©λλ€. (ν ν¨μ μ μΈ)
// sampleMixin
export default {
data() {
return {
count: 1,
mag: "HI~",
};
},
};
// App.vue
import Hello from "~/components/Hello";
import sampleMixin from "~/mixins/sample";
export default {
components: {
Hello,
},
mixins: [sampleMixin],
data() {
return {
msg: "Hello Vue!",
};
},
};
{{ msg }}
λ₯Ό μΆλ ₯νμ λ, μ»΄ν¬λνΈμ msgκ° μΆλ ₯λλ κ²μ νμΈν μ μμ΅λλ€.
[+] λμ μ»΄ν¬λνΈ + λ―Ήμ€μΈ μμ
λΉμ·ν λ°μ΄ν° νμμ λ λλ§νλ λ κ°μ μ»΄ν¬λνΈλ₯Ό λμ μ»΄ν¬λνΈμ κ°λ μΌλ‘ λ¬Άμ ν,
λ―Ήμ€μΈμΌλ‘ ν΄λΉ μ»΄ν¬λνΈλ€μ propsλ€μ μ μν΄μ€ μ μμ΅λλ€.
// App.vue
<template>
<component
:is="field.component"
v-for="field in fields"
:key="'component-' + field.title"
v-model="field.value"
:title="field.title"
:items="field.items"
/>
<h1>κ²°κ³Ό</h1>
<div v-for="field in fields" :key="'result-' + field.title">
{{ field.value }}
</div>
<button @click="submit">μ μΆ</button>
</template>
<script>
import * as FieldComponents from "~/components/fields/index";
export default {
components: {
...FieldComponents,
},
data() {
return {
fields: [
{
component: "TextField",
title: "μ΄λ¦",
value: "",
},
{
component: "SimpleRadio",
title: "λμ΄λ",
value: "",
items: ["20λ", "30λ", "40λ", "50λ"],
},
],
};
},
methods: {
submit() {
const results = this.fields.map(({ title, value }) => ({
title,
value,
}));
console.log(results);
},
},
};
</script>
// index.js - μ΄λ κ² importλ₯Ό λ¬Άμ΄μ μ¬μ©νλ©΄ μ»΄ν¬λνΈκ° λ§μμ§ κ²½μ°μλ μ½λκ° κΉλν©λλ€.
export { default as TextField } from "./TextField";
export { default as SimpleRadio } from "./SimpleRadio";
// mixin.js
export default {
props: {
items: {
type: Array,
default: () => [],
},
title: {
type: String,
default: "",
},
modelValue: {
type: String,
default: "",
},
},
emits: ["update:modelValue"],
};
// SimpleRadio.vue
<template>
<h3>{{ title }}</h3>
<ul>
<li v-for="item in items" :key="item">
<label>
<input
type="radio"
:value="item"
:name="title"
@input="$emit('update:modelValue', $event.target.value)"
/>
{{ item }}
</label>
</li>
</ul>
</template>
<script>
import fieldMixin from "./mixin";
export default {
mixins: [fieldMixin],
};
</script>
// TextField.vue
<template>
<div>
<h3>{{ title }}</h3>
<input
:value="modelValue"
type="text"
@input="$emit('update:modelValue', $event.target.value)"
/>
</div>
</template>
<script>
import fieldMixin from "./mixin";
export default {
mixins: [fieldMixin],
};
</script>
[3] Teleport
teleport
νκ·Έλ₯Ό μ¬μ©νμ¬ μ»΄ν¬λνΈ μμλ₯Ό μκ°μ΄λ μν¬ μ μμ΅λλ€.
<template>
<div @click="onModal">
<slot name="activator"></slot>
</div>
<teleport to="body">
<!-- νλ¨μ μμλ€μ body νκ·Έ λ΄λΆλ‘ μκ°μ΄λ μν΅λλ€. -->
<template v-if="isShow">
<div class="modal" @click="offModal">
<div
:style="{ width: `${parseInt(width, 10)}px` }"
class="modal__inner"
@click.stop
>
<slot></slot>
</div>
</div>
</template>
</teleport>
</template>
fixed λ± μμ μμμ μ€νμΌ μν₯μ λ°λ μμλ€μ μ΅μλ¨μΌλ‘ μ΄λμν¬ λ, μνλ λ³λμ μμλ‘ μ΄λνκΈ° μν λ λ± μ μ©νκ² μ¬μ©ν μ μμ΅λλ€.
[+] @click.stop
ν΄λΉ μ½λμμλ μ΄λ²€νΈ λ²λΈλ§μ΄ μΌμ΄λ modal__inner
ν΄λμ€λ₯Ό κ°μ§ divλ₯Ό ν΄λ¦ν΄λ offModal
ν¨μκ° μ€νλ©λλ€.
<template v-if="isShow">
<div class="modal" @click="offModal">
<div class="modal__inner">
<slot></slot>
</div>
</div>
</template>
μ΄λ, modal__inner
ν΄λμ€λ₯Ό κ°μ§ divμ @click.stop
μμ±μ λ£μ΄μ£Όμ΄ μ΄λ²€νΈ μ νλ₯Ό λ§μ μ μμ΅λλ€.
<template v-if="isShow">
<div class="modal" @click="offModal">
<div class="modal__inner" @click.stop>
<slot></slot>
</div>
</div>
</template>
λ°°μ΄ λ΄μ©μ μμ§ λ§μλ€! π
[4] Provide, Inject
μΌλ°μ μΌλ‘ λ°μ΄ν°λ₯Ό λΆλͺ¨μμ μμ μ»΄ν¬λνΈμ μ λ¬ν λ props
λ₯Ό μ¬μ©ν©λλ€.
νμ§λ§ μ΄λ λΆλͺ¨ μ»΄ν¬λνΈμ μμ μ»΄ν¬λνΈ μ¬μ΄μ λ°μ΄ν°λ₯Ό μ λ¬ν λΏ, μ‘°μ μ»΄ν¬λνΈμμ λ°λ‘ 맨 νμ μ»΄ν¬λνΈμ λ°μ΄ν°λ₯Ό μ λ¬ν΄μ€ μ μμ΅λλ€.
λ¬Όλ‘
props
λ₯Ό κ·Έ μ¬μ΄μ μλ μ»΄ν¬λνΈμ μ λΆ μ μνλ©΄ κ°λ₯νμ§λ§ λ§€μ° λ²κ±°λ‘μ΄ μμ μ λλ€.
μ΄λ Provide
μ Inject
μμ μ¬μ©ν΄μ λ°μ΄ν°λ₯Ό μ λ¬ν μ μμ΅λλ€.
// App.vue
export default {
components: {
Parent,
},
provide() {
return {
msg: this.msg, // data μ΅μ
μμͺ½μ provide μ΅μ
μΌλ‘ λ°μ΄ν°λ₯Ό νμ μμλ€μκ² λ³΄λ΄μ€λλ€.
};
},
data() {
return {
msg: "App Vue!",
};
},
};
// Child.vue
<template>
<h1>Child {{ msg }}</h1>
</template>
<script>
export default {
inject: ["msg"], // injectλ‘ μ‘°μ μμμμ 보λ΄μ€ λ°μ΄ν°λ₯Ό λ°μ΅λλ€.
};
</script>
νμ§λ§
provide
μinject
λ₯Ό μ¬μ©ν΄μ μ λ¬λ λ°μ΄ν°λ λ°μν λ°μ΄ν°κ° μλλλ€.
[4-1] computed
computed
λ κ³μ°λ λ°μ΄ν°λ₯Ό λ§λ€ λ μ¬μ©ν μ μλ μ΅μ
μ
λλ€.
μ΄λ₯Ό ν¨μ ννλ‘ λΆλ¬μμ μ¬μ©νκ² λλ©΄, μ΅μ μ΄ μλ νλμ ν¨μλ‘μ¨ μνλ λΆλΆμμ κ³μ°λ λ°μ΄ν°λ₯Ό λ§λ€μ΄μ€ μ μμ΅λλ€.
// App.vue
import Parent from "~/components/Parent";
import { computed } from "vue";
export default {
components: {
Parent,
},
provide() {
return {
msg: computed(() => this.msg),
// computed ν¨μ λ΄λΆμ νλμ μ½λ°±μ μμ±ν΄μ κ³μ°λ κ°μ λ°νν©λλ€.
};
},
data() {
return {
msg: "App Vue!",
};
},
};
// Child.vue
<template>
<h1>Child {{ msg.value }}</h1>
</template>
νμ§λ§ μ΄ computed
ν¨μλ₯Ό μ¬μ©νκ² λλ©΄ λ°νκ°μ΄ μλμ λ°μ΄ν° νμμ΄ μλλλ€.
λ°λ‘ κ°μ²΄ ννλ‘ λ°νμ΄ λ©λλ€.
λ°λΌμ κ³μ°λ λ°μ΄ν°λ₯Ό μ§μ κΊΌλ΄ μ¬μ©νκΈ° μν΄ .value
λ₯Ό κΌ λΆμ¬ μ¬μ©ν΄μΌ ν©λλ€.
μ΄λ
Vuex
λ₯Ό μ¬μ©νλ©΄ μ λΆ ν΄κ²°λ λ¬Έμ μ λλ€. π
[5] Vuex(Store)
μ¬λ¬ μ»΄ν¬λνΈμμ λμμ μ¬μ©λλ λ°μ΄ν°λ λ³λμ νμΌμ μ μ₯ν΄μ μ μμΌλ‘ νμ©ν μ μμ΅λλ€.
// store/index.js
import { reactive } from "vue"; // λ°μν λ°μ΄ν°λ₯Ό μ 곡ν΄μ£Όλ vue λ΄μ₯ ν¨μμ
λλ€.
export const state = reactive({
msg: "Hello vue",
count: 1,
});
export const getters = {
// computed(κ³μ°λ λ°μ΄ν°) λ‘μ§
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 fetchResult = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
).then((res) => res.json());
mutations.updateMsg(fetchResult.title);
},
};
μ΄λ κ² λ³λμ νμΌλ‘ κ΄λ¦¬νκ² λλ©΄ λ€μν μ»΄ν¬λνΈμμ λ°μ΄ν°λ₯Ό μ¬μ©ν μ μμΌλ©°
ν΄λΉ λ°μ΄ν°μ κ΄λ¦¬κ° μ¬μμ§λλ€.
μ΄λ¬ν λ°μ΄ν° κ΄λ¦¬ λ‘μ§μ μ‘°κΈ λ μ½κ² ν΄μ€ μ μλ λΌμ΄λΈλ¬λ¦¬κ° λ°λ‘ Vuex
μ
λλ€.
Vuex
λ μν κ΄λ¦¬ νν΄ λΌμ΄λΈλ¬λ¦¬μ
λλ€.
Vue μ΄ν리μΌμ΄μ μ λͺ¨λ μ»΄ν¬λνΈμ λν μ€μ μ§μ€μ μ μ₯μ μν μ νκ² λ©λλ€.
vuex
λ λ€μ λͺ
λ Ήμ΄λ‘ μ€μΉν μ μμ΅λλ€.
npm i vuex@next // next λ²μ μΌλ‘ μ€μΉν©λλ€. (4.0 μ΄μ)
// store/index.js
import { createStore } from "vuex";
export default createStore({
state() {
return {
msg: "store data",
count: 1,
};
},
getters: {
reverseMsg(state) {
return state.msg.split("").reverse().join("");
},
},
mutations: {
increaseCount(state) {
state.count += 1;
},
updateMsg(state, newMsg) {
state.msg = newMsg;
},
},
actions: {
// context => state, getters, commit(mutations), dispatch
async fetchTodo({ commit }) {
const todo = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
).then((res) => res.json());
commit("updateMsg", todo.title);
},
},
});
μ¬κΈ°μ context κ°μ²΄
λ‘ μ΅μ
μμμ λ€λ₯Έ μ΅μ
μΌλ‘ μ κ·Όν μ μμ΅λλ€.
context
λ state, getters, commit(mutations), κ·Έ μΈμΈ dispatch
λ₯Ό μ 곡ν©λλ€.
// main.js
const app = createApp(App);
app.use(store); // λ±λ‘ν΄μ μ¬μ©ν©λλ€.
app.mount("#app");
// Hello.vue
export default {
computed: {
msg() {
return this.$store.state.msg; // $storeμΌλ‘ μ κ·Όν μ μμ΅λλ€.
},
count() {
return this.$store.state.count;
},
},
};
[5-1] λͺ¨λν
vuex
μ λ°μ΄ν°λ μ¬λ¬ μ΅μ
λ€μ μ κ·ΌνκΈ° μν΄ this.$store. ...
μ νμμΌλ‘ μ κ·Όν©λλ€.
νμ§λ§ λ°μ΄ν°μ μ, λλ μ¬λ¬ μ΅μ λ΄λΆ κ°λ€μ΄ λ§μμ§μλ‘ store/index.js νμΌ νλμμ λͺ¨λ λ°μ΄ν°λ€μ κ΄λ¦¬νκΈ° νλλλ€.
λ°λΌμ vuex
λ storeμ κΈ°λ₯λ³λ‘ μͺΌκ°μ μ 곡νλ λͺ¨λνλ₯Ό μ 곡ν©λλ€.
κ·Έλ¦Όκ³Ό κ°μ΄ store λ΄λΆμμ λΆλ¦¬ν js νμΌμ index.js νμΌ μμ λͺ¨λνν΄μ λΆλ¬μ μ¬μ©ν μ μμ΅λλ€.
// message.js
export default {
namespaced: true, // ν΄λΉνλ μ΄λ¦ λ²μ(message)λ‘ λ°μ΄ν°λ€μ μ¬μ©ν μ μλ μ΅μ
state() {
return {
message: "hello Store Module",
};
},
getters: {
reversedMessage(state) {
return state.message.split("").reverse().join("");
},
},
mutations: {
updateMessage(state, newMessage) {
state.message = newMessage;
},
},
actions: {
async fetchTodo({ commit }) {
const todo = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
).then((res) => res.json());
commit("updateMessage", todo.title);
},
},
};
// index.js
export default createStore({
state() {
...
},
modules: { // modules μ΅μ
λ΄λΆμμ λΆλ¬μ μ¬μ©ν©λλ€.
message
}
}
// Hello.vue
export default {
computed: {
msg() {
return this.$store.state.msg;
},
count() {
return this.$store.state.count;
},
storeMessage() {
return this.$store.state.message.message;
// λͺ¨λμ μ΄λ¦, ν΄λΉ μνμ μ΄λ¦λ λͺ
μν΄μ€μΌ ν©λλ€.
},
},
methods: {
increaseCount() {
this.$store.commit("count/increaseCount");
},
},
};
[5-2] map~
vuexμ λ΄μ₯ ν¨μ mapState, mapGetters, mapMutations, mapActions
λ₯Ό νμ©ν μ μμ΅λλ€.
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
export default {
computed: {
...mapState("count", ["count"]),
...mapState("message", ["message"]),
},
};
/*
1. λͺ¨λμμ μν κ°μ Έμ€κΈ°
...mapState('λͺ¨λν λ νμΌ μ΄λ¦', ['κ°μ Έμ¬ μν 1', 'κ°μ Έμ¬ μν 2'])
2. λͺ¨λν λ νμΌ μ΄λ¦μ΄ μμ κ²½μ° (index.jsμΈ κ²½μ°)
...mapState(['κ°μ Έμ¬ μν1', 'κ°μ Έμ¬ μν2']);
*/
λκΈλ¨κΈ°κΈ°