关于vue3:Vue3初体验

装置

独立装置

能够在Vue.js官网间接下载最新版本,并用script标签引入

独立装置

CDN形式装置

间接应用script引入<script src="https://unpkg.com/vue@next"></script>

npm形式装置

npm版本需大于3.0

npm install vue@next

命令行工具:

从之前的版本包名扭转了,从vue-cli变为@vue/cli。如果之前已全局装置了vue-cli1.x或vue-cli2.x。首先须要

应用命令npm uninstall vue-cli -g或者yarn global remove vue-cli卸载掉之前的版本,在进行装置

Node版本留神点:

Vue CLI 4.x 须要NodeJs的版本>=8.9

npm install -g @vue/cli

或者

yarn global add @vue/cli

留神:vue-cli 3.x 和 vue-cli 2.x 应用了雷同的 vue 命令,如果你之前曾经装置了 vue-cli 2.x,它会被替换为 Vue-cli 3.x。装置 @vue/cli-int:

npm i -g @vue/cli-init

创立我的项目

Vue CLI

应用命令vue create 项目名称来创立我的项目

而后期待下载对应的模板以及依赖。

运行

cd 我的项目名

npm run serve

Vite

Vite 是一个 web 开发构建工具,因为其原生 ES 模块导入形式,能够实现闪电般的冷服务器启动。

通过在终端中运行以下命令,能够应用 Vite 疾速构建 Vue 我的项目。

全局装置 create-vite-app:

npm i -g create-vite-app

创立我的项目:

npm init vite-app <我的项目名>

运行:

cd 我的项目名

npm install

npm run dev

Vue3目录构造

命令行工具@vue/cli

目录解析

目录文件 阐明
public 公共资源目录
src 这里是咱们要开发的目录,基本上要做的事件都在这个目录里
.xxxx文件 这些是一些配置文件,包含语法配置,git配置等
package.json 我的项目配置文件
README.md 我的项目的阐明文档,markdown 格局

Vue3-根底点

起步

以下所以笔记都是基于@vue/cli形式创立我的项目进行阐明

Composition API

为什么须要Composition API

Composition API是Vue3的最大特点,也能够很显著看出他是受到React Hooks的启发

  • 解决代码的可读性随着组件变大而变差
  • 每一种代码复用的形式,都存在缺点
  • TS反对无限

setup

setup 是 Vue3.x 新增的一个选项, 它是组件内应用 Composition API的入口

setup执行机会

基于VueJs生命周期的比照,发现setup要早于beforeCreate执行

<script>
import { defineComponent } from '@vue/composition-api'

export default defineComponent({
  beforeCreate() {
    console.log('beforeCreate')
  },
  created() {
    console.log('created')
  },
  setup() {
    console.log('setup')
  },
})
</script>
setup参数

应用setup时,它承受两个参数:

  1. {Data} props
  2. {SetupContext} context

setup 中承受的props是响应式的, 当传入新的 props 时,会及时被更新。因为是响应式的, 所以不能够应用 ES6 解构,解构会打消它的响应式。错误代码示范:

<script>
import { defineComponent } from '@vue/composition-api'
export default defineComponent({
  setup(props) {
    const { name } = props
    console.log('prop name', name)
  },
})
</script>

Getting a value from the props in root scope of setup() will cause the value to lose reactivity vue/no-setup-props-destructure

setup类型
interface Data {
  [key: string]: unknown
}

interface SetupContext {
  attrs: Data
  slots: Slots
  emit: (event: string, ...args: unknown[]) => void
}

function setup(props: Data, context: SetupContext): Data

从下面ts定义的两个接口能够看出,setup函数的第二个参数context对象,有三个属性,别离是:

  • attrs:对应vue2.x中的$attr属性
  • slots: 对应vue2.x中的slot插槽
  • emit: 对应vue2.x中的$emit发送事件

这样设计的目标在于,咱们在setup函数中不能拜访到this。并且这几个属性都是主动同步最新的值,所以咱们每次应用拿到的都是最新值。

生命周期


改图起源其余博主

能够通过间接导入onX办法来注册生命周期钩子函数。

<script>
import { defineComponent, onMounted, onUpdated, onUnmounted } from 'vue'
export default defineComponent({
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
})
</script>

这些生命周期钩子函数在setup函数里可能同步地应用,因为它们依赖于外部全局状态来定位以后的流动实例(以后正在调用setup的组件实例)。在没有以后流动实例的状况下调用它们将导致谬误。

