设计vue3组件
在程序设计过程中,我们经常会遇到很多地方用到相同结构内容的情况,这时,我们想复用一部分代码,这时可以将可复用的UI部分以组件的形态封装,形成一定的组件调用关系。
组件化最简单的就是把一段代码提出来单独写进一个vue文件里了。要做的工作分被调用组件和调用方两个部分
组件设计
defineProps
写入组件第一个面临的事情是原来直接可以使用的数据跨组件不能用了。
这时,只能通过定义props,将组件需要的数据改为从调用方获取。
const props = defineProps({
user: Object,
doc: Object,
});
分支情况1:有些变量,我们希望不要直接反应到调用方去
例如:一个组件化的form,只有点击保存,数据才传回去。
这时,我们可以将实际修改的变量存到一个临时变量中,通过watch对应的props,使得props更新的时候,可以实时更新这个临时值;对应的,在修改完成后,通过emit将值以事件的形式返回回去。
defineExpose
有时,我们还需要将组件的某个功能提供给调用方调取,这时,我们可以通过将部分函数封装成expose,达到在调用方调用的目的。一般来讲,能以props传递的尽量还是使用props,尽量不要使用expose的函数进行值设定。
inject
inject和provide,通常用在一对上下级组件里面,例如我们的开源项目APIcat里用到的简易tabs
分成父级的tabs(总体框架)和子层的tab-item(每个tab页面)两个组件
tabs里面provide了一个状态数据对象
const state = reactive({ selectedIndex: null, count: 0 });
provide("tabsProvider", state)
子对象里inject了这个数据
const tabs = inject("tabsProvider");
watch( () => tabs.selectedIndex, () => {
isActive.value = props.name === tabs.selectedIndex;
if (isActive.value) {
emits("shown")
}
});
onBeforeMount(() => {
tabs.count++;
if (tabs.selectedIndex == null) {
tabs.selectedIndex = props.name
}
if (tabs.selectedIndex == props.name) {
isActive.value = true;
}
});
注意那个count,父组件里面是0,子组件里通过onBeforeMount,将其做了自增操作,因为每个组件都做了一次onBeforeMount,实际达成的效果就是动态获得总的tab数量,同时通过对selectedIndex的watch,实现了对选中状态的监听,这样,子组件就可以控制显示与否了。
这个做法element-ui里特别多,但是那个里面更为复杂,用到了很多render等高级技术,如果要在组件化的高级道路上继续发展,可以去看他们的源码,如果初级应用,看看我们的APIcat(界面部分在ui里面)。
调用方设计
调用方就是调用不同的组件了,需要讲的点有两个
活用component
作为调用方,可能我们页面某个位置是按条件调用不同的组件的,例如我们的百家饭网站,API操作页面有不同的面板,通过切换左侧按钮切换
这时,我们可以使用component语法来加载不同的模块
<component :is="Opening"></component>
通过为Opening设置不同的模块,就可以加载不同的页面组件了
function handleSelect(to) {
switch (to) {
case "structure":
Opening.value = markRaw(Structure)
break
case "json_editor":
Opening.value = markRaw(jsonEditor);
break;
case "document":
Opening.value = markRaw(Editor);
break;
case "tester":
Opening.value = markRaw(Tester);
break;
...
default:
break;
}
}
活用promise
有时,组件还会遇到从不同的数据来源获取数据的问题,例如从本地文件、远程api、内存、剪贴板等。我们可以活用promise来做到
function open(id, req_code = "") {
var p;
if (id === "local") {
p = getYamlAndOpen(localCache.value, { Editable: true });
} else if (id === "copy") {
p = navigator.permissions.query({ name: 'clipboard-read' }).then((permission) => {
if (permission.state !== 'denied') {
return navigator.clipboard.read()
}
return Promise.reject("没有剪贴板访问权限")
}).then((clipboardContents) => {
...
} else if (id === "disk") {
p = uploadBlob().catch(() => { });
} else {
p = new_axios
...
.then((data) => {
return getYamlAndOpen(data, docInfo.value);
})
.catch((err) => testNeedCode(id, err));
}
p.finally(() => {
loading.close();
});
}
通过在不同的分支里定义promise,并返回给下游操作,下游操作可以避免不同加载方式的区别,统一进行操作。