Svelte 는 자체적으로 상태 관리 수단 - store 를 제공한다.
svelte/store
에 대한 자세한 내용은 아래 링크들을 참조하숑.
내가 svelte/store
를 쓰려는 이유는?
사실 그냥 blog 화면만 만든다고 하면 크게 필요는 없을 듯한데 메뉴 생성하는 admin 화면도 만들다 보니 메뉴 생성 후 화면을 업데이트 해야 할 필요가 있다.
뭐 상태 관리 수단은 대부분의 플젝에서 필요하니까 겸사 겸사 익힐겸도 해서 한번 써 봄.
일단 svelte/store
를 쓰기 전에, 데이터를 받아올 수 있도록 REST API 를 사용하는 함수을 구현하자.
api/api.js
를 생성하고 fetch
를 호출하는 fetchWrap
함수를 작성하자. (어떤 api 를 호출하든 fetchWrap
함수를 호출하도록......)
const address = 'http://132.226.16.67:8080';
const getQueryString = query => {
let str = '';
try {
for (let key in query) {
if (str.length > 0) {
str += '&';
}
str += (key + '=' + query[key]);
}
} catch (e) {
console.error(e);
str = '';
}
return str;
};
const fetchWrap = async (options) => {
const { url, method, query, body, headers } = options;
try {
return (await fetch(`${address + url}?${getQueryString(query)}`, {
method,
headers: {
'Content-Type': 'application/json',
...headers
},
body: JSON.stringify(body)
})).json();
} catch (e) {
console.error(e);
}
};
fetchWrap
에서는 api 호출에 필요한 옵션들을 파라미터로 받을 수 있다.
address
과 조합하여 호출함address
+ url 과 조합하여 호출함getQueryString
함수에서 string 으로 변환JSON.stringify
를 호출해 string 으로 변환fetchWrap
아래에 바로 메뉴 관련 API 들을 호출하는 함수들을 작성했고, 해당 함수들은 다른 Component 에서 써야 하기 때문에 export
로 뺐다.
뭐 api/menu.js
같은 파일로 따로 정의 할수도 있지만... 귀찮으니 일단은 한꺼번에 작성
export const getMenu = async query => {
return await fetchWrap({
url: '/menu',
method: 'GET',
query
});
};
export const postMenu = async body => {
return await fetchWrap({
url: '/menu',
method: 'POST',
body
});
};
export const patchMenu = async body => {
return await fetchWrap({
url: '/menu',
method: 'PATCH',
body
});
};
export const deleteMenu = async body => {
return await fetchWrap({
url: '/menu',
method: 'DELETE',
body
});
};
호출해보니 잘된다.
이제 메뉴 데이터의 상태 관리를 할 수 있는 코드를 작성하자.
store/menu.js
파일안에 메뉴 데이터 상태 관리 코드를 작성하자. (svelte/store
에 대한 자세한 사용법은 맨 위의 링크 참조)
해당 파일에서는 getMenu
를 호출하여 메뉴 데이터를 가져오고 store 에 set 하는 로직만 들어가도록 하자.
또한 외부 Component 에서는 메뉴 데이터 참조, 변화 감지, 업데이트 요청 등만 할 수 있도록 subscribe
, reset
함수만 제공한다.
import { writable } from 'svelte/store';
import { getMenu } from '../api/api.js';
const { subscribe, set } = writable([]);
const reset = async () => {
const response = await getMenu();
if (response && response.data) {
set(response.data);
}
};
export default menu = { subscribe, reset };
svelte/store
에서 제공하는 writable
를 호출하면 store 가 생성되고 store 에 접근할 수 있는 set
, update
, subscribe
함수를 제공한다.
reset
함수에서는 getMenu
호출 후 set
을 통해 메뉴 데이터를 업데이트하고, 메뉴 데이터가 업데이트 되면 subscribe
함수를 통해 메뉴 데이터 업데이트 소식을 알린다.
뭐 subscribe
사용법을 작성하기 전에, 메뉴 데이터를 tree 구조로 만들자. 서버에서 주는 메뉴 데이터는 1차원 배열 형태이기 때문에 tree 구조로 변경해서 사용할 필요가 있음.
const getMenuTree = data => {
const result = [];
data.map(target => {
// 부모 걸기
if (target.parent) {
target.parent = data.filter(t => t._id === target.parent)[0];
} else {
result.push(target);
}
// 자식들 걸기
if (target.children?.length > 0) {
target.children = data.filter(t => target.children.includes(t._id));
}
});
return result;
};
const reset = async () => {
const response = await getMenu();
if (response && response.data) {
set(getMenuTree(response.data));
}
};
추가로 메뉴 데이터는 언제 init 해야 할까?
뭐 menu/menu.js
안에서 바로 해도 되긴 하는데, 아예 SPA 가 처음 시작할때 init 하는 지점을 만들어서 거기서 init 을 해보자.
App.svelte
import { initStore } from './store/store.js';
onMount(() => {
initStore();
});
store/store.js
import menu from './menu.js';
export const initStore = () => {
menu.reset();
};
이제 AdminMenu.svelte
에 메뉴 데이터 관련 로직을 추가하면서 이 메뉴 데이터를 어떻게 참조하고 구독하는지 알아보자.
store/menu.js
를 import 하면 메뉴 데이터를 두가지 방법으로 참조할 수 있다.
첫번째는 subscribe
함수 호출, 두번째는 svelte 에서 자동으로 생성하는 참조변수 - $menu
를 사용해서 데이터를 알 수 있다.
둘 중에 하나만 써도 되고 둘 다 써도 된다. 다만 $menu
를 <script>
태그 안에서 쓸 경우에는 데이터 변화 감지를 못하므로 <script>
태그 안에서 데이터 변화를 감지하려면 subscribe
를 써야 한다.
import menu from '../../store/menu.js';
.....
const init = value => {
if (value === 'parent') {
// $menu 를 따로 정의하지 않고 그냥 사용 - 해당 시점의 데이터
parents = [{list: $menu, select: null}];
parents.initTime = (new Date()).getTime();
}
if (value === 'info') {
name = '';
}
};
// 메뉴 데이터 변화를 감지하기 위해 subscribe 사용
// value 가 메뉴 데이터임
menu.subscribe(value => {
init('parent');
});
참고로 관찰 결과 최초에는 subscribe
이 두번 호출된다. (맨 처음 초기화시 호출 - []
, 메뉴 데이터 가져온 이후에 한번 - [......]
)
어쨌든 AdminMenu.svelte
에서는 자동으로 메뉴 데이터 세팅을 하게 된다.
추가로 메뉴 데이터 생성 버튼 눌렀을때 실제로 postMenu
를 호출하고, 메뉴 생성 성공시 데이터를 reset
하는 코드를 추가하자.
import { postMenu } from '../../api/api.js';
const addMenu = async () => {
if (!name) {
alert('메뉴명 입력하숑~');
return;
}
let parent = null;
for (let i = parents.length - 1; i >= 0; i--) {
if (!!parents[i].select?._id) {
parent = parents[i].select._id;
}
}
// 실제 메뉴 생성 - api 호출
const response = await postMenu({name, parent});
if (response?.success) {
alert(`${name} 메뉴 생성에 성공하였습니다.`);
// 메뉴 생성 이후 메뉴 데이터 다시 가져옴
menu.reset();
name = '';
} else {
alert(`${name} 메뉴 생성에 실패하였습니다.`);
}
};
menu.reset()
호출하면 메뉴 데이터가 업데이트 되고 subscribe
함수 - 정확히는 subscribe
함수로 등록한 listener 함수가 호출되고 화면을 초기화한다.
글쓰기가 힘들다. ㅜㅜ
AdminMenu.svelte
의 완성은 담 글에서 하자.