组件实例上下文也在生命周期钩子的同步执行期间设置。在卸载组件时候,在生命周期钩子内同步创立的的察看程序watch和计算属性computed也将主动删除。

  • 比照Options API 和Composition API 生命周期

    • beforeCreate -> use setup()
    • created -> use setup()
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeUnmount -> onBeforeUnmount
    • unmounted -> onUnmounted
    • errorCaptured -> onErrorCaptured
    • renderTracked -> onRenderTracked
    • renderTriggered -> onRenderTriggered
    • activated -> onActivated
    • deactivated -> onDeactivated
    Options API Hook inside setup
    beforeCreate Not needed*
    created Not needed*
    beforeMount onBeforeMount
    mounted onMounted
    beforeUpdate onBeforeUpdate
    updated onUpdated
    beforeUnmount onBeforeUnmount
    unmounted onUnmounted
    errorCaptured onErrorCaptured
    renderTracked onRenderTracked
    renderTriggered onRenderTriggered
    activated onActivated
    deactivated onDeactivated

    从下面的比照能够看出:

    1. beforeCreatecreatedsetup替换了
    2. 钩子命名都减少了on
    3. 新增用于调试的钩子函数onRenderTriggeredonRenderTricked
    4. 将 Vue2.x 中的beforeDestroy名称变更成beforeUnmount; destroyed 表更为 unmounted

provide与inject

provide与inject启动了依赖注入项,两者都只能在setup期间应用以后组件实例进行调用

类型
interface InjectionKey<T> extends Symbol {}

function provide<T>(key: InjectionKey<T> | string, value: T): void

// without default value
function inject<T>(key: InjectionKey<T> | string): T | undefined
// with default value
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
// with factory
function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T

reactive、ref、toRefs

在vue2.x中,数据的定义都是在data函数中,然而在vue3.x中,能够应用reactvieref来定义数据

reactive

返回一个响应式的对象正本。

const obj = reactive({ count: 0 })

特点:响应式的转换是“深度”的。它会影响所有嵌套属性,基于Proxy去实现,返回的proxy对象与原始对象并不相等,倡议只与响应式的proxy对象应用防止依赖原始对象。

类型
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>

函数reactive承受一个对象作为参数,并返回一个响应式的对象。这里采纳了泛型束缚的形式使得reactive函数的参数的类型更加具体。

例子:

<template>
  <div>
    <p>{{user.name}}</p>
    <p>{{user.hobby}}</p>
    <p v-for="item in user.sexs" :key="item.id">{{item.label}}</p>
  </div>
</template>
<script>
import { defineComponent, reactive } from 'vue'
export default defineComponent({
  setup() {
   const user = reactive({
     name: 'DarkCode',
     hobby: '篮球',
     sexs: [
       {
         id: 1,
         label: '男'
       },
       {
         id: 2,
         label: '女'
       }
     ]
   })

   return {
     user
   }
  }
})
</script>

联合toRefs应用解构

下面的例子中能够看到,咱们在页面上应用user.nameuser.hobby等比拟繁琐。那么是否将user对象进行解构,间接失去它的相干属性呢?这是不能的,因为会打消它的响应式。但咱们能够借助toRefstoRefs 用于将一个 reactive 对象转化为属性全副为 ref 对象的一般对象

<template>
  <div>
    <p>{{name}}</p>
    <p>{{hobby}}</p>
    <p v-for="item in sexs" :key="item.id">{{item.label}}</p>
  </div>
</template>
<script>
import { defineComponent, reactive,toRefs } from 'vue'
export default defineComponent({
  setup() {
   const user = reactive({
     name: 'DarkCode',
     hobby: '篮球',
     sexs: [
       {
         id: 1,
         label: '男'
       },
       {
         id: 2,
         label: '女'
       }
     ]
   })

   return {
     ...toRefs(user)
   }
  }
})
</script>

留神点:reactive会“解开”所有深层的refs,同时放弃ref是响应式的

<script>
import { defineComponent, reactive, ref } from 'vue'
export default defineComponent({
  setup() {
    const count = ref(1)
    const obj = reactive({ count })

    // ref will be unwrapped
    console.log(obj.count === count.value) // true

    // it will update `obj.count`
    count.value++
    console.log(count.value) // 2
    console.log(obj.count) // 2

    // it will also update `count` ref
    obj.count++
    console.log(obj.count) // 3
    console.log(count.value) // 3
  }
})
</script>

在将ref调配给响应属性时,该ref将主动解包

