* 해당 프로젝트는 위키독스를 참조합니다.
프로젝트의 모든 내용을 포스팅하지 않으므로 이전 또는 자세한 설명은 위키독스 참조.
FastAPI & Svelte - 서비스 개발 2(스토어, 날짜 표시)
스토어
svelte의 스토어(store)를 사용하면 변수의 값을 전역적으로 저장할 수 있기 때문에 라우팅되는 페이지에 상관없이 스토어에 저장된 변수를 사용할 수 있다.
svelte는 상태 관리를 store를 통해 관리한다. 값이 변경될 때 마다 subscribe 함수를 호출하는 단순 객체이다.
기존 props를 이용하여 통신할 경우 최상단 부모 컴포넌트에서 최하위 컴포넌트까지 값을 전달하거나 전달받을 경우 각 컴포넌트마다 props를 할당해주어야 한다.
이러한 경우 스토어를 만들어(ex. store.js ..) 호출할 변수를 스토어에 저장하여 사용하면 간편하게 데이터를 전달할 수 있다.
스토어 변수 생성
// proj/myapi/frontend/src/lib/store.js
import { writable } from 'svelte/store'
export const page = writable
- writable(0)→ 초깃값을 0으로 설정한다.
https://svelte.dev/tutorial/writable-stores
위 페이지는 스벨트 스토어 생성 예제
스토어 변수 사용하기
// Home.svelte
<script>
import fastapi from "../lib/api"
import { link } from 'svelte-spa-router'
import { page } from '../lib/store'
let question_list = []
let size = 10
// let page = 0
let total = 0
$: total_page = Math.ceil(total/size)
function get_question_list(_page) {
let params = {
page: _page,
size: size,
}
fastapi('get', '/api/question/list', params, (json) => {
question_list = json.question_list
$page = _page
total = json.total
})
}
get_question_list($page)
</script>
...
</table>
<!-- 페이징처리 시작 -->
<ul class="pagination justify-content-center">
<!-- 이전페이지 -->
<li class="page-item {$page <= 0 && 'disabled'}">
<button class="page-link" on:click="{() => get_question_list($page-1)}">이전</button>
</li>
<!-- 페이지번호 -->
{#each Array(total_page) as _, loop_page}
{#if loop_page >= $page-3 && loop_page <= $page+3}
<li class="page-item {loop_page === $page && 'active'}">
<button on:click="{() => get_question_list(loop_page)}" class="page-link">{loop_page+1}</button>
</li>
{/if}
{/each}
<!-- 다음페이지 -->
<li class="page-item {$page >= total_page-1 && 'disabled'}">
<button class="page-link" on:click="{() => get_question_list($page+1)}">다음</button>
</li>
</ul>
<!-- 페이징처리 끝 -->
<a use:link href="/question-create" class="btn btn-primary"> 질문 등록하기</a>
</div>
<!-- <ul>
{#each question_list as question}
<li><a use:link href="/detail/{question.id}">{question.subject}</a></li>
{/each}
</ul> -->
- 스토어 변수 page는 $page 처럼 변수명 앞에 $ 기호를 붙여 참조해야 한다.
- 페이지 최초 로딩 시 get_question_list(0) → get_question_list($page)
지속성 스토어(persistent store)
위와 같이 스토어 변수를 사용해도 브라우저를 새로고침 했을 경우 스토어 변수가 초기화 된다.
스토어 변수의 초기화 현상은 브라우저 새로고침 외에도 js의 location.href 또는 a 태그를 통한 링크를 호출할 경우에도 발생한다. 따라서 이러한 문제를 해결하려면 지속성을 지닌 스토어가 필요하다.
// src/lib/store.js
import { writable } from 'svelte/store'
const persist_storage = (key, initValue) =>{
const storedValueStr = localStorage.getItem(key)
const store = writable(storedValueStr != null ? JSON.parse(storedValueStr) : initValue)
store.subscribe((val) => {
localStorage.setItem(key,JSON.stringify(val))
})
return store
}
export const page = persist_storage("page", 0)
- persist_storage 함수는 이름(key), 초기값(initValue)을 입력받아 writable 스토어를 생성하여 리턴. localStorage를 사용하여 지속성을 갖도록 했다.
- localStorage에 해당 이름의 값이 이미 존재하는 경우 → 기존의 값으로 리턴 localStorage에 저장하는 값은 항상 문자열로 유지하기 위해 JSON.stringify를 사용하고 읽을 때는 JSON.parse 사용
- store의 subscribe 함수는 스토어에 저장된 값이 변경될 때 실행되는 콜백 함수. store 변수의 값이 변경될 때 localStroage 값도 함께 변경
로고
// Navigation.svelte
<script>
import { link } from 'svelte-spa-router'
import { page } from "../lib/store"
</script>
<!-- 네비게이션바 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a use:link class="navbar-brand" href="/" on:click="{() => {$page = 0}}">FastAPI & Svelte</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon" />
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a use:link class="nav-link" href="/user-create">회원가입</a>
</li>
<li class="nav-item">
<a use:link class="nav-link" href="/user-login">로그인</a>
</li>
</ul>
</div>
</div>
</nav>
- 로고 클릭 시 첫 페이지로 이동
// Home.svelte
<script>
(... 생략 ...)
$: get_question_list($page)
</script>
(... 생략 ...)
- $: 반응형 기호. 해당 기호는 변수 뿐만 아니라 함수나 구문 앞에 추가하여 사용 가능
- page 값이 변경될 경우 get_question_list 함수도 다시 호출
질문 상세 페이지에 질문 목록 버튼 추가
// src/routes/Detail.svelte
<script>
import fastapi from "../lib/api"
import Error from "../components/Error.svelte"
import { push } from 'svelte-spa-router'
...
</script>
<div class="container my-3">
<!-- 질문 -->
<h2 class="border-bottom py-2">{question.subject}</h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{question.content}</div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2">
{question.create_date}
</div>
</div>
</div>
</div>
<button class="btn btn-secondary" on:click="{() => {
push('/')
}}">목록으로</button>
<!-- 답변 목록 -->
...
- 목록으로 버튼 추가
날짜 표시하기
자바스크립트의 moment 라이브러리 사용.
moment 설치하기
frontend > npm install moment
질문 목록에 적용하기
// Home.svelte
<script>
import fastapi from "../lib/api"
import { link } from 'svelte-spa-router'
import { page } from "../lib/store"
import moment from 'moment/min/moment-with-locales'
moment.locale('ko')
...
</script>
<div class="container my-3">
<table class="table">
<thead>
<tr class="table-dark">
<th>번호</th>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
{#each question_list as question, i}
<tr>
<td>{i+1}</td>
<td>
<a use:link href="/detail/{question.id}">{question.subject}</a>
</td>
<td>{moment(question.create_date).format("YYYY년 MM월 DD일 hh:mm a")}</td>
</tr>
{/each}
</tbody>
</table>
...
- 한국 날짜 형식 사용을 위해 ko 사용.
- 포맷은 YYYY MM DD hh:mm a
질문 상세 화면에 적용하기
// Detail.svelte
<script>
import fastapi from "../lib/api"
import Error from "../components/Error.svelte"
import { push } from 'svelte-spa-router'
import moment from 'moment/min/moment-with-locales'
moment.locale('ko')
export let params = {}
let question_id = params.question_id
let question = {answers:[]}
let content = ""
let error = {detail: []}
...
</script>
<div class="container my-3">
<!-- 질문 -->
<h2 class="border-bottom py-2">{question.subject}</h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{question.content}</div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2">
{moment(question.create_date).format("YYYY년 MM월 DD일 hh:mm a")}
</div>
</div>
</div>
</div>
<button class="btn btn-secondary" on:click="{() => {
push('/')
}}">목록으로</button>
<!-- 답변 목록 -->
<h5 class="border-bottom my-3 py-2">{question.answers.length}개의 답변이 있습니다.</h5>
{#each question.answers as answer}
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{answer.content}</div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2">
{moment(answer.create_date).format("YYYY년 MM월 DD일 hh:mm a")}
</div>
</div>
</div>
</div>
{/each}
...
* 참조한 블로그
https://beomy.github.io/tech/svelte/store/
반응형
'Python > FastAPI' 카테고리의 다른 글
점프 투 FastAPI 추가 기능 - 댓글 (1) | 2024.02.05 |
---|---|
점프 투 FastAPI 추가 기능 - 답변 페이징 (0) | 2024.02.02 |
FastAPI & Svelte - 질문 목록 화면 (0) | 2024.01.27 |
FastAPI & Svelte - 질문 목록 API (1) | 2024.01.26 |
FastAPI & Svelte - 프로젝트 기초 진행하기 (0) | 2024.01.24 |