¡£^\深入淺出 TanStack Queryï¼ˆäºŒï¼‰ï¼šåœ¨å‘¼å« useMutation 後發生了什麼事 | Alex Liu

深入淺出 TanStack Queryï¼ˆäºŒï¼‰ï¼šåœ¨å‘¼å« useMutation 後發生了什麼事

• 6 min read

你是怎麼管ç†å°ˆæ¡ˆçš„ server data 狀態呢?å‰ç«¯é–‹ç™¼æ™‚ä¸åƒ…è¦è™•ç† server data 的快å–,還è¦è®“它能盡å¯èƒ½çš„跨元件共用,最後åˆè¦åœ¨é©ç•¶çš„æ™‚候清除或更新,阿哩阿雜的真的很煩人。TanStack Query 是一個å¯ä»¥å¾ˆå¥½çš„解決這些å•題的工具。這個系列文章將分享如何使用 TanStack Query ä»¥åŠæ·±å…¥æš¸è§£å®ƒåº•層é‹ä½œçš„原ç†èˆ‡é‚輯與架構。

å‰è¨€

本篇的 TanStack Query 版本為 5.4.3

這是一個跟 TanStack Query 相關的深入原始碼系列文章,TanStack Query 的架構é¾å¤§ä¸”迭代快速,所以這個系列會ä¸å®šæœŸæ›´æ–°ï¼Œä¸‹åˆ—是目å‰å·²ç¶“發布的文章:

  1. 深入淺出 TanStack Queryï¼ˆä¸€ï¼‰ï¼šåœ¨å‘¼å« useQuery 後發生了什麼事
  2. 深入淺出 TanStack Queryï¼ˆäºŒï¼‰ï¼šåœ¨å‘¼å« useMutation 後發生了什麼事
  3. 深入淺出 TanStack Queryï¼ˆä¸‰ï¼‰ï¼šåœ¨å‘¼å« invalidateQueries 後發生了什麼事

useMutation 與 useQuery 有什麼ä¸åŒ

useMutation 與 useQuery 有什麼ä¸åŒå‘¢ï¼Ÿé€™å€‹å•題我們å¯ä»¥åƒè€ƒ @tannerlinsley(TanStack Query 的作者)的回答:

useMutation 主è¦ä½¿ç”¨åœ¨æ–°å¢žã€ä¿®æ”¹ã€åˆªé™¤ï¼Œåœ¨è¨­è¨ˆä¸Šä»–æ˜¯è¢«å‹•çš„ï¼Œä¹Ÿå°±æ˜¯èªªä»–ä¸æœƒè‡ªå‹•觸發請求,而是è¦ç­‰åˆ°ä½¿ç”¨è€…調用 mutate æ™‚æ‰æœƒç™¼å‡ºç›¸å°æ‡‰çš„請求;而 useQuery 是主動的,他會自動觸發請求,一但 queryKey æ›´æ–°ä»–ä¹Ÿæœƒè‡ªå‹•é‡æ–°ç™¼é€è«‹æ±‚。

useMutation

const useMutationTodo = () => {
  return useMutation({
    mutationFn: (options: { id: number: content: string }) => {
      return mutationTodoById(options)
    }
  })
}

const { mutate } = useMutationTodo()

const handleUpdateTodo = () => {
  // é€™è£¡æ‰æœƒç™¼é€è«‹æ±‚
  mutate({ id: 1, content: 'update todo' })
}

useQuery

const useQueryTodo = (id: MaybeRefOrGetter<number>) => {
  const toTefId = toRef(id)

  return useQuery({
    queryKey: ['TODO', toTefId],
    queryFn: () => fetchTodoById(toTefId.value),
  })
}

// 這裡會自動發é€è«‹æ±‚
// 並且會在 id æ›´æ–°æ™‚é‡æ–°ç™¼é€è«‹æ±‚
const { data } = useQueryTodo(() => router.params.id)

在 Dominik Dorfmeister(TanStack Query 的維護者)的這篇文章中也有æåˆ°ï¼š

useQuery is declarative, useMutation is imperative.

中文翻譯æˆè²æ˜Žå¼ï¼ˆdeclarative)跟是命令å¼ï¼ˆimperative),但在這裡使用主動跟被動去解釋å¯èƒ½æœƒæ›´å¥½ç†è§£ã€‚

å¦å¤–å¦‚æžœåŒæ™‚èª¿ç”¨å¤šæ¬¡ç›¸åŒ useMutation ,他們之間的狀態都是ç¨ç«‹ï¼Œè€Œå¤šæ¬¡èª¿ç”¨ç›¸åŒçš„ useQuery ,他們之間的狀態則是共享的。

useMutation

下列三個 useMutation 之間的狀態是ç¨ç«‹çš„,他們之間的狀態ä¸äº’ç›¸åŒæ­¥ã€‚

const mutation1 = useMutationTodo()
const mutation2 = useMutationTodo()
const mutation3 = useMutationTodo()

useQuery

下列三個 useQuery ä¹‹é–“çš„ç‹€æ…‹æ˜¯å…±äº«çš„ï¼Œä»–å€‘ä¹‹é–“çš„ç‹€æ…‹æ˜¯åŒæ­¥çš„。

