vue2/vue3 Composition Api 常用工具集, @vueuse/core 之核心函数 state 实践

作者: tww844475003 分类: 前端开发 发布时间: 2023-08-05 13:39

VueUse是一款基于组合式API的函数集合。

VueUse不是Vue.use,它是为Vue 2和3服务的一套Vue Composition API的常用工具集,是目前世界上Star最高的同类型库之一。它的初衷就是将一切原本并不支持响应式的JS API变得支持响应式,省去程序员自己写相关代码。

createGlobalState

将状态保存全局作用域中,以便跨Vue实例复用。

该状态存储在内存中,刷新页面就丢失,如果希望刷新页面存在,可以与localstorage 一起用。

// stores.ts
import { ref, computed } from 'vue'
import { createGlobalState } from '@vueuse/core'

const useGlobalState = createGlobalState(
  () => {
    // state
    const count = ref(0)

    // getters
    const doubleCount = computed(() => count.value * 2)

    // actions
    function increment() {
      count.value++
    }
    function decrement() {
      count.value--
    }
    return {
      count,
      doubleCount,
      increment,
      decrement
    }
  }
)

export default useGlobalState

// Index.vue
<script lang="ts" setup>
import useGlobalState from './store.js'
import Controls from './Controls.vue'

const { count, doubleCount} = useGlobalState()
</script>

<template>
  <div class="count">
    {{ count }}
    double: {{ doubleCount }}
    <Controls></Controls>
  </div>
</template>

// Controls.vue
<script lang="ts" setup>
import useGlobalState from './store.js'

const { increment, decrement } = useGlobalState()
</script>

<template>
  <div class="btn">
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

createInjectionState

创建可以注入到组件中的全局状态。

// useCounterStore.ts
import { computed, ref } from 'vue'
import { createInjectionState } from '@vueuse/core'

const [useProvideCounterStore, useCounterStore] = createInjectionState((initialValue: number) => {
  // state
  const count = ref(initialValue)
  
  // getters
  const double= computed(() => count.value * 2)

  // actions
  const increment = () => count.value++
  const decrement = () => count.value--

  return {
    count,
    double,
    increment,
    decrement,
  }
})

export { useProvideCounterStore, useCounterStore }

// Index.vue
<script setup lang="ts">
import Root from './Root.vue'
import Counter from './Counter.vue'
import Controls from './Controls.vue'
</script>

<template>
  <div>
    <Root>
      <Counter /> 
      <Controls />
    </Root>
  </div>
</template>

// Root.vue
<script setup lang="ts">
import { useProvideCounterStore } from './useCounterStore'

useProvideCounterStore(0)
</script>

<template>
  <div>
    <slot />
  </div>
</template>

// Counter.vue
<script lang="ts" setup>
import { useCounterStore } from './useCounterStore'

const { count, double } = useCounterStore()!
</script>

<template>
  <ul>
    <li>
      count: {{ count }}
    </li>
    <li>
      double: {{ double }}
    </li>
  </ul>
</template>

// Controls.vue
<script setup lang="ts">
import { useCounterStore } from './useCounterStore'

const { increment, decrement } = useCounterStore()!
</script>

<template>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</template>

createSharedComposable

让一个钩子函数可用于多个Vue实例中。

// useSharedMouse.ts
import { createSharedComposable, useMouse } from '@vueuse/core'

const useSharedMouse = createSharedComposable(useMouse)
export default useSharedMouse

// Index.vue
<script lang="ts" setup>
import CompA from './CompA.vue'
import CompB from './ComB.vue'
</script>

<template>
  <div>
    <CompA />
    <CompB />
  </div>
</template>

// CompA.vue
<script lang="ts" setup>
import useSharedMouse from './useSharedMouse'

const { x, y } = useSharedMouse()
</script>

<template>
  <div>
    ComA组件
    x:{{ x }},
    y:{{ y }}
  </div>
</template>

// CompB.vue
<script lang="ts" setup>
import useSharedMouse from './useSharedMouse'

const { x, y } = useSharedMouse()
</script>

<template>
  <div>
    ComB组件
    x:{{ x }},
    y:{{ y }}
  </div>
</template>

useAsyncState

响应式获取异步状态。不会阻塞setup 函数,在promise完成后,将自动触发。

<script lang="ts" setup>
import { onMounted, reactive } from 'vue'
import { useAsyncState } from '@vueuse/core'

const ajaxData = reactive({
  state: '',
  isReady: false,
  isLoading: false,
})

const fetchUser = async(id: number) => {
  const resp = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
  return resp.json()
}

const loadData = () => {
  ajaxData.state = ''
  ajaxData.isLoading = true
  ajaxData.isReady = false
  
  const { execute }= useAsyncState(fetchUser, null, {
    immediate: false,
    delay: 2000,
    onSuccess: (data) => {
      ajaxData.state = data
      ajaxData.isLoading = false
      ajaxData.isReady = true
    }
  })
  execute(2000, 1)
}

onMounted(() => {
  loadData()
})
</script>

<template>
  <div class="use-async-state">
    Ready: {{ ajaxData.isReady }}
    Loading: {{ ajaxData.isLoading }}
    State: {{ ajaxData.state }}
    <br />
    <button @click="loadData">加载数据</button>
  </div>