<script>
import { defineComponent, reactive, ref } from 'vue'
export default defineComponent({
  setup() {
    const count = ref(1)
    const obj = reactive({})

    // assigning a ref to a reactive property
    obj.count = count

    console.log(obj.count) // 1
    // ref will be automatically unwrapped.
    console.log(obj.count === count.value) // true
  }
})
</script>
reactive实质
  • 是一个基于proxy实现的响应式函数,返回值是一个proxy的响应式对象
  • 函数的参数是对象类型,对于根本数据类型来说不能用reactive
  • 可能深层次地监听到响应式对象属性
<script>
import { defineComponent, reactive } from 'vue'
export default defineComponent({
  setup() {
    const obj = reactive({
      name: 'DarkCode',
      age: 22
    })
    console.log(obj)
  }
})
</script>
ref

承受一个外部值并返回一个响应式且可变的ref对象。ref对象具备指向外部值的单个属性(.value)

如:

<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
  setup() {
    const count = ref(0)
    console.log(count.value)

    count.value++
    console.log(count.value)
  }
})
</script>

或

<template>
  <div>
    {{count}}
  </div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
  setup() {
    const count = ref(0)
    console.log(count.value)

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

如果将一个对象调配为ref的值,则该对象将通过响应式办法reactive赋予深度响应式

类型
interface Ref<T> {
  value: T
}

function ref<T>(value: T): Ref<T>

定义了一个Ref泛型接口,接口中定义了一个value变量,类型通过泛型进行决定。办法ref返回值是Ref类型

如:

<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
  setup() {
    const count = ref({
      name: 'Darkcode',
      age: 22
    })
    console.log(count.value.age)

    count.value.age++
    console.log(count.value.age)
    return {
      count
    }
  }
})
</script>
ref总结
  • 办法ref承受一个参数,类型能够是任何类型
  • 办法的返回值是一个接口(对象)类型
  • 要拜访或批改值须要通过.value的模式去实现
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
  setup() {
    const count = ref({
      name: 'Darkcode',
      age: 22
    })
    console.log(count)
  }
})
</script>
toRefs

可能将一个响应式对象转换为一个一般对象,其中后果对象的每个属性都指向原始对象相应属性的ref

如:

<script>
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
  setup() {
    const state = reactive({
      foo: 1,
      bar: 2
    })
    /*
    Type of stateAsRefs:

    {
      foo: Ref<number>,
      bar: Ref<number>
    }
    */
    const stateAsRefs = toRefs(state)
    
    // The ref and the original property is "linked"
    state.foo++
    console.log(stateAsRefs.foo.value)// 2

    stateAsRefs.foo.value++
    console.log(state.foo)// 3
  }
})
</script>

从组合函数返回响应式对象的时候,toRefs是很有用的,以便应用组件时能够对返回的对象进行解构/扩大而不会失去”响应式”

<script>
import { defineComponent, reactive, toRefs } from 'vue'
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  return toRefs(state)
}
export default defineComponent({
  setup() {
    // can destructure without losing reactivity
    const { foo, bar } = useFeatureX()
    return {
      foo,
      bar
    }
  }
})
</script>

toRefs仅为源对象中蕴含的属性生成ref援用。要为特定属性创立ref援用,需应用toRef

toRefs总结
  • 能将一个响应式对象转换为一个一般对象
  • 失去的对象的属性领有一个隐式的value属性
  • 失去的对象的每个属性可看成是一个个的ref
  • 在对对象进行解构的时候,toRefs很有用

readonly

能将一个对象(响应式或一般对象)或者一个ref,并返回原始的只读proxy代理。只读proxy代理“很深”:拜访的任何嵌套属性也将是只读的

如:

<script>
import { defineComponent, reactive, readonly, watchEffect } from 'vue'
export default defineComponent({
  setup() {
    const original = reactive({
      count: 0
    })

    const copy = readonly(original)
    console.log(copy)
    watchEffect(() => {
      // works for reactivity tracking
      console.log(copy.count) // 0、1
    })

    // mutating original will trigger watchers relying on the copy
    original.count++

    // mutating the copy will fail and result in a warning
    copy.count++ //warning
  }
})
</script>

与响应式一样,如果任何属性应用了ref,则通过proxy代理拜访该属性时,该属性将主动解包。

<script>
import { defineComponent, ref, readonly } from 'vue'
export default defineComponent({
  setup() {
    const raw = {
      count: ref(123)
    }

    const copy = readonly(raw)

    console.log(raw.count.value) // 123
    console.log(copy.count) // 123
  }
})
</script>

