데이터를 호출하지 않았다.
이유 : 최초 '/browse/movie'로 이동하면, MoviePage 컴포넌트가 생성되고, created() 훅에 있는 fetchData 함수가 실행되어서 데이터를 가져온다.
하지만, 라우터 링크를 클릭해서 다시 기존 경로로 이동하면, 컴포넌트가 생성되어 있으므로 created 훅이 실행되지 않고, 데이터를 로드하지 않는다.
해결책 : watch에서 route의 변화를 감지하는 방법이 있다.
참고 링크 : https://router.vuejs.org/guide/advanced/navigation-failures.html#detecting-navigation-failures
: https://stackoverflow.com/questions/53608244/how-to-add-multiple-data-types-for-vuejs-props
props: {
type: [Number, String, Array]
}
참고 링크 : https://mygumi.tistory.com/330
: 프로토타입이 아닌 해당 객체 자체가 가지고 있는 속성을 가지고 판단한다.
: delete 연산자를 사용해서 제거한다.
: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/delete
const person = {
name: 'sam',
age: 12,
}
// age 속성 제거
delete person.age;
: https://ko.javascript.info/bubbling-and-capturing
=> input에 click 이벤트 핸들러를 적용하면 label을 클릭할때 발생한 클릭이벤트를 핸들링 할 수 있다.
=> history.replaceState(this.$data, null, 현재 경로)를 이용해서 history.state에 현재 데이터들을 받고,
페이지를 로드했을때 state가 있는지 검사, 있으면 $data에 state안에있는 데이터를 합쳐준다.
=> 따라서 history가 있다면, API를 호출하지 않고, 이전에 있는 데이터를 그대로 보존할 수 있도록 created 훅에서 분기처리가 필요하다.
// URL 이동
goURL(url) {
window.location.href = url;
},
: https://nuli.navercorp.com/community/article/1132804
: https://www.boostcourse.org/web344/lecture/47663?isDesc=false
: https://haneunbi.github.io/2020/06/01/css-ir/
.blind {
/* 레이아웃에 영향을 끼치지 않도록 */
position: absolute;
/* 스크린 리더가 읽을 수 있도록 */
width: 1px;
height: 1px;
/* 눈에 보이는 부분을 제거 */
clip: rect(0 0 0 0);
margin: -1px;
overflow: hidden;
}
.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
.sr-only {
position: absolute; /* position: absolute/fixed 에서만 clip 속성 작동 */
margin: -1px; /* 부트스트랩에선 안씀 */
width: 1px;
height: 1px;
padding: 0;
border: 0;
white-space: nowrap;
overflow: hidden; /* overflow: visible 이면 clip 속성 작동 안됨 */
clip: rect(0, 0, 0, 0);
clip-path: inset(50%); /* H5BP에선 안씀 */
}
: 없다!
: https://stackoverflow.com/questions/6393827/can-i-nest-a-button-element-inside-an-a-using-html5
: https://stackoverflow.com/questions/34253779/tomcat-server-error-port-8080-already-in-use
// 매장 리스트 조회
async fetchStoreList() {
try {
// this.fetches.storeList = false;
const response = await this.$store.dispatch("network/request", {
method: "post",
url: '/store/shop/getShopList',
data: {
areaType: '',
searchText: '',
page: '',
limit: '',
},
});
const _data = response.data;
if(response.code == CODE.SUCCESS) {
console.log(response, _data, 'fetchStoreList');
} else {
// throw를 통해 에러를 발생시킴
throw new Error(response.message);
}
}
catch(ex) {
console.error("fetchAvailableStores error...", ex.message); // response.message가 나온다!
} finally {
// this.fetches.storeList = true;
}
}
참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Control_flow_and_error_handling
=> try...catch 문법 중첩하기 탭 확인!!!
throw {
code: response.code,
message: response.message,
data: response.data
}
=> 해당 함수의 catch 문에서 다시 에러를 throw
=> catch문에서 에러를 캐치
=> throw 한 객체에서 원하는 데이터를 뽑아내서 에러 핸들링!
=> 얼럿을 띄운다거나.. 로그를 찍는다던가..
=> 에러 핸들링 함수에 해당 에러 객체를 전달하는것이 효과적! switch case문!!
: https://server0.tistory.com/57
참고 링크: https://jeonghwan-kim.github.io/2018/05/12/extended-component.html
: https://developer.mozilla.org/ko/docs/Web/API/Event/isTrusted
: 사용자에 의해 발생했는지 여부 체크
: 사용자에 의해 발생했으면 true, 아니면 false;
참고 링크: https://stackoverflow.com/questions/1248081/how-to-get-the-browser-viewport-dimensions
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
Vue.use(Vuex);
const requireModule = require.context(
".",
true,
/^((?!\.unit\.).)*.index\.js$/
);
const rootModule = { modules: {} };
requireModule.keys().forEach(fileName => {
if (fileName === "./index.js") return;
const modulePath = fileName
.replace(/^\.\//, "")
.replace(/\.\w+$/, "")
.split(/\//)[0];
rootModule.modules[modulePath] = requireModule(fileName).default;
});
export default new Vuex.Store({
modules: {
...rootModule.modules,
},
=> ✨ axios 게시판으로 옮기기
const form = new FormData();
form.append('id', 'id');
form.append('password', 'password');
const convert = await axios({
method: 'post',
url: '',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data : form
});
=> ✨ Vue 게시판으로 옮기기
: https://kdydesign.github.io/2019/04/06/vuejs-vuex-helper/
=> ✨ Vue 게시판으로 옮기기
<template>
<main>
<header-component></header-component>
<lnb-component></lnb-component>
<gnb-component></gnb-component>
<section class="content">
<slot name="content"></slot>
</section>
<footer-component></footer-component>
</main>
</template>
//some.vue
<template>
<div>
<lay-out>
<tmeplate slot="content">
</template>
</lay-out>
</div>
</template>
: https://developer.mozilla.org/ko/docs/Web/API/Node/contains
: 노드가 특정 노드를 포함하고 있는지 여부를 판단
(return true/false)
=> ✨ Vue 게시판으로 옮기기
: https://stackoverflow.com/questions/36170425/detect-click-outside-element
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
// here I check that click was outside the el and his children
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
});
<div v-click-outside="onClickOutSide"></div>
// directives
directives: {
'click-outside': {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
if (!(el == event.target || el.contains(event.target))) {
vnode.context[binding.expression](event);
}
};
// 바인드되고 그 다음 사이클에 적용!
// https://developer.mozilla.org/ko/docs/Web/API/Window/requestAnimationFrame
// * requestAnimationFrame()을 사용하지 않았을 경우에는 달력 컴포넌트가 생기는 즉시 클릭 이벤트가 발생해서 모달이 닫혀버렸다.
window.requestAnimationFrame(() => {
document.body.addEventListener('click', el.clickOutsideEvent);
});
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
},
}
}
: isPageLoaded라는 변수를 초기값을 false로 세팅한다.
페이지에서 필수적으로 호출하는 API가 모두 로드되면, isPageLoaded의 값을 true로 변경한다.
특정 함수에서 이 값이 false인지를 체크해서 동작을 제어한다.
: document.activeElement
: https://stackoverflow.com/questions/497094/how-do-i-find-out-which-dom-element-has-the-focus
// 이미지 prefix 교체
replaceGoodsImageUrl(url = '', size = 'default') {
if( !url ) return '';
let resultUrl = '';
// 숫자3개 + _, 숫자2개 + _로 시작하는 문자열 검사
const regex = /(^\d{3}\_|^\d{2}\_)/;
const image_sizes = {
small: '75_',
default: '216_',
middle: '320_',
};
const prefix = image_sizes[size];
// 찾은 사이즈가 있을 경우
if( prefix ) {
if( regex.test(url) ) {
// prefix 있을 경우
resultUrl = url.replace(regex, prefix);
} else {
// 없을 경우
resultUrl = prefix + url;
}
}
else {
// 없을 경우
if( regex.test(url) ) {
// prefix 있을 경우
resultUrl = url.replace(regex, '');
} else {
// 없을 경우
resultUrl = url;
}
}
return resultUrl;
},
: https://developer.mozilla.org/ko/docs/Web/API/Window/beforeunload_event
: https://gist.github.com/VassilisPallas/edc38a3e9489042e86c413241c0d9819
: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
return true/false
: 특정 문자열을 변환한 새로운 문자열을 반환한다.
배열인 경우 : v-for안에서 사용했을 경우
단일 데이터인 경우 : 그 외의 케이스
<keep-alive>와 <component>
: 이거 되게 신기
: https://betterprogramming.pub/how-to-create-your-own-event-emitter-in-javascript-fbd5db2447c4
이벤트 등록 및 사용 방식..
addEventListener(name, cb) {
if(this.#has.call(this.#events, name)) this.#events[name].push(cb);
else this.#events[name] = [cb];
}
eventCall(name) {
switch(name) {
case "timer":
if(this.#has.call(this.#events, name)) {
for(let cb of this.#events[name]) {
/**
* @callback fbTimerTimerCallback
* @param {number} timestamp
*/
cb(this.timestamp);
}
}
break;
case "completed":
if(this.#has.call(this.#events, name)) {
for(let cb of this.#events[name]) {
/**
* @callback fbTimerCompletedCallback
* @param {boolean} isCompleted
*/
cb(true);
}
}
break;
}
}
링크 : https://betterprogramming.pub/how-to-create-your-own-event-emitter-in-javascript-fbd5db2447c4
링크 : https://ko.javascript.info/try-catch
3.1 Vuex 모듈에 있는 getters 사용하기
return this.$store.getters["모듈명/getters 속성 명"];
공식 문서 : https://apis.map.kakao.com/web/documentation/#Marker
samples: https://apis.map.kakao.com/web/sample/basicMap/
4.1 kakao.maps.load(callback) => 스크립트가 동적으로 로드된 후에 실행되도록
참고 : https://navydoc.tistory.com/21
4.2 확대 축소 이벤트
: https://apis.map.kakao.com/web/sample/addMapZoomChangedEvent/
4.3 오버레이
4.4 지도/스카이뷰
: https://apis.map.kakao.com/web/sample/addMapControl/
4.5 마커 클릭 시 인포윈도우 예제
: https://apis.map.kakao.com/web/sample/addMarkerClickEvent/
카카오 지도 API를 하면서
: 카카오 map을 생성하기 위해서 container를 넘겨야 하는데, ref로 지정한 엘리먼트를 찾지 못해서 에러가 발생.
: mounted훅 혹은 v-if로 조건이 걸려있는 경우 해당 영역이 완전히 렌더링된 이후에 실행해야함.
스크립트를 동적으로 불러올 경우
: Promise를 이용해서 비동기 처리를 할 수 있다.