ToDSung/Note-Blog-Github

探索 Vue SWRV 的快取函式庫

Opened this issue · 0 comments

SWR: stale-while-revalidate

今天會用到的新東東

  1. SWRV: 支援 Vue3 的 SWR 套件
  2. composition API: Vue3 的新語法
  3. json server: 取代平常 express mock server 的接近 restful api 方式假後端

SWR: stale-while-revalidate (好像中文叫非同步更新快取)

SWR 會在你要從 API 取得資料的時候先確認之前有沒有取過了,如果有取過就先送前一次的給你,等到 API 把資料送到前端的時候,再確認資料一不一樣,如果不一樣的話就再把新的資料存起來,然後送給你更新畫面!

基本款程式碼

📃 /src/composition/HelloWorld.vue

import useSWRV from 'swrv' 

const fetcher = (url) => { 
  return fetch(url).then(r => r.json())
} 
// 使用 const let 宣告 function 時 hoisting 的行為 不同於 var ,記得宣告於使用之前。
// 不然就直接使用 function fetcher () {} 宣告就好囉!

setup() {
  const { data: articles, error: articlesError } = useSWRV('/articles', fetcher)
  // data 取得成功時會是 data,失敗時是 undefined
  // error 取得成功時是 undefined,失敗時是 message
  // 這兩個變數都是 ref (ref 在 composition api 中才能響應資料到畫面上)
  // fetcher 是一個 return promise 的 function
  return {
    articles,
    articlesError,
  }
}

為什麼要建立 fetcher 這個函數

  1. fethcer 可以做為一個抽象函數重複使用,而且不用去關注底下用的 library 是原生地 fetch 還是 axios 之類的 promise function
  2. fetcher 中的 url 參數會被做為 SWRV 的 cache key

測試 useSWRV 的效用

  1. 建立另一個 components
  2. 與第一個 components 引入同一個 fetch api
    📃 /src/components/Count.vue
import { fetcher } from '../utils/fetcher.js'
import useSWRV from 'swrv'

export default {
  name: 'Count',
  setup() {
    const { data: articles } = useSWRV('/articles', fetcher)
 
    return {
      articles
    }
  },
}

📃 /src/utils/fetcher.js

export const fetcher = (url) => {
  console.log('fetching ...'); // ADD
  return fetch(url)
    .then(r => r.json())
}
  1. 同時 render 時,正常情況下會呼叫兩次 API 資訊
  2. 可以從 console.log 得知成功使用了 cache

Revalidation 特性

Revalidation 表示的是特定情況下,不會使用 cache 處理 request

Q: 什麼情況下會使用 cache ?
A: 一連串的相同 promise function 時, useSWRV 能直接回傳 cache 回來
(promise return 表示還沒得到資料)

📃 /src/composition/HelloWorld.vue
新增 setTimeOut 讓 Count 元件延遲出現

import { fetcher } from '../utils/fetcher.js'
import { ref } from 'vue'
import useSWRV from "swrv";
import Count from './Count.vue'

export default {
  name: 'App',

  components: { Count },

  setup() {
    const { data: articles, error: articlesError } = useSWRV('/articles', fetcher)
    const showCount = ref(false) // 用 ref 建立的變數,才能動態響應到 template 中
    setTimeout(() => showCount.value = true, 3000) // ref 的值必須 .value 才能修改
 
    return {
      articles,
      articlesError,
      showCount,
    }
  },
}

這種情況下元件不會使用 cache 的資料,
原因是 Count 元件中的 fetch function 跟 HelloWorld 中的 fetch function
不是一個累積的未處理完成的 promise return (Event loop 那件事)
所以會被視作是不同次的呼叫。

(附帶一提在切換分頁回來時, useSWRV 也會幫我們重新呼叫 API,來取得最新資料)

Mutation

Q: 候我們可能需要強制更新資料時,該怎麼做呢?

A: 透過呼叫 mutate 這個 useSWRV的 回傳 function,可以送出強制的 request,來避免持續 cache

export default {
  name: 'App',

  components: { Count },

  setup() {
    const { 
      data: articles, 
      error: articlesError, 
      mutate: mutateArticles
    } = useSWRV('/articles', fetcher)

    const BtnClickedCount = ref(0)

    const updateArticles = () => {
      mutateArticles() 
      BtnClickedCount.value += 1
    }
    
    const showCount = ref(false) 
    setTimeout(() => showCount.value = true, 3000)
 
    return {
      articles,
      articlesError,
      showCount,
      updateArticles,
      BtnClickedCount,
    }
  },
}

POST Request

更新資料後避免快取的做法如下

const updateArticles = async () => {
  await fetch('http://localhost:3000/articles', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      "title": 'Untitled - ' + new Date().toString(),
      "author": "Andy",
      "link": "https://vuemastery.com/blog",
    })
  })
  mutateArticles() 
  BtnClickedCount.value += 1
}

Centralized Store

若需要一個全域的 store 機制(VueX),也可以透過 SWRV 做到

const { 
  data: hideList, // 這個 hideList 是一個 ref 預設值為空陣列
  mutate: mutateHideList 
} = useSWRV('hideList', () => []) // 也可以不用回傳 promise function

const visibleArticles = computed(() => {
  if(articles.value === undefined) {
   return articles.value
  } 
  return articles.value.filter(a => hideList.value.indexOf(a.id) === -1)
})

const hideArticle = (id) => {
  mutateHideList(() => hideList.value = [...hideList.value, id])
} 

composition API

setup

能夠把所有的 vue2 內容寫在 setup 中
要注意的點是只有 return 的值才能被 template 獨到
另外 setup 中是沒有辦法用 this 取得 vue2 的那些各種變數的

<template>
    {{ count }} // 點完 add button 這個不會變
    {{ refCount }}
    <button onClick="addCount"></button>
</template>
import { ref } from 'vue'

export default {
  name: 'Count',
  setup(props, context) {
  // props 就跟以前一樣,因為現在沒有 this 了
  // context 裡面可取得 context.attrs context.slots context.emit
    const count = 0
    const refCount = ref(0)
 
    const addCount = () = {
        count += 1
        refCount.value += 1
    }
 
    return {
      count,
      refCount,
      addCount
    }
  },
}

lifecycle hook

import { onMounted, onUpdated, onUnmounted } from 'vue'

const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
}

reactive

reactive 與 ref 的不同之處在於
reactive 中只能放物件
好處是 reactive 不需要用 .value 來改變值了

setup() {
  const obj = reactive({ count: 0 })
  obj.count += 1
  
  return obj
}

toRefs

用來把

  1. 外部傳入的變數
  2. 解構賦的 reactive 變數

再度轉為 ref 時所使用的 function

json server

install

npm install -g json-server

db.json

{
  "articles": [
    {
      "title": "GraphQL Client with Vue.js",
      "author": "Andy",
      "link": "https://www.vuemastery.com/blog/part-3-client-side-graphql-with-vuejs",
      "id": 1
    },
    {
      "title": "TypeScript with Vue.js",
      "author": "Andy",
      "link": "https://www.vuemastery.com/blog/getting-started-with-typescript-and-vuejs",
      "id": 2
     }
  ]
}

command

json-server --watch db.json

串接方式

現在針對網址 http://localhost:3000/articles
get 可以得到內容
post 可以新增內容(是真的會寫入新資料到 db.json 中)

參考

https://www.vuemastery.com/blog/data-fetching-and-caching-with-swr-and-vuejs/
https://book.vue.tw/