isProxy

检测对象是否由reactive响应式或readonly只读形式创立的proxy代理。

isReactive

检测对象是否由reactive响应式创立的proxy代理对象。

如:

<script>
import { defineComponent, reactive, isReactive } from 'vue'
export default defineComponent({
  setup() {
    const user = reactive({
      name: 'DarkCode'
    })

    console.log(isReactive(user)) // true
  }
})
</script>

如果通过readonly创立一个proxy代理对象,也会返回true。但包装由reactive响应式创立的另一个proxy代理对象。

import { reactive, isReactive, readonly } from 'vue'
export default {
  setup() {
    const state = reactive({
      name: 'John'
    })
    // readonly proxy created from plain object
    const plain = readonly({
      name: 'Mary'
    })
    console.log(isReactive(plain)) // -> false

    // readonly proxy created from reactive proxy
    const stateCopy = readonly(state)
    console.log(isReactive(stateCopy)) // -> true
  }
}

isReadonly

检测对象是否是由readonly创立的只读proxy代理。

toRaw

返回reactivereadonly代理的原始对象。只是一个本义口,可用于长期读取而不会产生代理拜访/跟踪开销,也可用于写入而不会触发更改。不倡议保留对原始对象的长久援用,应用的时候要审慎。

如:

<script>
import { defineComponent, reactive, toRaw } from 'vue'
export default defineComponent({
  setup() {
    const foo = {
      name: 'DarkCode'
    }
    const user = reactive(foo)

    console.log(toRaw(user) === foo) // true
  }
})
</script>

markRaw

标记一个对象,使其永远不会转换为代理,而是返回对象自身。

<script>
import { defineComponent, reactive, markRaw, isReactive } from 'vue'
export default defineComponent({
  setup() {
    const foo = markRaw({})
    console.log(isReactive(reactive(foo))) // false

    // also works when nested inside other reactive objects
    const bar = reactive({ foo })
    console.log(isReactive(bar.foo)) // false
  }
})
</script>

留神点:

markRaw以及上面的shallowXXX API使咱们能够有选择地抉择默认的深度响应式/只读转化,并将原始的,非代理的对象嵌入状态图中,有如下理由:

  • 不应使某些值具备响应式,如负责的第三方类实例或Vue组件实例对象。
  • 渲染具备不可变数据源的大列表时,跳过代理转换能够进步性能。

shallowReactive

创立一个响应式代理,该代理跟踪其本身属性的相应性,但不执行嵌套对象的深度响应式转换。相似咱们的浅拷贝。

如:

<script>
import { defineComponent, shallowReactive, isReactive } from 'vue'
export default defineComponent({
  setup() {
    const state = shallowReactive({
      foo: 1,
      nested: {
        bar: 2
      }
    })

    // mutating state's own properties is reactive
    state.foo++
    // ...but does not convert nested objects
    isReactive(state.nested) // false
    state.nested.bar++ // non-reactive
  }
})
</script>

shallowReadonly

相似下面的shallowReactive,就不多说了。

toRef

用于为源响应式对象上的属性创立ref,而后能够传递ref,保留与其源属性的响应式链接。

如:

<script>
import { defineComponent, reactive } from 'vue'
export default defineComponent({
  setup() {
    const state = reactive({
      foo: 1,
      bar: 2
    })

    const fooRef = toRef(state, 'foo')

    fooRef.foo++
    console.log(state.foo) // 2

    state.foo++
    console.log(fooRef.foo) // 3
  }
})
</script>

当要将属性的ref传递给组合函数时,toRef很有用。

export default {
  setup(props) {
    useSomeFeature(toRef(props, 'foo'))
  }
}

customRef

创立一个自定义ref,并对其依赖项跟踪进行显式管制,并触发更新。它须要一个工厂函数,该函数接管tracktrigger函数作为参数,并返回带有getset的对象。

如:

<template>
  <div>
    <input type="text" v-model="text" />
  </div>
</template>
<script>
import { defineComponent, customRef } from 'vue'
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}
export default defineComponent({
  setup() {
    return {
      text: useDebouncedRef('hello')
    }
  }
})
</script>
类型
function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

computed与watch

computed

应用getter函数,并为getter返回的值返回一个不可变的响应式ref对象。

如:

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

export default defineComponent({
  setup() {
    const count = ref(0)
    const plusOne = computed(() => count.value + 1) // 2

    console.log(plusOne.value)

    plusOne.value++ //error
  },
})
</script>

