总结实用(项目里能马上用到的)的 vue 组件设计方法,让封装组件更加容易,代码更加好维护。
vue2 如何共享公共行为的?
-
mixins -- 缺点太对,不建议使用
-
renderLess 组件提供公共的业务逻辑
比如下面一个点击某个 div 外部的组件
<script>
export default {
name: 'onClickOutside',
props: ['clickOutside'],
mounted() {
const listener = e => {
if (e.target === this.$el || this.$el.contains(e.target)) {
return
}
this.clickOutside()
}
document.addEventListener('click', listener)
this.$once('hook:beforeDestroy', () => document.removeEventListener('click', listener))
},
render() {
return this.$slots.default[0]
},
}
</script>
vue3 的写法
<script>
import { getCurrentInstance, onMounted, onBeforeUnmount, ref, defineComponent } from 'vue'
export default defineComponent({
name: 'OnClickOutside',
props: ['clickOutside'],
setup(props, { emit, attrs, slots }) {
const vm = getCurrentInstance()
const listener = event => {
const isClickInside = vm.subTree.children.some(element => {
const el = element.el
return event.target === el || el.contains(event.target)
})
if (isClickInside) {
console.log('clickInside')
return
}
props.clickOutside && props.clickOutside()
}
onMounted(() => {
document.addEventListener('click', listener)
})
onBeforeUnmount(() => {
document.removeEventListener('click', listener)
})
return () => slots.default()
},
})
</script>
vue3 中获取$el
比较麻烦, 并且 vue3 的组合 api 提供了更加优雅的解决方案,后续把这个组件提取成组合函数。
再看一个使用 renderLess 组件封装业务逻辑的组件:
<script>
// scoped-slot + render == 业务组件:实现共享业务逻辑
export default {
name: 'FetchData',
props: ['url'],
data() {
return {
data: null,
loading: true,
}
},
created() {
fetch(this.url)
.then(response => response.json())
.then(json => {
setTimeout(() => {
this.json = json
this.loading = false
}, 2000)
})
},
render() {
return this.$scopedSlots.default({
json: this.data,
loading: this.loading,
})
},
}
</script>
vue3 的写法:
export const FetchData = (props, { slots }) => {
const data = { name: 'JackChou' }
return [slots.left(data), slots.default(), slots.right()]
}
使用:
<FetchData>
<template #left="{ name }">left,{{ name }}</template>
<p>world</p>
<template #right>right</template>
</FetchData>
使用函数组件封装公共逻辑的缺点是无法使用生命钩子,希望在 FetchData 中请求后台数据,然后在父组件布局展示。
<FetchData url="https://api.github.com/users/jackchoumine">
<!--这里展示数据 -->
</FetchData>
优化后的组件:
import { defineComponent, onMounted, ref } from 'vue'
export const FetchData = defineComponent({
props: ['url'],
setup(props, { slots }) {
const userInfo = ref({})
const loading = ref(true)
onMounted(() => {
fetch(props.url)
.then(res => res.json())
.then(data => {
console.log(data)
userInfo.value = data
loading.value = false
})
})
return () => slots.default({ userInfo: userInfo.value, loading: loading.value })
},
})
使用:
<FetchData url="https://api.github.com/users/jackchoumine">
<template #default="{ userInfo, loading }">
<p v-if="loading">正在加载...</p>
<p v-else-if="userInfo.avatar_url">
<img :src="userInfo.avatar_url" />
</p>
<div v-else>
<h3>error</h3>
<p>{{ userInfo.message }}</p>
</div>
</template>
</FetchData>