场景
一个简单的场景:前端发起请求获取users,得到数据后展示在页面。虽然简单,还是有一下几个点需要我们注意:
- 接口请求的这过程,页面状态为loading
- 数据请求成功,由loading =》 展示数据
- 接口请求失败,loading =》 展示报错信息
然后你就会写出包含v-if、v-else-if、v-else,这样的代码不但不那么优雅,而且每一个页面都要来重复写这一套逻辑。
<template>
<div>
<div v-if="isLoading">loading----</div>
<div v-else-if="error">{{ error.message }}</div>
<div v-else>
<pre>{{users}}</pre>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent({
setup() {
// 数据
let users = ref<unknown | null>(null)
// 是否loading
let isLoading = ref(true)
// 错误信息
let error = ref(null)
// 模拟getUser接口, 2s后返回数据
const getUser = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{name: 'jgmiu1', id: 27},
{name: 'jgmiu2', id: 28},
{name: 'jgmiu3', id: 29},
])
}, 2000)
})
}
// 调用接口,根据状态设置值
const fetchUsers = () => {
getUser().then(data => {
//
isLoading.value = false
error.value = null
users.value = data
}).catch(err => {
error.value = err
isLoading.value = false
users.value = null
})
}
onMounted(() =>{
fetchUsers()
})
return {
users,
isLoading,
error
}
},
})
</script>
对于以上的这种重复性的动作,肯定会有大佬去抽离出来,形成一个流程性控制得组件。vue-promised就是一个专门进行异步流程控制得组件,他讲ui与数据进行分离,使用slot-scope在适当的时候回抛数据,出现异常的时候也回抛异常。
简单版vue-promised
参考vue-promised源码,使用vue3的实现一个异步流程控制组件。这个组件旨在流程控住,与视图无关,所以我们的组件不是一个template组件。视图是通过插槽传入的,该组件只关心什么状态下显示什么视图。
前置知识
ts/tsx文件中使用defineComponent创建组件
vue3出来之后,我像react一样在ts/tsx文件编写组件。
import {defineComponent} from "vue";
export const Test = defineComponent({
setup() {
return () => <>
<div>JSX test</div>
</>
}
})
// 这里有个坑 使用export会不生效
export default Test
根据setup(props, {slots})中的slots决定渲染那个插槽
- 新建一个Test组件
import {defineComponent} from "vue";
export const Test = defineComponent({
setup(props, {slots}) {
console.log(slots)
return () => <h1></h1>
}
})
export default Test
- 调用Test组件
<Test>
<template v-slot:pending>
<h1>loading...</h1>
</template>
<template v-slot="data">
<pre>{{data}}</pre>
</template>
<template v-slot="error">
<pre>{{error}}</pre>
</template>
</Test>
插槽:pending、error和default。我们只需要请求进行中展示pending视图,在请求成功之后展示default视图并利用slot-scope外抛数据展示到页面
- 在Test中输出slots如下,可以发现slots是一个对象,每一个插槽是一个函数
4.根据slots展示相应视图。
import {defineComponent} from "vue";
export const Test = defineComponent({
setup(props, {slots}) {
console.log('xxx',slots)
return () => slots['default']!({name: 'xxx'})
}
})
export default Test
具体实现
根据用户传入promise的状态,显示相应视图
Promised
// rejected default pending
import {
defineComponent,
PropType,
toRefs,
reactive
} from 'vue-demi'
import { usePromise, UsePromiseResult } from './usePromise'
export const MyPromised = defineComponent({
name: 'MyPromised',
props: {
// 接受一个用户传入的promise
promise: {} as PropType<Promise<unknown> | null | undefined>,
},
setup(props, {slots}) {
// 将props变成ref对象
const propsAsRefs = toRefs(props)
// 得到promise的状态
const promiseState = reactive<UsePromiseResult>(
usePromise(propsAsRefs.promise)
)
return () => {
// 根据promiseState展示相应的视图
const [slotName, slotData] = promiseState.isRejected
? ['rejected', promiseState.error]
: !promiseState.isPending
? ['default', promiseState.data]
: ['pending', promiseState.data]
return slots[slotName]!(slotData)
}
},
})
- 接受传入的promised
- 调用usePromise得到promisedState
- 根据promisedState展示相应视图
usePromise
实时获取promised的状态
import { Ref, ref, computed, watch, unref, ComputedRef } from 'vue-demi'
type Refable<T> = Ref<T> | T
export function usePromise<T = unknown> (
promise: Refable<Promise<T> | null | undefined>
) {
// 是否有错
const isRejected = ref(false)
// 是否成功
const isResolved = ref(false)
// 是否处于调用(等待)状态
const isPending = computed(() => !isRejected.value && !isResolved.value)
// 异常错误
const error = ref<Error | undefined | null>()
// promise被解决的data
const data = ref<T | null | undefined>()
// 使用watch监听promise,实时向上发送状态
watch(
() => unref(promise),
(newPromise) => {
isRejected.value = false
isResolved.value = false
error.value = null
newPromise?.then(newData => {
data.value = newData
isResolved.value = true
}, err => {
error.value = err
isRejected.value = true
})
},
{
immediate: true
}
)
return {
isRejected, isResolved, isPending, error, data
}
}
// promiseState接口定义
export interface UsePromiseResult<T = unknown> {
isPending: ComputedRef<boolean>
isResolved: Ref<boolean>
isRejected: Ref<boolean>
error: Ref<Error | undefined | null>
data: Ref<T | undefined | null>
}
感想
都2022年了,我还在思考怎么写好一个组件。自身实力远对不起我的工作时间,2022了希望不在沉沦业务,不在错的公司浪费时间。