본문 바로가기
Python/FastAPI

Svelte - Store(스토어)

by 깐테 2024. 1. 30.

 

https://wikidocs.net/176723

 

3-03 스토어

* `[완성 소스]` : [https://github.com/pahkey/fastapi-book/tree/v3.03](https://github.com/pahkey/fastapi…

wikidocs.net

* 해당 프로젝트는 위키독스를 참조합니다.

프로젝트의 모든 내용을 포스팅하지 않으므로 이전 또는 자세한 설명은 위키독스 참조.


FastAPI & Svelte - 서비스 개발 2(스토어, 날짜 표시)

스토어

svelte/store • Docs • Svelte

 

svelte/store • Docs • Svelte

Edit this page on GitHub On this page On this page The svelte/store module exports functions for creating readable, writable and derived stores. Keep in mind that you don't have to use these functions to enjoy the reactive $store syntax in your components.

svelte.dev

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

 

Stores / Writable stores • Svelte Tutorial

Stores / Writable stores a. Basicsb. Adding datac. Dynamic attributesd. Stylinge. Nested componentsf. Making an appa. Assignmentsb. Declarationsc. Statementsd. Updating arrays and objectsa. Declaring propsb. Default valuesc. Spread propsa. If blocksb. Else

svelte.dev

위 페이지는 스벨트 스토어 생성 예제

 

스토어 변수 사용하기

// 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/

 

[Svelte] Store

Svelte의 상태 관리를 지원하는 store를 제공합니다.

beomy.github.io

https://velog.io/@heejin-k/Svelte-%EC%8A%A4%EB%B2%A8%ED%8A%B8%EC%97%90%EC%84%9C%EC%9D%98-%EC%A0%84%EC%97%AD%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-Store

 

Svelte ] 스벨트에서의 전역상태관리 Store

svelte에는 전역 상태를 저장하는 store가 계층구조에 포함되어있어 유용하게 사용할 수 있습니다.

velog.io

 

반응형