VueUse 是怎么封装Vue3 Provide/Inject 的?

Posted 核桃大号

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VueUse 是怎么封装Vue3 Provide/Inject 的?相关的知识,希望对你有一定的参考价值。

Provide/Inject

Provide 和 Inject 可以解决 Prop 逐级透传问题。注入值类型不会使注入保持响应性,但注入一个响应式对象,仍然有响应式的效果。

Provide 的问题是无法追踪数据的来源,在任意层级都能访问导致数据追踪比较困难,不知道是哪一个层级声明了这个或者不知道哪一层级或若干个层级使用了。

看看 VueUse 的 createInjectionState 是怎么封装 Provide 的,并且怎么避免 Provide 的问题。

介绍

createInjectionState:创建可以注入组件的全局状态。

//useCounterStore.ts
const [useProvideCounterStore, useCounterStore] = createInjectionState(
  (initialValue: number) => 
    // state
    const count = ref(initialValue)

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

    // actions
    function increment() 
      count.value++
    

    return  count, double, increment 
  )

export  useProvideCounterStore 
// If you want to hide `useCounterStore` and wrap it in default value logic or throw error logic, please don\'t export `useCounterStore`
export  useCounterStore 
<!-- RootComponent.vue -->
<script setup lang="ts">
  import  useProvideCounterStore  from \'./useCounterStore\'

  useProvideCounterStore(0)
</script>

<template>
  <div>
    <slot />
  </div>
</template>
<!-- CountComponent.vue -->
<script setup lang="ts">
import  useCounterStore  from \'./useCounterStore\'

// use non-null assertion operator to ignore the case that store is not provided.
const  count, double  = useCounterStore()!
// if you want to allow component to working without providing store, you can use follow code instead:
// const  count, double  = useCounterStore() ??  count: ref(0), double: ref(0) 
// also, you can use another hook to provide default value
// const  count, double  = useCounterStoreWithDefaultValue()
// or throw error
// const  count, double  = useCounterStoreOrThrow()
</script>

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

源码

/**
 * Create global state that can be injected into components.
 *
 * @see https://vueuse.org/createInjectionState
 *
 */
