vue create 프로젝트명
[X] Default ([Vue3] babel, eslint)
생성 명령어 입력하고 옵션 vue3 선택함
npm run serve
실행 명령어 입력
뷰에서 style 속성 데이터바인딩 하려면 :style="" 안에 자바스크립트 코드를 넣으면 된다.
보다 깔끔한 코드를 위해서 object 데이터 형태로 다중 css 속성을 넣을 수 있다.
<div :style="{ fontSize : '20px', marginTop : '10px' }">
<div :style="{ color : 'red' }">
자바스크립트에서 css 속성명은 대시기호를 쓸 수 없기 때문에 카멜케이스로 작성한다.
ajax 요청하려면
호환성 때문에 axios 사용하기로 함.
npm install axios
axios 요청하는 방법은 import 해오고 axios.get('/경로') 하면 됨.
<template>
<ContainerComp :data="data" />
<button @click="handleClick">더보기</button>
</template>
더보기 button 클릭시 axios 요청 메서드 작동
<script setup>
import postdata from '@/assets/postdata';
import axios from 'axios';
import { ref } from 'vue';
// data
const data = ref(postdata);
// methods
const handleClick = async () => {
const response = await axios.get('https://codingapple1.github.io/vue/more0.json');
data.value.push(response.data);
}
</script>
vue3 composition API 방식으로 코딩해봤다.
axios.post('URL', {name: 'KIM'}).then((res)=>{실행코드}).catch((err)=>{실행코드})
성공하면 then() 실행
실패하면 catch() 실행
get요청할때 쓴 것 처럼 async await 문법으로 then() 대신 결과를 변수에 담아 사용해도 된다.
(app.vue)
<script setup>
// tab 구분용 state
const step = ref(0);
</script>
<template>
// 하위 컴포넌트로 props 전달
<ContainerComp :step="step" />
</template>
(최상위 컴포넌트는 composition API로 script 작성했음)
step 값에 따라 tab이 변경되도록 UI를 구성했다.
props를 넘겨준 하위 컴포넌트에서 나머지를 세팅해보자.
(ContainerComp.vue)
<template>
<div>
<!-- 글목록 페이지 -->
<div v-if="step == 0">
<PostComp :data="data[idx]" v-for="(item, idx) in data" :key="idx" />
</div>
<!-- 필터선택페이지 -->
<div v-if="step == 1">
<div class="upload-image"></div>
<div class="filters">
<div class="filter-1"></div>
<div class="filter-1"></div>
<div class="filter-1"></div>
<div class="filter-1"></div>
<div class="filter-1"></div>
</div>
</div>
<!-- 글작성페이지 -->
<div v-if="step == 2">
<div class="upload-image"></div>
<div class="write">
<textarea class="write-box">write!</textarea>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
step: Number
}
}
</script>
각 섹션마다 wrap div에 v-if를 사용해 step을 구분해줬다.
이제 상위 컴포넌트의 step 값이 변경될 때 마다 각 값에 맞는 tab이 노출된다.
이미지 업로드한걸 HTML에 보여주려면 어떻게 해야할까?
이전 방식은 서버가 필요하다.
하지만 익스11 이후부터는 이미지 다루는 함수를 쓰면 된다.
브라우저에서 이미지 다루는 함수를 사용하면 서버로 보내기도 전에 내가 업로드한 이미지를 바로 띄워보고 조작할 수 있음.
둘중 사용하면 됨.
createObjectURL이 좀 더 가벼우니 이거 사용해보기로.
<ul class="footer-button-plus">
<input @change="uploadImage" type="file" id="file" class="inputfile" />
</ul>
업로드한 이미지를 받아오는 탬플릿이다.
해당 input 태그에 @change 이벤트리스너로 함수를 넘겨줬다.
const imageUrl = ref('');
const uploadImage = (e) => {
let files = e.target.files;
let url = URL.createObjectURL(files[0]);
imageUrl.value = url;
step.value = 1; // 이미지 노출 확인 페이지(tab) 이동
}
input에서 받아오는 e.target.files에는 업로드한 모든 파일 정보를 담고있다.
변수에 담아주자.
콘솔에 출력해보면 FileList라는 객체에 정보가 담겨오기 때문에 file[0] 식으로 배열 선택을 해줘야한다.
업로드한 이미지를 띄워보자.
URL.createObjectURL() 함수를 이용해서 가상 url을 생성해줬다.
가상 url을 변수에 담고 props로 하위 컴포넌트에 전달 해준다.
(app.vue)
<template>
<ContainerComp :url="imageUrl" />
</template>
(ContainerComp.vue)
<template>
<!-- 필터선택페이지 -->
<div v-if="step == 1">
<div class="upload-image" :style="{backgroundImage: `url(${url})`}"></div>
...
</div>
</template>
export default {
props: {
url: String,
}
}
<ul class="footer-button-plus">
<input type="file" id="file" multiple />
</ul>
input 태그에 multiple 속성을 추가해주면 이미지 업로드시 다중 선택이 가능하다.
<ul class="footer-button-plus">
<input type="file" id="file" accept="image/*" />
</ul>
accept 옵션을 사용하면 input 태그로 받아오는 파일 형식을 제한할 수 있다.
근데 사실 제한이라기 보다는 선택 화면에서 특정 형식만 보이도록 하는 거라서 이용자가 선택에서 바꿀 수 있다.
엄격한 파일 형식 제한을 위해서는 자바스크립트로 확장자를 검열하는 작업이 필요하다.
const uploadImage = (e) => {
let files = e.target.files;
console.log(files[0].type);
}
files[0].type하면 확장자 확인 가능.
내가 원하는 타입만 거를 수 있게 엄격히 작업하는게 좋다.
메인페이지에 Post 컴포넌트를 하나 더 추가하는게 글 발행 기능이다.
이용자가 업로드한 이미지에 작성한 글을 포함하여 게시물을 추가한다.
(app.vue)
<ul class="header-button-right">
<li v-if="step == 1" @click="step++">Next</li>
<li v-if="step == 2" @click="publish">발행</li>
</ul>
const publish = () => {
let newPost = {
내용들
};
data.value.unshift(newPost) // 새로 배열 추가
step.value = 0; // 메인 페이지로 돌아가기
}
unshift()는 배열의 맨 상단, 처음부분에 해당 자료를 추가해준다.
(index.html)
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cssgram/0.1.12/cssgram.min.css" integrity="sha512-kr3JaEexN5V5Br47Lbg4B548Db46ulHRGGwvyZMVjnghW1BKmqIjgEgVHV8D7V+Cbqm/VBgo3Rcbtv+mGLoWXA==" crossorigin="anonymous" />
인스타그램 이미지 필터기능을 위해 cssgram cdn 추가
link 태그를 직접 최상위 index.html에 추가해준다.
(ContainerComp.vue)
<template>
<!-- 필터선택페이지 -->
<div v-if="step == 1">
<div class="upload-image" :style="{backgroundImage: `url(${url})`}"></div>
<div class="filters">
<FilterBox :url="url" :filters="arr" v-for="arr in filters" :key="arr" />
</div>
</div>
</template>
<script>
...
data() {
return {
filters: [ "aden", "_1977", "brannan", ...]
}
},
</script>
상위컴포넌트에서 필터명들을 data()에 담고 v-for 사용해서 컴포넌트에 각 array들을 내려준다.
하위 컴포넌트에서 props로 받아온 내용대로 처리해주면 끝
(FilterBox.vue)
<template>
<div class="filter-item" :class="filters" :style="`background-image: url(${url})`"></div>
</template>
<script>
export default {
props: {
filters: String
}
}
html상으로 표현되는 클래스명은 filter-item :class명이 된다.
사진 위에 필터명을 표시해보자.
부모 컴포넌트에서 자식 컴포넌트로 쉽게 데이터 보내는 방법이 props 말고도 존재한다.
slot 사용하면 됨.
props를 등록하는 과정 없이 조금 더 직관적으로 보내고 사용할 수 있다.
react에서 컴포넌트 안에 사용하는 {children}과 같은 개념이다.
(상위컴포넌트)
<!-- <FilterBox :url="url" :filters="a" v-for="(a) in filters" :key="a" /> -->
<FilterBox :url="url" :filters="a" v-for="(a) in filters" :key="a">
{{ a }}
</FilterBox>
(하위컴포넌트)
<div class="filter-item" :class="filters">
<slot></slot>
</div>
출력된 화면을 보면 {{ a }} 로 전달한 필터 이름이 제대로 화면에 표시되고 있다.
그런데 slot이 props의 대체제가 되지 못하는 이유가 무엇일까?
slot 문법은 데이터바인딩을 html에 하고 싶을 때 사용한다.
속성에 데이터바인딩이 불가하다는 의미.
즉, :class 같은거에 내려받은 slot 데이터를 사용할 수 없다!
부모 컴포넌트 여기저기서 slot으로 데이터를 내려보냈을 경우,
자식 컴포넌트에서는 각 부모에 맞는 slot 노출란을 만들어야 한다.
<div>
<slot name="a"></slot>
<slot name="b"></slot>
</div>
slot name="" 으로 각 고유 slot 이름을 등록해주면 된다.
<div>
<template v-slot="a"> 데이터1 </template>
<template v-slot="b"> 데이터2 </template>
</div>
마찬가지로 부모 컴포넌트에서는 template v-slot="슬롯명"으로
알맞은 slot에 전송하도록 지정해주면 된다.
slot은 컴포넌트간 html을 담아서 전송하는 용도로만 사용하도록 하자.
slot 사용할 때 부모가 자식 데이터 필요한 경우 사용하는 slot props도 있다.
필터 박스를 클릭 시 해당 필터가 이미지에 적용되도록 해보자!
상위 컴포넌트의 상위 컴포넌트로 이벤트를 넘겨야한다.
$emit을 두번 중첩 사용하기보다 한번에 원하는 컴포넌트로 다이렉트로 전달하는 방법을 사용하자.
vue3에서는 mitt 라이브러리를 사용한다.
조부모는 물론 하위 컴포넌트, 형제 컴포넌트로도 데이터 전달이 가능하다!
npm install mitt
설치를 진행한다.
(main.js)
import mitt from 'mitt'
// emitter 생성
let emitter = mitt();
const app = createApp(App)
// 추가!
app.config.globalProperties.emitter = emitter;
app.use(router)
app.mount('#app')
mitt 라이브러리 세팅을 해준다.
자주 쓰는 라이브러리는 여기다 전부 등록해도 된다.
예시 ) axios 등록하는 법
app.config.globalProperties.axios = axios;
이러면 컴포넌트에서 import 해서 사용할 필요 없이 this.axios로 사용할 수 있다!
<button @click="fire">버튼</button>
template에 이벤트와 메서드를 연결해주고
methods: {
fire() {
this.emitter.emit('작명', '데이터')
}
},
메서드에 this.emitter.emit('작명', '전달하는 데이터') 형식으로 작성하여 다른 컴포넌트로 emit 해준다.
<script>
export default {
data() {
return {
};
},
mounted() {
this.emitter.on("event명", (param) => {코드실행}
);
};
</script>
this.emitter.on("이벤트명", ()=>{})으로 수신하면 된다.
이벤트명을 누군가 실행하면 내부 ()=>{} 함수 코드란을 실행하라는 의미다.
아래는 vue3 composition API에서 수신하는 방법.
import { getCurrentInstance } from 'vue'
export default{
setup(){
const internalInstance = getCurrentInstance();
const emitter = internalInstance.appContext.config.globalProperties.emitter;
emiiter.on('event명', (param) => {
// 내부 코드 실행
})
...
},
...
}
근데 많이 쓰다보면 이벤트명 겹치고 관리가 힘들어진다.
그러므로 최종적으로는 Vuex로 상태관리 하는게 좋다.