Astro 筆記 (3):在 Astro 中用 nanostores 共享全局變數

Astro

Astro 本身是沒有共享全局變數的功能,不過文件中建議使用 nanostores 這個套件,可以在純 JS 和各種前端框架中使用,以及支援 TypeScript,且使用上也很簡單。

在 Astro 中我對 nanostores 使用方式的理解是,因為 Astro 預設每個頁面都會重新初始化,因此狀態管理就可以非常簡單,只需管理跨元件的共用狀態,不用管切換路由後如何清理資料。

這篇文章記錄了一下我第一次用 nanostores 做的範例,是訊息提示元件的狀態管理,可以呼叫顯示訊息提示的函數,並在 2 秒後自動消失。

安裝 nanostores

yarn add nanostores

實作成功訊息功能

首先先定義一個 messageStateStoresrc/stores/message.ts 中,messageStateStore 保存了顯示訊息類型和訊息內容,然後寫一個 showMessage() 函數來設定呼叫時要顯示的類型和內容,並在 2 秒後會自己消失:

import { atom } from 'nanostores'

export type MessageType = 'success' | 'info'

export const messageStateStore = atom({
  type: null as MessageType | null,
  content: '',
})

export function showMessage(type: MessageType, content: string) {
  messageStateStore.set({ type, content })

  setTimeout(() => {
    messageStateStore.set({ type: null, content: '' })
  }, 2000)
}

然後需要增加一個 src/components/MessagesContainer.astro 用來顯示訊息提示,會監聽剛才的 messageStateStore 更新訊息內容,有內容存在就顯示出來:

<div class="messages-container"></div>

<script>
import { messageStateStore } from '@/stores/message'

messageStateStore.subscribe(value => {
  const messagesContainer = document.querySelector('.messages-container') as HTMLElement
  if (value.type) {
    messagesContainer.innerHTML = `
      <div class="message message-${value.type}">
        ${value.content}
      </div>
    `
  } else {
    messagesContainer.innerHTML = ''
  }
})
</script>

這樣一個簡易的訊息提示功能就完成了~ 現在用一個複製連結按鈕當範例,用法就是綁一個點擊事件,然後呼叫 showMessage() 函數即可:

<script>
import { showMessage } from '@/stores/message'

const btnCopy = document.querySelector('.btn-copy') as HTMLButtonElement
btnCopy.addEventListener('click', () => {
  // 複製連結...
  showMessage('success', '連結已複製')
})
</script>

只是現在如果短時間內觸發多次的話,舊的 setTimeout() 會干擾到新的狀態呈現,這邊我們可以加一個 timer,在設定 setTimeout() 之前先清除掉舊的 timer

let timer = null as ReturnType<typeof setTimeout> | null

export function showMessage(type: MessageType, content: string) {
  messageStateStore.set({ type, content })

  if (typeof timer === 'number') {
    clearTimeout(timer)
  }

  timer = setTimeout(() => {
    messageStateStore.set({ type: null, content: '' })
  }, 2000)
}

參考資料