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 의 완성은 담 글에서 하자.