</template>

useRefHistory

跟踪 ref 的更改历史,提供撤消和重做功能

<script lang="ts" setup>
import { ref } from 'vue'
import { useRefHistory } from '@vueuse/core'

const count = ref(0)
const { history, undo, redo } = useRefHistory(count)

const increment = () => {
  count.value++
}

const decrement = () => {
  count.value--
}
</script>

<template>
  <div class="use-ref-history">
    count: {{ count }} <br />
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="undo">undo</button>
    <button @click="redo">redo</button>
    <ul>
      <li v-for="item in history" :key="item.timestamp">
        {{ item.timestamp }}: {{ item.snapshot }}
      </li>
    </ul>
    {{ history }}
  </div>
</template>

useDebouncedRefHistory

useRefHistory 的简写,带有防抖过滤器。

<script lang="ts" setup>
import { ref } from 'vue'
import { useDebouncedRefHistory } from '@vueuse/core'

const count = ref(0)
const { history, undo, redo } = useDebouncedRefHistory(count, {
  deep: true,
  debounce: 100
})

const increment = () => {
  count.value++
}

const decrement = () => {
  count.value--
}
</script>

<template>
  <div class="use-debounced-ref-history">
    count: {{ count }} <br />
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="undo">undo</button>
    <button @click="redo">redo</button>
    <ul>
      <li v-for="item in history" :key="item.timestamp">
        {{ item.timestamp }}: {{ item.snapshot }}
      </li>
    </ul>
    {{ history }}
  </div>
</template>

useThrottledRefHistory

带节流过滤器的 useRefHistory 的简写。

<script lang="ts" setup>
import { ref } from 'vue'
import { useDebouncedRefHistory } from '@vueuse/core'

const count = ref(0)
const { history, undo, redo } = useThrottledRefHistory(count, {
  deep: true,
  debounce: 100
})

const increment = () => {
  count.value++
}

const decrement = () => {
  count.value--
}
</script>

<template>
  <div class="use-throttled-ref-history">
    count: {{ count }} <br />
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="undo">undo</button>
    <button @click="redo">redo</button>
    <ul>
      <li v-for="item in history" :key="item.timestamp">
        {{ item.timestamp }}: {{ item.snapshot }}
      </li>
    </ul>
    {{ history }}
  </div>
</template>

useManualRefHistory

调用 commit() 时,手动跟踪ref的更改历史,提供撤消和重做功能

必须调用了commit 方法才会产生记录

<script lang="ts" setup>
import { ref } from 'vue'
import { useManualRefHistory } from '@vueuse/core'

const count = ref(0)
const { history, commit, undo, redo } = useManualRefHistory(count)

const increment = () => {
  count.value++
  commit()
}

const decrement = () => {
  count.value--
  commit()
}
</script>

<template>
  <div class="use-manual-ref-history">
    count: {{ count }} <br />
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="undo">undo</button>
    <button @click="redo">redo</button>
    <ul>
      <li v-for="item in history" :key="item.timestamp">
        {{ item.timestamp }}: {{ item.snapshot }}
      </li>
    </ul>
    {{ history }}
  </div>
</template>

useLastChanged

记录最后一次更改的时间戳

<script lang="ts" setup>
import { ref } from 'vue'
import { useLastChanged } from '@vueuse/core'

const input = ref(0)
const lastChanged = useLastChanged(input)
</script>

<template>
  <div class="use-last-changed">
    <input v-model="input" /> <br />
    lastChanged: {{ lastChanged }}
  </div>
</template>

useStorage

响应式的 LocalStorage/SessionStorage

<script lang="ts" setup>
import { useStorage } from '@vueuse/core'

const state = useStorage('my-store', 'hello @vueuse/core')

const edit = () => {
  state.value = 'edit store'
}

const clear = () => {
  state.value = null
}
</script>

<template>
  <div class="use-storage">
    storage: {{ state }} <br />
    <button @click="edit">修改</button>
    <button @click="clear">清除</button>
  </div>
</template>

useStorage 传入第三个参数(localStorage、sessionStorage),第二个参数默认值就不起做用

localStorage.setItem('my-store', '{"a": "1", "b": "2"}')
sessionStorage.setItem('my-store', '{"a": "1", "b": "2"}')
const state = useStorage('my-store', { c: 3, d: 4, a: 10}, sessionStorage)

合并默认值

const options = {mergeDefaults: true}
const state = useStorage('my-store', { c: 3, d: 4, a: 10}, sessionStorage, options)

自定义合入规则

{ 
   mergeDefaults: (storageValue, defaults) => deepMerge(defaults, storageValue)
}

自定义序列化

const state = useStorage('key', '123', undefined, {
  serializer: {
    read: (v: any) => v ? JSON.parse(v) : null,
    write: (v: any) => JSON.parse(v),
  }
})

useStorageAsync

支持异步的响应式Storage

useLocalStorage

响应式的 LocalStorage,方法是基于useStorage,使用方式同useStorage。

useSessionStorage

响应式的 SessionStorage,方法是基于useStorage,使用方式同useStorage。

前端开发那点事
微信公众号搜索“前端开发那点事”

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注