另外,它还能够应用带有getset函数的对象来创立可写的ref对象。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0
类型
// read-only
function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>>

// writable
function computed<T>(options: { get: () => T; set: (value: T) => void }): Ref<T>

watchEffect

在响应式地跟踪其依赖关系时立刻运行一个函数,并在依赖关系产生更改时从新运行这个函数。

如:

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

export default defineComponent({
  setup() {
    const count = ref(0)

    watchEffect(() => console.log(count.value)) // 0、1

    setTimeout(() => {
      count.value++
    }, 100)
  },
})
</script>
类型
function watchEffect(
  effect: (onInvalidate: InvalidateCbRegistrator) => void,
  options?: WatchEffectOptions
): StopHandle

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // default: 'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

type InvalidateCbRegistrator = (invalidate: () => void) => void

type StopHandle = () => void

watch

watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认状况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。

watch(source, callback, [options])

参数阐明:

  • source: 能够反对 string,Object,Function,Array; 用于指定要侦听的响应式变量
  • callback: 执行的回调函数
  • options:反对 deep、immediate 和 flush 选项。

接下来我会别离介绍这个三个参数都是如何应用的, 如果你对 watch 的应用不明确的请往下看:

监听reactive定义的数据

如:

<script>
import { defineComponent, reactive, watch } from 'vue'

export default defineComponent({
  setup() {
    const state = reactive({
      nickname: 'DarkCode',
      age: 22
    })

    setTimeout(() => {
      state.age++
    }, 1000)

    // 批改age的值时会触发watch的回调
    watch(
      () => state.age,
      (curAge, preAge) => {
        console.log('new value:', curAge, 'old value:', preAge)
      }
    )
  },
})
</script>
监听ref定义的数据
<script>
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    const count = ref(2021)

    setTimeout(() => {
      count.value++
    }, 1000)

    watch(count, (curCount, preCount) => {
      console.log('new Value and old Value are:', curCount, preCount) // 2022,2021
    })
  },
})
</script>
监听多个数据

语法:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

如,针对下面监听reactive与ref定义的数据:

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

export default defineComponent({
  setup() {
    const count = ref(2021)
    const state = reactive({
      nickname: 'DarkCode',
      age: 22
    })
    setTimeout(() => {
      count.value++
      state.age++
    }, 1000)

    watch([() => state.age, count], ([curAge, curCount], [preAge, preCount]) => {
      console.log("新值:", curAge, "老值:", preAge); console.log("新值:", curCount,"老值:", preCount)
    })
  },
})
</script>
监听简单的嵌套对象

如:

<script>
import { defineComponent, reactive, watch } from 'vue'

export default defineComponent({
  setup() {
    const state = reactive({
      house: {
        id: 11,
        attrs: {
          area: 111.2,
          height: 12,
          label: '学区房',
          owner: 'DarkCode'
        }
      }
    })

    setTimeout(() => {
      state.house.id++
    }, 1000)

    watch(
      () => state.house,
      (newT,oldT) => {
        console.log('new value and oldT are:', newT, oldT)
      },
      { deep: true }
    )
  },
})
</script>

如果不应用第三个参数deep:true, 是无奈监听到数据变动的。 后面咱们提到,默认状况下,watch 是惰性的, 那什么状况下不是惰性的, 能够立刻执行回调函数呢?其实应用也很简略, 给第三个参数中设置immediate: true即可。

stop进行监听

咱们在组件中创立的watch监听,会在组件被销毁时主动进行。如果在组件销毁之前咱们想要进行掉某个监听, 能够调用watch()函数的返回值,操作如下:

<script>
import { defineComponent, reactive, watch } from 'vue'

export default defineComponent({
  setup() {
    const state = reactive({
      house: {
        id: 11,
        attrs: {
          area: 111.2,
          height: 12,
          label: '学区房',
          owner: 'DarkCode'
        }
      }
    })

    setTimeout(() => {
      state.house.id++
    }, 1000)

    setTimeout(() => {
      stopWatchHouse()
    }, 3000)

    const stopWatchHouse = watch(
      () => state.house,
      (newT,oldT) => {
        console.log('new value and oldT are:', newT, oldT)
      },
      { deep: true }
    )
  },
})
</script>

比照watchEffect:

  1. watchEffect 不须要手动传入依赖
  2. watchEffect 会先执行一次用来主动收集依赖
  3. watchEffect 无奈获取到变动前的值, 只能获取变动后的值

Vue3-高级

自定义hooks

