安装
独立安装
可以在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
时,它接受两个参数:
{Data} props
{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
-> usesetup()
created
-> usesetup()
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
从上面的对比可以看出:
beforeCreate
和created
被setup
替换了- 钩子命名都增加了
on
- 新增用于调试的钩子函数
onRenderTriggered
和onRenderTricked
- 将 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中,可以使用reactvie
和ref
来定义数据
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.name
、user.hobby
等比较繁琐。那么能否将user
对象进行解构,直接得到它的相关属性呢?这是不能的,因为会消除它的响应式。但我们可以借助toRefs
。toRefs 用于将一个 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
返回reactive
或readonly
代理的原始对象。只是一个转义口,可用于临时读取而不会产生代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用,使用的时候要谨慎。
如:
<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
,并对其依赖项跟踪进行显式控制,并触发更新。它需要一个工厂函数,该函数接收track
和trigger
函数作为参数,并返回带有get
和set
的对象。
如:
<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>
另外,它还可以使用带有get
和set
函数的对象来创建可写的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
:
- watchEffect 不需要手动传入依赖
- watchEffect 会先执行一次用来自动收集依赖
- 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>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。