const query1 = useQueryTodo(1)
const query2 = useQueryTodo(1)
const query3 = useQueryTodo(1)

在ç†è§£äº† useMutation 與 useQuery 的差異後,接下來我們來看看 useMutation 背後的實作é‚輯。

åœ¨å‘¼å« useMutation 後發生了什麼事

跟 useQuery 一樣,useMutation 背後與三個類(Class)密切相關:

  1. MutationCache - 儲存 Mutation 的地方。
  2. MutationObserver - 訂閱 Mutation 的狀態。
  3. Mutation - 管ç†è«‹æ±‚與請求狀態的地方。

但就如åŒå‰é¢æåˆ°åˆ°çš„,useMutation åœ¨è¨­è¨ˆä¸Šæ˜¯è¢«å‹•çš„ï¼Œæ‰€ä»¥åœ¨å‘¼å« useMutation æ™‚ä¸¦ä¸æœƒåƒæ˜¯ useQuery æœƒåŽ»å»ºç«‹å°æ‡‰ Query,而是直到調用 mutate æ™‚æ‰æœƒå»ºç«‹ Mutation。

æ‰€ä»¥æˆ–è¨±æ¨™é¡Œæ‡‰è©²å¯«ï¼šåœ¨å‘¼å« mutate 後發生了什麼事。

mutate 的實作如下:

class MutationObserver {
  mutate (
    variables: TVariables,
    options?: MutateOptions<TData, TError, TVariables, TContext>,
  ) {
    this.#mutateOptions = options

    this.#currentMutation?.removeObserver(this)

    this.#currentMutation = this.#client
      .getMutationCache()
      .build(this.#client, this.options)

    this.#currentMutation.addObserver(this)

    return this.#currentMutation.execute(variables)
  }
}

mutate 接å—一個 variables 與 options。variables 是 mutationFn æœƒæŽ¥æ”¶åˆ°çš„è«‹æ±‚åƒæ•¸ï¼›è€Œ options å¯ä»¥ç”¨ä¾†è¨­å®š mutate çµæŸå¾Œçš„行為。

ç•¶å‘¼å« mutate 後,如果 MutationObserver 上é¢å­˜æœ‰ Mutation,會先移除該上的 MutationObserver,然後建立新的 Mutationï¼Œä¸¦ä¸”å‘Šè¨´ä»–å°æ‡‰åˆ°çš„ MutationObserver 是誰,最後執行 Mutation 上的 execute 方法完æˆè«‹æ±‚的發é€ã€‚

MutationObserver 與 Mutation 關係如下:

Mutation 與 MutationObserver 關係圖- by Alex Liu

æ¯ä¸€å€‹ MutationObserver åªæœƒå°æ‡‰åˆ°ä¸€å€‹ Mutationï¼Œä½†ä¸æ˜¯æ¯ä¸€å€‹ MutationObserver éƒ½æœƒå°æ‡‰åˆ°ä¸€å€‹ Mutationï¼Œé€™å–æ±ºæ–¼æ˜¯å¦æœ‰èª¿ç”¨éŽ mutate 這個方法。

çµèªž

ç¶œåˆä»¥ä¸Šå…§å®¹æˆ‘們å¯ä»¥æ•´ç†å‡ºï¼šåœ¨å‘¼å« usMutation 後發生了什麼事?

  1. 建立 MutationObserver instance。
  2. 當使用者調用 mutate æ™‚ï¼Œæª¢æŸ¥ä½¿å¦æœ‰èˆŠæœ‰çš„ Mutation instance,如果有則先移除該 instance 上é¢çš„ MutationObserver instance,最後建立 Mutation instance。
  3. å‘¼å« Mutation instance 發é€è«‹æ±‚。

useMutation Flow Chart - by Alex Liu

useMutation 與 useQuery 都是基於 Observer Pattern,所以實作上都是éžå¸¸ç›¸ä¼¼çš„。關於如何將 query-core çš„ mutationObserver æ•´åˆåˆ° vue-query 或是 react-query 中的方法其實跟 queryObserver 類似,所以這篇就沒有å†é‡è¤‡æåŠäº†ã€‚

ä¸éŽ useMutation 背後ä¸åƒ useQuery 需è¦è™•ç†å¤šå€‹ QueryObserver å°æ‡‰åˆ°åŒä¸€å€‹ Query çš„å•題,所以在實作上簡單很多,MutationCache 的存在感也低éžå¸¸å¤šã€‚

到這裡就是 TanStack Query çš„ useMutation 底層基本概念拉ï¼ä¹‹å¾Œé‚„會慢慢推出更多探究 TanStack Query 底層的分享,有任何想暸解或內容有誤的地方都歡迎跟我討論。

åƒè€ƒè³‡æ–™

è«‹æˆ‘å–æ¯å’–å•¡

如果這裡的內容有幫助到你的話,一æ¯å’–å•¡å°±æ˜¯å°æˆ‘最大的鼓勵。

è«‹æˆ‘å–æ¯å’–å•¡
ÿÿÿÿ