这里如果有相熟react的小伙伴的话,那么对hooks是比拟相熟的。在Vue3.x中,提供了自定义hooks,目标在于代码的重用,与vue2.x中mixins的区别在于其性能以及浏览性更好。

如:构建一个平时开发中常见的hooks来进行数据的封装申请解决。

咱们将构建两个可组合的hooks。

  • 第一个hook将用于间接与其余API进行交互
  • 第二个hook将依赖于第一个

/hooks/api.ts

import { ref } from 'vue'

export default function useApi(url: RequestInfo, options ?: RequestInit | undefined) {
  const response = ref()
  const request = async () => {
    const res = await fetch(url, options)
    const data = await res.json()
    response.value = data
  }
  return {
    response,
    request
  }
}

/hooks/products.ts

import useApi from './api'
import { ref } from 'vue'

export default async function useProducts() {
  const { response: products, request } = useApi(
    "https://ecomm-products.modus.workers.dev/"
  )
  const loaded = ref(false)
  if(loaded.value === false) {
    await request()
    loaded.value = true
  }
  return {
    products
  }
}

/test.vue

<template>
  <div>
    <h3>Customers</h3>
    <table id="customers" >
      <tr>
        <th>ID</th>
        <th>title</th>
        <th>category</th>
      </tr>
      <tr v-for="product in products" :key="product.id">
        <td>{{product.id}}</td>
        <td>{{product.title}}</td>
        <td>{{product.category}}</td>
      </tr>
    </table>
  </div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import useProducts from "../hooks/products";
export default defineComponent({
  async setup() {
    const { products } = await useProducts()
     return { products };
  },
});
</script>

留神,给setup加上async。须要给父组件设置<Suspense>包裹子组件,如parent.vue

<template>
  <div>
    <Suspense>
      <template #default>
        <HelloWorld></HelloWorld>
      </template>
      <template #fallback>
        <h1>Loading...</h1>
      </template>
    </Suspense>
  </div>
</template>

再来看一个获取用户信息的例子:

/hooks/user.ts

import useApi from "./api";
import { ref } from "vue";
export interface Location {
  lat: number;
  lng: number;
}
export interface Address {
  street: string;
  suite: string;
  city: string;
  zipcode: number;
  geo: Location;
}
export interface User {
  id: string;
  name: string;
  username: string;
  email: string;
  address: Address;
}
export default async function useUserss() {
  const { response: users, request } = useApi<User[]>(
    "https://jsonplaceholder.typicode.com/users"
  );
  const loaded = ref(false);
  if (loaded.value === false) {
    await request();
    loaded.value = true;
  }
  return { users };
}

/component/User.vue

<script lang="ts">
import { defineComponent } from "vue";
import useUsers from "../hooks/users";
export default defineComponent({
  name: "Users",
  async setup() {
    const { users } = await useUsers();
    return { users };
  },
});
</script>

Teleport

为什么须要

<teleport /> 准许将一个元素从一个中央移到另一个中央。

有了这个意识,咱们再来看一下为什么须要用到 Teleport 的个性呢,看一个小例子: 在子组件Header中应用到Dialog组件,咱们理论开发中常常会在相似的情景下应用到 Dialog ,此时Dialog就被渲染到一层层子组件外部,解决嵌套组件的定位、z-index和款式都变得艰难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 构造应该齐全剥离 Vue 顶层组件挂载的 DOM;同时还能够应用到 Vue 组件内的状态(data或者props)的值。简略来说就是,即心愿持续在组件外部应用Dialog, 又心愿渲染的 DOM 构造不嵌套在组件的 DOM 中。 此时就须要 Teleport 上场,咱们能够用<Teleport>包裹Dialog, 此时就建设了一个传送门,能够将Dialog渲染的内容传送到任何指定的中央。 接下来就举个小例子,看看 Teleport 的应用形式

应用

咱们心愿 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素。

  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <div id="dialog"></div>
  </body>

Dialog.vue

<template>
  <teleport to="#dialog">
    <div class="dialog">
      <div class="dialog_wrapper">
        <div class="dialog_header" v-if="title">
          <slot name="header">
            <span>{{ title }}</span>
          </slot>
        </div>
      </div>
      <div class="dialog_content">
        <slot></slot>
      </div>
      <div class="dialog_footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  setup() {
    return {
      title: '登录'
    }
  },
})
</script>

【腾讯云】轻量 2核2G4M,首年65元

阿里云限时活动-云数据库 RDS MySQL  1核2G配置 1.88/月 速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

您可能还喜欢...

发表回复

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

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据