Options API
在Vue2中,我们组织一个组件代码的方式是(且只能是)Options API。Options API的特点是将组件需要的状态(data)和方法(methods)等全部写在一个对象,比如以下计数器的例子:
Options API的弊端
因为只需要将代码写在指定的地方,Options API对新手很友好,这也是Vue容易上手的原因。但随着组件的复杂度增长,处理相同逻辑的代码分散在文件的各处,这些复杂的组件往往有几百上千行代码,阅读和导航经常需要上下反复横跳。 比如以下的文档编辑组件,主要包含3个功能
- 修改文档名称
- 编辑器
- 分享
使用Options API的代码是这样的:
<template>
<div v-if="hasInit" class="doc-container">
<div class="header aic-jcsb">
<div class="left">
<div class="left-content">
<doc-name-input class="doc-name" :value="docDetail?.name" @blur="saveDocName" />
<!-- <clickable-input class="doc-name" :value="docDetail?.name" @blur="saveDocName" />-->
<el-tooltip content="查看历史版本" size="small" placement="bottom">
<div class="doc-status" @click="viewHistory">
<!-- 保存中 -->
<div v-if="isEditorSaving">正在保存中…</div>
<!-- 内容已被修改 -->
<div v-else-if="isEditorModify">待保存</div>
<!-- 最近修改 -->
<div v-else-if="formatUpdateDate">最近修改:{{ formatUpdateDate }}</div>
</div>
</el-tooltip>
</div>
</div>
<div class="right">
<el-button icon="share" @click="showCopyLink = true"> 分享 </el-button>
<el-button v-if="isEdit" :loading="isEditorSaving" type="primary" icon="finished" @click="() => {
saveEditorContent()
}
">
更新
</el-button>
<el-button v-if="!isEditMode" :type="isEdit ? 'default' : 'primary'" :icon="isEdit ? 'switch-button' : 'edit'"
@click="toggleEditorReadOnly">{{ isEdit ? '退出编辑' : '编辑' }}</el-button>
</div>
</div>
<div class="editor-wrap">
<Editor class="editor" ref="refEditor" holder="doc-editor" :config="editorConfig" @ready="onEditorReady"
@change="onEditorChange" />
<doc-outline class="doc-outline" :editor="editor" :blocks="docDetail?.content?.blocks || []" />
</div>
<!-- 复制分享链接弹窗 -->
<dialog-copy-share-link v-model="showCopyLink" :book-hash-id="book.node.hash_id" :share-link="shareLink"
@success="broadcastRefreshEvent" />
</div>
</template>
<script>
// 省略模块引入
// 处理自动保存,内容变化后,2分钟保存一次
let saveTimer = null
export default {
name: 'OptionsApiDoc',
components: {
Editor,
DocOutline,
DocNameInput,
DialogCopyShareLink
},
inject: ['book', 'space'],
setup() {
// 知识库详情eventbus
const bookDetailEventBus = useEventBus(EventBookDetail.EVENT_BUS_NAME)
return {
bookDetailEventBus
}
},
data() {
return {
hasInit: false,
docDetail: null,
editor: null,
isEditorModify: false,
isEditorSaving: false,
isEdit: false,
isEditMode: false,
showCopyLink: false
}
},
computed: {
// 编辑器配置
editorConfig() {
return {
data: this.docDetail?.content,
readOnly: !this.isEdit,
placeholder: ''
}
},
// 分享链接
shareLink() {
const domain = getSpaceDomain(this.space)
return `${domain}/book/${this.book?.node.share_id}/${this.docDetail?.node.hash_id}`
},
formatUpdateDate() {
if (!this.docDetail?.updated_at) return ''
return dayjs(this.docDetail?.updated_at).format('MM月DD号 HH:mm')
}
},
watch: {
// 监听编辑态模式,赋值编辑态
book: {
handler(val) {
this.isEditMode = val.node.is_edit
this.isEdit = this.isEditMode
},
deep: true,
immediate: true
},
// 监听编辑态取消时,自动保存内容
isEdit: {
async handler(val) {
if (!val && this.isEditorModify) {
await this.saveEditorContent()
}
// 切换编辑器状态
this.$nextTick(() => {
this.$refs.refEditor.initEditor()
})
}
},
isEditorModify(val) {
if (val) {
saveTimer = setTimeout(() => {
this.saveEditorContent()
}, 2 * 60 * 1000)
} else {
clearTimeout(saveTimer)
saveTimer = null
}
},
$route: {
handler: debounce(async (val) => {
if (val) {
// 清除自动保存timer
if (saveTimer) {
clearTimeout(saveTimer)
saveTimer = null
}
// 保存编辑器数据
await this.saveEditorContent(true)
// 页面初始化
await this.initData()
}
}, 500)
}
},
beforeMount() {
this.initData()
// 文档详情eventbus
const docDetailEventBus = useEventBus(EventDocDetail.EVENT_BUS_NAME)
docDetailEventBus.on(this.onReceiveEvent)
},
beforeUnmount() {
// 销毁组件前保存编辑器数据
this.saveEditorContent(true)
// 页面销毁时,清除监听事件
document.onkeydown = null
},
methods: {
// 广播知识库刷新事件
broadcastRefreshEvent() {
this.bookDetailEventBus.emit(EventBookDetail.EVENT_MAP.REFRESH)
},
// 打开编辑器编辑态
async toggleEditorReadOnly() {
this.isEdit = !this.isEdit
},
async getDocDetail() {
const { data } = await apiDoc.getDocDetail(this.$route.params.node_hash_id)
this.docDetail = data
// 设置标题
document.title = getPageTitle(this.docDetail.name)
},
async saveDocName(val) {
if (!val.trim()) return
if (val.trim() !== this.docDetail.name) {
// 更新文档名称
await apiDoc.updateDoc(this.docDetail.id, {
name: val
})
// 通知外层刷新数据
this.broadcastRefreshEvent()
// 获取文档详情
await this.getDocDetail()
}
},
// 监听编辑器初始化完成
onEditorReady(editorInstance) {
this.editor = editorInstance
},
async onEditorChange(api, event) {
// 如果是标题变更,需要更新文档内容,以便大纲可以得到更新
const formatEvent = Array.isArray(event) ? event : [event]
if (formatEvent.some((item) => item?.detail?.target?.name === 'header')) {
const content = await api.saver.save()
this.docDetail.content = content
}
this.isEditorModify = true
},
// 保存编辑器内容
async saveEditorContent(quickAndSave = false) {
if (this.isEditorModify) {
this.isEditorSaving = true
// 获取编辑器内容
const content = await this.editor.save()
// 保存内容
await apiDoc.updateDoc(this.docDetail.id, {
content
})
this.isEditorSaving = false
this.isEditorModify = false
if (quickAndSave) {
ElMessage.success(`「${this.docDetail?.name}」自动保存成功`)
} else {
// 刷新节点内容
await this.getDocDetail()
}
}
if (!quickAndSave) {
ElMessage.success('保存成功')
}
},
// 初始化编辑器的快捷键
initEditorShortCuts() {
this.$nextTick(() => {
// 处理快捷键事件
const handleShortCuts = (e) => {
// 保存操作
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
// 阻止默认事件
e.preventDefault()
if (this.isEdit) {
// 保存编辑器内容
this.saveEditorContent()
}
}
}
document.onkeydown = handleShortCuts
})
},
// 初始化数据
async initData() {
this.hasInit = false
this.isEditorModify = false
this.isEditorSaving = false
this.isEdit = this.isEditMode
// 获取文档详情
await this.getDocDetail()
this.hasInit = true
// 设置编辑器中的快捷键
this.initEditorShortCuts()
// 滚动到内容顶部
document.querySelector('#layout-book-content').scrollTop = 0
},
// 查看文档历史记录
viewHistory() {
const { book_node_hash_id, node_hash_id } = this.$route.params
this.$router.push(`/edit/book/${book_node_hash_id}/${node_hash_id}/history`)
},
// 监听文档刷新事件
onReceiveEvent(event) {
if (event === EventDocDetail.EVENT_MAP.REFRESH) {
this.getDocDetail()
}
}
}
}
</script>
其中和分享文档相关的代码,分散在文件的85行、97-101行、172-175行,Vue3中新增了Composition API来解决这个问题。
Composition API
Composition API是一系列API的集合,它允许我们使用引入的函数来组织代码,包括:
- 响应式API: ref() reactive() ...
- 生命周期钩子函数:onBeforeMount()...
- 依赖注入:provide() inject()
对上面用Options API写的计数器例子进行重构
对比Options API,Composition API不需要再通过export default导出包含选项的对象,组件的状态和方法都写在了一个
<script setup></script>标签中。data中的数据需要使用ref()包裹一层,同时在中访问的时候需要加上.value,methods中的方法在Composition API中对应的是函数。
Composition API的好处
由于Composition API是通过导入的函数去定义组件,因此它可以
- 更好地组织代码:相同逻辑的代码全部写在一块,大大提升的阅读体验和可维护性,不再需要上下反复横跳,相比Options API更灵活。但灵活是把双刃剑,容易写出不好维护的代码,所以我们需要探索Composition API的最佳实践。具体来说就是如何组织setup中的代码,让代码的阅读性和可维护性更好。
- 能更好地复用:有了Composition API,我们就能将项目中能复用的逻辑抽成一个js文件,过去我们是用Options API的mixin实现复用的,Composition API提供了比mixin更灵活,更直观的方式来复用。
现在来用Composition API来重构文档编辑页组件的代码
<script setup>
// 省略模块引入
// 知识库详情eventbus
const bookDetailEventBus = useEventBus(EventBookDetail.EVENT_BUS_NAME)
// 广播知识库刷新事件
const broadcastRefreshEvent = () => {
bookDetailEventBus.emit(EventBookDetail.EVENT_MAP.REFRESH)
}
const book = inject('book')
const space = inject('space')
// 路由
const route = useRoute()
const router = useRouter()
// 页面是否完成初始化
const hasInit = ref(false)
// 文档内容
const docDetail = ref()
// 编辑器实例
let editor = ref()
// 编辑器组件示例
const refEditor = ref()
// 编辑器内容是否修改
const isEditorModify = ref(false)
// 编辑器内容保存中
const isEditorSaving = ref(false)
// 是否编辑态
const isEdit = ref(false)
// 是否编辑态模式
const isEditMode = ref(false)
// 编辑器配置
const editorConfig = computed(() => {
return {
data: docDetail.value?.content,
readOnly: !isEdit.value,
placeholder: ''
}
})
// 监听编辑态模式,赋值编辑态
watch(
() => book.value?.node.is_edit,
(val) => {
isEditMode.value = val
isEdit.value = isEditMode.value
},
{
immediate: true
}
)
// 监听编辑态取消时,自动保存内容
watch(
() => isEdit.value,
async (val) => {
if (!val && isEditorModify.value) {
await saveEditorContent()
}
// 切换编辑器状态
nextTick(() => {
refEditor.value.initEditor()
})
}
)
// 打开编辑器编辑态
const toggleEditorReadOnly = async () => {
isEdit.value = !isEdit.value
}
// 分享链接
const shareLink = computed(() => {
const domain = getSpaceDomain(space.value)
return `${domain}/book/${book.value?.node.share_id}/${docDetail.value?.node.hash_id}`
})
// 是否展示分享弹窗
const showCopyLink = ref(false)
// 文档更新时间
const formatUpdateDate = computed(() => {
if (!docDetail.value?.updated_at) return ''
return dayjs(docDetail.value?.updated_at).format('MM月DD号 HH:mm')
})
// 获取文档详情
const getDocDetail = async () => {
const { data } = await apiDoc.getDocDetail(route.params.node_hash_id)
docDetail.value = data
// 设置标题
document.title = getPageTitle(docDetail.value.name)
}
// 保存文档名称
const saveDocName = async (val) => {
console.log('保存文档名称', val);
if (!val.trim()) return
if (val.trim() !== docDetail.value.name) {
// 更新文档名称
await apiDoc.updateDoc(docDetail.value.id, {
name: val
})
// 通知外层刷新数据
broadcastRefreshEvent()
// 获取文档详情
await getDocDetail()
}
}
// 监听编辑器初始化完成
const onEditorReady = (editorInstance) => {
editor.value = editorInstance
}
const onEditorChange = async (api, event) => {
// 如果是标题变更,需要更新文档内容,以便大纲可以得到更新
const formatEvent = Array.isArray(event) ? event : [event]
if (formatEvent.some((item) => item?.detail?.target?.name === 'header')) {
const content = await api.saver.save()
docDetail.value.content = content
}
isEditorModify.value = true
}
// 保存编辑器内容
const saveEditorContent = async (quickAndSave = false) => {
if (isEditorModify.value) {
isEditorSaving.value = true
// 获取编辑器内容
const content = await editor.value.save()
// 保存内容
await apiDoc.updateDoc(docDetail.value.id, {
content
})
isEditorSaving.value = false
isEditorModify.value = false
if (quickAndSave) {
ElMessage.success(`「${docDetail.value?.name}」自动保存成功`)
} else {
// 刷新节点内容
await getDocDetail()
}
}
if (!quickAndSave) {
ElMessage.success('保存成功')
}
}
// 初始化编辑器的快捷键
const initEditorShortCuts = () => {
nextTick(() => {
// 处理快捷键事件
const handleShortCuts = (e) => {
// 保存操作
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
// 阻止默认事件
e.preventDefault()
if (isEdit.value) {
// 保存编辑器内容
saveEditorContent()
}
}
}
document.onkeydown = handleShortCuts
})
}
// 初始化数据
const initData = async () => {
hasInit.value = false
isEditorModify.value = false
isEditorSaving.value = false
isEdit.value = isEditMode.value
// 获取文档详情
await getDocDetail()
hasInit.value = true
// 设置编辑器中的快捷键
initEditorShortCuts()
// 滚动到内容顶部
document.querySelector('#layout-book-content').scrollTop = 0
}
// 查看文档历史记录
const viewHistory = () => {
const { book_node_hash_id, node_hash_id } = route.params
router.push(`/edit/book/${book_node_hash_id}/${node_hash_id}/history`)
}
// 监听文档刷新事件
const onReceiveEvent = (event) => {
if (event === EventDocDetail.EVENT_MAP.REFRESH) {
getDocDetail()
}
}
// 文档详情eventbus
const docDetailEventBus = useEventBus(EventDocDetail.EVENT_BUS_NAME)
docDetailEventBus.on(onReceiveEvent)
// 处理自动保存,内容变化后,2分钟保存一次
let saveTimer = null
watch(
() => isEditorModify.value,
(val) => {
if (val) {
saveTimer = setTimeout(() => {
saveEditorContent()
}, 2 * 60 * 1000)
} else {
clearTimeout(saveTimer)
saveTimer = null
}
}
)
// 监听路由中的文档hashId
// 获取文档内容
watch(
() => route.params.node_hash_id,
debounce(async (val) => {
if (val) {
// 清除自动保存timer
if (saveTimer) {
clearTimeout(saveTimer)
saveTimer = null
}
// 保存编辑器数据
await saveEditorContent(true)
// 页面初始化
await initData()
}
}, 500)
)
// 页面进来时初始化数据
onBeforeMount(() => {
initData()
})
onBeforeUnmount(() => {
// 销毁组件前保存编辑器数据
saveEditorContent(true)
// 页面销毁时,清除监听事件
document.onkeydown = null
})
</script>
<template>
<div v-if="hasInit" class="doc-container">
<div class="header aic-jcsb">
<div class="left">
<div class="left-content">
<doc-name-input class="doc-name" :value="docDetail?.name" @blur="saveDocName" />
<!-- <clickable-input class="doc-name" :value="docDetail?.name" @blur="saveDocName" />-->
<el-tooltip content="查看历史版本" size="small" placement="bottom">
<div class="doc-status" @click="viewHistory">
<!-- 保存中 -->
<div v-if="isEditorSaving">正在保存中…</div>
<!-- 内容已被修改 -->
<div v-else-if="isEditorModify">待保存</div>
<!-- 最近修改 -->
<div v-else-if="formatUpdateDate">最近修改:{{ formatUpdateDate }}</div>
</div>
</el-tooltip>
</div>
</div>
<div class="right">
<el-button icon="share" @click="showCopyLink = true"> 分享 </el-button>
<el-button v-if="isEdit" :loading="isEditorSaving" type="primary" icon="finished" @click="() => {
saveEditorContent()
}
">
更新
</el-button>
<el-button v-if="!isEditMode" :type="isEdit ? 'default' : 'primary'" :icon="isEdit ? 'switch-button' : 'edit'"
@click="toggleEditorReadOnly">{{ isEdit ? '退出编辑' : '编辑' }}</el-button>
</div>
</div>
<div class="editor-wrap">
<Editor class="editor" ref="refEditor" holder="doc-editor" :config="editorConfig" @ready="onEditorReady"
@change="onEditorChange" />
<doc-outline class="doc-outline" :editor="editor" :blocks="docDetail?.content?.blocks || []" />
</div>
<!-- 复制分享链接弹窗 -->
<dialog-copy-share-link v-model="showCopyLink" :book-hash-id="book.node.hash_id" :share-link="shareLink"
@success="broadcastRefreshEvent" />
</div>
</template>
85~92行是和分享文档相关的代码,和Options API不同的是,功能相关的数据和computed写在了一起。正如前面所说,灵活是把双刃剑,Composition API允许我们将处理相同逻辑的代码写在一起,但大多数组件都不会只有一个功能,随着功能越来越复杂,写在script中的代码也会越来越多,参考上面的代码,虽然相同逻辑的代码都在一块,但还是不好阅读和维护。因此我们可以进一步对代码进行拆分,将独立的功能逻辑写在一个函数中,这样的函数在Vue中被称为组合式函数。
组合式函数
在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
组合式函数的概念就不多介绍了,可参考文档组合式函数。简单来说就是一个函数,定义了数据(响应式的)和方法。 我们尝试用组合式函数再次重构文档编辑组件的代码:
<script setup>
// 省略模块引入
const route = useRoute()
const router = useRouter()
const book = inject('book')
const space = inject('space')
// 编辑器组件示例
const refEditor = ref()
const { docDetail, broadcastRefreshEvent, saveDocName, formatUpdateDate, getDocDetail } = useEditDocName()
const { showCopyLink, shareLink } = useCopyShareLink()
const {
editor,
editorConfig,
isEditorModify,
isEditorSaving,
isEdit,
isEditMode,
onEditorReady,
onEditorChange,
toggleEditorReadOnly,
saveEditorContent
} = useEditor({
book,
docDetail,
getDocDetail,
refEditor
})
function useEditDocName() {
const docDetail = ref(null)
// 知识库详情eventbus
const bookDetailEventBus = useEventBus(EventBookDetail.EVENT_BUS_NAME)
// 广播知识库刷新事件
const broadcastRefreshEvent = () => {
console.log('onReceiveEvent');
bookDetailEventBus.emit(EventBookDetail.EVENT_MAP.REFRESH)
}
// 文档更新时间
const formatUpdateDate = computed(() => {
if (!docDetail.value?.updated_at) return ''
return dayjs(docDetail.value?.updated_at).format('MM月DD号 HH:mm')
})
// 获取文档详情
async function getDocDetail() {
const { data } = await apiDoc.getDocDetail(route.params.node_hash_id)
docDetail.value = data
// 设置标题
document.title = getPageTitle(docDetail.value.name)
}
// 保存文档名称
async function saveDocName(val) {
console.log('保存文档名称', val);
if (!val.trim()) return
if (val.trim() !== docDetail.value.name) {
// 更新文档名称
await apiDoc.updateDoc(docDetail.value.id, {
name: val
})
// 通知外层刷新数据
broadcastRefreshEvent()
// 获取文档详情
await getDocDetail()
}
}
onBeforeMount(() => {
getDocDetail()
})
return {
docDetail,
broadcastRefreshEvent,
saveDocName,
formatUpdateDate,
getDocDetail
}
}
function useCopyShareLink() {
// 是否展示分享弹窗
const showCopyLink = ref(false)
// 分享链接
const shareLink = computed(() => {
const domain = getSpaceDomain(space.value)
return `${domain}/book/${book.value?.node.share_id}/${docDetail.value?.node.hash_id}`
})
return {
showCopyLink,
shareLink
}
}
function useEditor({ book, docDetail, getDocDetail, refEditor }) {
// 省略具体实现
return {
editor,
editorConfig,
onEditorReady,
onEditorChange,
isEditorModify,
isEditorSaving,
toggleEditorReadOnly,
isEdit,
isEditMode,
saveEditorContent
}
}
// 查看文档历史记录
function viewHistory() {
const { book_node_hash_id, node_hash_id } = route.params
router.push(`/edit/book/${book_node_hash_id}/${node_hash_id}/history`)
}
// 监听文档刷新事件
function onReceiveEvent(event) {
if (event === EventDocDetail.EVENT_MAP.REFRESH) {
console.log('doc 监听文档刷新事件')
getDocDetail()
}
}
// 文档详情eventbus
const docDetailEventBus = useEventBus(EventDocDetail.EVENT_BUS_NAME)
docDetailEventBus.on(onReceiveEvent)
onBeforeUnmount(() => {
// 销毁组件前保存编辑器数据
saveEditorContent(true)
// 页面销毁时,清除监听事件
document.onkeydown = null
})
</script>
<template>
<div v-if="docDetail" class="doc-container">
<div class="header aic-jcsb">
<div class="left">
<div class="left-content">
<doc-name-input class="doc-name" :value="docDetail?.name" @blur="saveDocName" />
<!-- <clickable-input class="doc-name" :value="docDetail?.name" @blur="saveDocName" />-->
<el-tooltip content="查看历史版本" size="small" placement="bottom">
<div class="doc-status" @click="viewHistory">
<div v-if="isEditorSaving">正在保存中…</div>
<div v-else-if="isEditorModify">待保存</div>
<div v-else-if="formatUpdateDate">最近修改:{{ formatUpdateDate }}</div>
</div>
</el-tooltip>
</div>
</div>
<div class="right">
<el-button icon="share" @click="showCopyLink = true"> 分享 </el-button>
<el-button v-if="isEdit" :loading="isEditorSaving" type="primary" icon="finished" @click="() => {
saveEditorContent()
}
">
更新
</el-button>
<el-button v-if="!isEditMode" :type="isEdit ? 'default' : 'primary'"
:icon="isEdit ? 'switch-button' : 'edit'" @click="toggleEditorReadOnly">{{ isEdit ? '退出编辑' : '编辑'
}}</el-button>
</div>
</div>
<div class="editor-wrap">
<Editor class="editor" ref="refEditor" holder="doc-editor" :config="editorConfig" @ready="onEditorReady"
@change="onEditorChange" />
<doc-outline class="doc-outline" :editor="editor" :blocks="docDetail?.content?.blocks || []" />
</div>
<!-- 复制分享链接弹窗 -->
<dialog-copy-share-link v-model="showCopyLink" :book-hash-id="book.node.hash_id" :share-link="shareLink"
@success="broadcastRefreshEvent" />
</div>
</template>
在上面的代码,我们定义了3个组合式函数useEditDocName useCopyShareLink useEditor,分别处理“修改文档名称”、“分享文档”和“编辑器”。一个组合式函数的结果可以作为另一个组合式函数的参数,使用组合式函数后页面的逻辑就是由各个组合式函数的调用组成,后续新增功能就加一个组合式函数,移除功能就移除一个组合式函数,不需要上下横跳地找找到对应的data、computed、method等进行删除。当组合式函数的逻辑很复杂时,如useEditor,我们还可以在组件文件夹下新建一个hooks文件夹,将组合式函数抽离到单独的js文件中。
最佳实践
- 怎么组织代码不能一概而论,需要根据组件的复杂度:
- 简单组件:单个功能,或者多个很简单的功能。直接写逻辑,按照一定顺序。
- 复杂组件:多个功能,或者单个很复杂的功能,使用组合式函数,对于逻辑很复杂组合式函数应抽离至组件目录下的hooks目录。
对于组件复杂度的判断,应该是根据开发经验判断,不能只依赖功能的数量,比如一个组件只有增删改3个功能,也不能算是复杂组件。再如上面编辑页的例子,同样是组合式函数,处理编辑器逻辑的useEditor应该抽离到hooks中。
- 能复用的逻辑抽离组合式函数到hooks中。
参考项目
通过注释的方式标记功能的起始 region表示区域的起始,方便阅读和跳转
//#region 改
const currentUpdateId = ref<undefined | string>(undefined)
const handleUpdate = (row: GetTableData) => {
currentUpdateId.value = row.id
formData.username = row.username
dialogVisible.value = true
}
//#endregion
优点:
- 组织结构清晰:通过注释标记代码区域,可以清晰地看到代码的组织结构,知道哪部分代码属于同一功能模块。
- 提高可读性:有助于其他开发者理解代码的功能和结构,提高代码的可读性。
- 方便导航:在某些IDE(如Visual Studio Code或IntelliJ IDEA)中,可以利用这种注释方便地在不同的代码区域之间跳转,提高开发效率。
缺点:
- 增加代码量:这种注释会增加代码量,可能会给代码带来一些冗余。
- 维护成本:如果代码的结构发生改变,需要同时更新注释,增加了维护成本。
- 可能引起混淆:如果注释和实际代码的功能不匹配,可能会引起混淆,误导其他开发者。
- 不是标准做法:这种做法并不是广泛接受的标准做法,不同的开发者可能有不同的理解和使用方式。
总的来说,这种方法在组织大型、复杂的代码文件时可能会有所帮助,但是也需要注意其可能带来的问题。
将组件的功能划分为一个个组合式函数,组合式函数返回状态或方法,提供下个组合式函数或者模板调用,组合式 api可以让我们像组织JavaScript那样组织组件。
写在最后
目前Vue3实践经验还不足,后面通过大量实践后还需要完善开发的规范,包括Composition API的调用顺序等。