Svelte 響應式資料之$

tags: Svelte
category: Front-End
description: Svelte 響應式資料之$
created_at: 2023/04/02 19:00:00

cover image


回到 手把手開始寫 Svelte


前言

這邊我覺得比較像是語法糖。

Vue 中比較像 watchEffect() 或是你要說像 watch()computed() 也可以,因為有重疊的部分,下面會提到。

React 比較像 useEffect(),但又不是一模一樣。

然後上一篇講太多太乾枯乏味,這一篇輕鬆一點。

而且因為 $ 能講的也不多,所以乾脆就來個與 VueReact 的比較。


用法 1

畫面上呈現

firstName = John
lastName = Doe
fullName = John Doe

Svelte code

<script>
    let firstName = 'John';
    let lastName = 'Doe';

    $: fullName = `${firstName} ${lastName}`;
</script>

<div>
    <p>firstName = {firstName}</p>
    <p>lastName = {lastName}</p>
    <p>fullName = {fullName}</p>
</div>

Vue code

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`
})
</script>

<template>
  <div>
    <p>firstName = {{ firstName }}</p>
    <p>lastName = {{ lastName }}</p>
    <p>fullName = {{ fullName }}</p>
  </div>
</template>

React code

function App() {
  const [firstName, setFirstName] = useState('John')
  const [lastName, setLastName] = useState('Doe')
  const fullName = `${firstName} ${lastName}`

  return (
    <div>
      <p>firstName = {firstName}</p>
      <p>lastName = {lastName}</p>
      <p>fullName = {fullName}</p>
    </div>
  )
}

可能有人會有疑問的是 React 的部分,可能會有人寫成這樣

const [fullName, setFullName] = useState(`${firstName} ${lastName}`)

甚至是這樣

useEffect(() => {
    setFullName(`${firstName} ${lastName}`)
}, [firstName, lastName])

千萬不要這樣做,你這樣只會無故造成多餘的 re-render

在這邊,當 firstNamelastName 有更新的時候,會觸發一次 re-render,而這時 fullName 自然就會被運算成新的值,所以並不需要寫那麼複雜。


用法 2

監聽 count ,每隔一秒幫我把 count += 1 ,且當他變化的時候幫我 console.log

Svelte code

<script>
    let count = 0;
    $: console.log('count changed', count)

    setInterval(() => {
        count++;
    }, 1000);
</script>

Vue code

<script setup>
    import { ref, watch } from 'vue'

    const count = ref(0)

    watch(count, (val) => {
        console.log('count changed', val)
    })

    setInterval(() => {
        count.value++
    }, 1000)
</script>

React code

function App() {
  const [count, setCount ] = useState(0)
  useEffect(() => {
    console.log('count changed', count)
  }, [count])

  setTimeout(() => {
    setCount(count + 1)
  }, 1000)

  return <></>
}

可能有人會有疑問的還是 React 的部分,就你毛最多

為什麼不是使用 setInterval?,你用啊,直接用啊,保證發生災難

用的好也是可以用啦,只是你要多寫一些東西,例如下面這樣,很沒必要:

  useEffect(() => {
    console.log('count changed', count)

    const timer = setInterval(() => {
      setCount(count + 1)
    }, 1000)

    return () => {
      clearInterval(timer)
    }
  }, [count])

用法 3

監聽 count ,我不希望 count 的值超過 5,最多就到 5,且在 console 呈現警告訊息。

Svelte code

<script>
    let count = 0;
    $: if (count > 5) {
        console.warn('count is dangerously high!');
        count = 5;
    }

    setInterval(() => {
        count++;
    }, 1000);
</script>

<div>{count}</div>

Vue code

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)
watch(count, (val) => {
  if (val > 5) {
    console.warn('count is dangerously high!')
    count.value = 5
  }
})

setInterval(() => {
  count.value++
}, 1000)
</script>

<template>
  <div>{{ count }}</div>
</template>

React code

function App() {
  const [count, setCount ] = useState(0)
  useEffect(() => {
    if (count > 5) {
      console.warn('count is dangerously high!')
      setCount(5)
      return
    }

    const timer = setInterval(() => {
      setCount(count + 1)
    }, 1000)

    return () => {
      clearInterval(timer)
    }
  }, [count])

  return (
    <div>
      {count}
    </div>
  )
}

這次 React 總算沒什麼毛了,主要就是宣告計時器的方式要對,免得他在一次 re-render 跑多次。

但總之就是想帶到 Svelte 可以監聽一個區塊內使用到的東西。


用法3 - 延伸範例

當變數變化,去改網站標題

Svelte code

<script>
    let title = 'Hello World';

    $: {
        if (typeof document !== 'undefined') {
            document.title = title;
        }
    }

    setTimeout(() => {
        title = '1 second later';
    }, 1000);
</script>

Vue code

<script setup>
import { ref, watchEffect } from 'vue'

const title = ref('Hello World')
watchEffect(() => {
  document.title = title.value
})

setTimeout(() => {
  title.value = '1 second later'
}, 1000)
</script>

React code

function App() {
  const [title, setTitle] = useState('Hello World')

  useEffect(() => {
    document.title = title
  }, [title])

  useEffect(() => {
    setTimeout(() => {
      setTitle('1 second later')
    }, 1000)
  }, [])

  return <></>
}

$ 也是批次更新

如同上一篇所提到的批次更新,$也是一樣。

所以有可能會遇到下面的情況

<script>
    let count = 0;
    $: doubleCount = count * 2;
    const update = () => {
        count += 1;
        console.log(count, doubleCount);
    };
</script>

<div>count: {count}</div>
<div>doubleCount: {doubleCount}</div>
<button on:click={update}>count++</button>

畫面上呈現是對的,但是 console 是錯的。

解決辦法,拉出去:

    let count = 0;
    $: doubleCount = count * 2;
    const update = () => {
        count += 1;
    };
    $: console.log(count, doubleCount);

總結

總之就是監聽變數變化之後要幹嘛,就用 $ 這個東西,用法也很簡單,相信可以從上面範例看的出來。(只是要記得注意他也是批次更新)




最後更新時間: 2023年04月02日.