主要功能点:
- 子组件使用v-model,通过computed修改父组件数据(setup语法糖, 从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏)
- dom内局部注册组件和使用组件
- 使用 ES 模块构建版本
目录结构
index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" />
</head>
<body>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
"element-plus": "https://unpkg.com/element-plus@2.7.2/dist/index.full.min.mjs",
"@element-plus/icons-vue": "https://unpkg.com/browse/@element-plus/icons-vue@2.3.1/dist/index.min.js"
}
}
</script>
<div id="app">
<el-tabs type="border-card">
<el-tab-pane label="父组件">
<h3>DOM内模版解析需要遵从html解析行为,使用kebab-case为组件命名,并显式的指定闭合标签</h3>
<h3>请注意下面讨论只适用于直接在 DOM 中编写模板的情况。如果你使用来自以下来源的字符串模板,就不需要顾虑这些限制了:</h3>
<ul>
<li>单文件组件</li>
<li>内联模板字符串 (例如 template: '...')</li>
<li><script type="text/x-template"></li>
</ul>
<pre>
<code>
<h2>kebab-case命名法</h2><component-a></component-a> <el-button type="success" circle ><el-icon :size="20"><Check /></el-icon></el-button>
<h2>camelCase命名法</h2><componentB></componentB>
<h2>PascalCase命名法</h2><ComponentB></componentB>
</code>
</pre>
</el-tab-pane>
<el-tab-pane label="组件B">
<component-b></component-b>
</el-tab-pane>
<el-tab-pane label="组件C">
<component-c/>
</el-tab-pane>
</el-tabs>
</div>
<script type="text/x-template" id="main-template">
<ComponentA :loginModel="loginModel" v-model:dialogVisible="loginModel.dialogVisible"/>
<el-button @click="loginModel.dialogVisible=true">{{ loginModel.title }}</el-button>
</script>
<script type="module">
import {
createApp,
ref,
reactive,
watchEffect,
watch
} from 'vue'
import ElementPlus from "element-plus"
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import ComponentA from './components/component-a.js'
//定义B组件
const ComponentB = {
components: {
ComponentA,
},
setup() {
const loginModel=ref({
title:"微信登录",
signIn:"扫码登录",
url:"data:image/png;base64,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",
tip:"使用手机微信扫码登录",
dialogVisible:false
})
return {
loginModel,
}
},
template: `
<ComponentA :loginModel="loginModel" v-model:dialogVisible="loginModel.dialogVisible"/>
<el-button @click="loginModel.dialogVisible=true">{{ loginModel.title }}</el-button>
`
}
//定义C组件
const ComponentC = {
//局部注册组件
components: {
ComponentA,
},
setup() {
const loginModel=ref({
title:"QQ登录",
signIn:"扫码登录",
url:"data:image/png;base64,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",
tip:"使用手机QQ扫码登录",
dialogVisible:false
})
return {
loginModel,
}
},
template: "#main-template"
}
const app = createApp({
components: {
ComponentB,
ComponentC
},
setup() {
const msg = ref("msg")
return {
msg
}
}
})
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus).mount('#app')
</script>
</body>
</html>
component-a.js
import { ref } from 'vue'
import {ElMessageBox} from "element-plus"
export default {
props:["loginModel","dialogVisible"],
//emits可省略但不能写错
emits:["update:dialogVisible"],
computed:{
visible:{
get(){
return this.dialogVisible
},
set(val){
this.$emit('update:dialogVisible',val)
}
}
},
methods:{
handleClose (done) {
this.visible=false
}
},
template: `
<el-dialog
v-model="visible"
:title="loginModel.signIn"
width="500"
style="text-align:center"
:before-close="handleClose"
>
<img :src="loginModel.url"/>
<p>{{loginModel.tip}}</p>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible=false">Cancel</el-button>
</div>
</template>
</el-dialog>
`
}