export function createInjectionState<Arguments extends Array<any>, Return>(
  composable: (...args: Arguments) => Return,
): readonly [useProvidingState: (...args: Arguments) => Return, useInjectedState: () => Return | undefined] 
  const key: string | InjectionKey<Return> = Symbol(\'InjectionState\')
  const useProvidingState = (...args: Arguments) => 
    const state = composable(...args)
    provide(key, state)
    return state
  
  const useInjectedState = () => inject(key)
  return [useProvidingState, useInjectedState]

思考

为什么返回的是数组

createInjectionState 返回的数组,使用 demo 中采用的数组解构的方式。那么数组解构和对象解构有什么区别么?

提到数组解构首先想到的是 react 的 useState。

const [count,setCount] =useState(0)

之所以用数组解构是因为在调用多个 useState 的时候,方便命名变量。

const [count,setCount] =useState(0)
const [double, setDouble] = useState(0);

如果用对象解构,代码会是

const state:count,setState:setCount =useState(0)
const state:double, setState:setDouble = useState(0);

相比之下数组显得代码更加简洁。

数组解构也有缺点:返回值必须按顺序取值。返回值中只取其中一个,代码就很奇怪。

const [,setCount] =useState(0)

因此数组解构时适合使用所有返回值,并且多次调用方法的情况;对象解构适合只使用其中部分返回值,并且一次调用方法的情况。

createInjectionState 创建的注入状态 key 是 Symbol(\'InjectionState\'),也就是每次运行的 key 都不一样,有可能多次调用 createInjectionState,因此 createInjectionState 采用数组解构的方式。但使用返回值可能只使用 useInjectedState,所有在 useCounterStore.ts 中又将 useProvideCounterStore 和 useInjectedState 以对象的方式导出避免出现下面奇怪的写法。

const [,useCounterStore] =useCounterStore()

使用例子中的 state 结构

使用案例中将 provide 中的对象分为 state、getters、actions。结构很想 vuex,而 useProvideCounterStore 相当于 vuex 中的 mutation。采用这种结构是因为 provide 的缺点:无法追踪数据的来源,在任意层级都能访问导致数据追踪比较困难,不知道是哪一个层级声明了这个或者不知道哪一层级或若干个层级使用了。

采用类似 vuex 的结构能相对比较好的追踪状态。

 // state
  const count = ref(initialValue)

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

  // actions
  function increment() 
    count.value++
  

readonly

createInjectionState 返回的数组是 readonly 修饰的,useInjectedState 返回的对象并没有用 readonly 修饰,provide/inject 的缺点就是状态对象不好跟踪,容易导致状态变更失控。既然提供了 useProvidingState 修改状态的方法,useInjectedState 返回的状态如果是只读的能更好防止状态变更失控。

Vue3 Composition API——生命周期钩子Provide函数 和 Inject函数封装Hook案例setup顶层编写方式

一、生命周期钩子

我们前面说过 setup 可以用来替代 data 、 methods 、 computed 、watch 等等这些选项,也可以替代
生命周期钩子。

那么setup中如何使用生命周期函数呢?

  • 可以使用直接导入的 onX 函数注册生命周期钩子;


二、Provide函数 和 Inject函数

事实上我们之前还学习过Provide和Inject,Composition API也可以替代之前的 Provide 和 Inject 的选项

我们可以通过 provide来提供数据:
可以通过 provide 方法来定义每个 Property;

provide可以传入两个参数:

  • name:提供的属性名称;
  • value:提供的属性值;

在 后代组件 中可以通过 inject 来注入需要的属性和对应的值:
可以通过 inject 来注入需要的内容;

inject可以传入两个参数:

  • 要 inject 的 property 的 name;
  • 默认值;

三、数据的响应式

为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 ref 和 reactive。

四、修改响应式Property

如果我们需要修改可响应的数据,那么最好是在数据提供的位置来修改:

  • 我们可以将修改方法进行共享,在后代组件中进行调用;

五、封装Hook函数案例

  1. 计数器案例的Hook

  2. 修改title的Hook


  3. 监听界面滚动位置的Hook

  1. 使用 localStorage 存储和获取数据的Hook

<template>
  <div>
    <h2>当前计数:counter</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <h1>data</h1>
    <button @click="changeData">修改data</button>
    <p class="content"></p>
    <div class="scroll">
      <div class="scroll-x">scrollX: scrollX</div>
      <div class="scroll-y">scrollY: scrollY</div>
    </div>
  </div>
</template>
<script>
import useTitle from "./hook/useTitle";
import useCounter from "./hook/useCounter";
import useScrollPosition from "./hook/useScrollPosition";
import useLocalStorage from "./hook/useLocalStorage";
export default 
  name: "Home",
  setup(props, context) 
    // 1. 计数器
    const counter,  increment, decrement = useCounter()

    // 2. 修改title
    const titleRef = useTitle('我是默认的title')
    setTimeout(() => 
      titleRef.value = 'hahaha'
    , 4000)

    // 3. 获取滚动位置
    const scrollX, scrollY = useScrollPosition()

    // 4.保存数据
    const data = useLocalStorage('user', name: 'zep', age: 22)
    const changeData = () => 
      data.value = name: 'haha', age: 18
    
    return 
      counter, increment, decrement,
      scrollX, scrollY,
      data, changeData
    
  

</script>

<style scoped>
  .content 
    width: 3000px;
    height: 5000px;
  
  .scroll 
    position: fixed;
    right: 30px;
    bottom: 30px;
  
</style>

六、补充:setup顶层编写方式(实验性特性,不稳定)


以上是关于VueUse 是怎么封装Vue3 Provide/Inject 的?的主要内容,如果未能解决你的问题,请参考以下文章

vue3.0项目中手动封装加载更多数据(常见)

vue3.0利用useIntervalFn 工具实现验证码倒计时

VueUse(中文)——简介

5个 VueUse 库函数,让你工作效率翻倍(收藏!)

vue3中provide和inject的使用

vue3源码分析——实现组件通信provide,inject