SFC
我们如果是打算使用vue完整的写完整个项目, 就很适合使用单文件组件,也就是.vue文件, 它的英文名字是Single-File Components,就是SFC的缩写,也就是一个文件代表着一个组件,我们把组件的模板,样式,逻辑都写在这一个文件内,在使用vite这样的构建工具的时候,当下载完所有依赖后,可以在我们的node_module里看到下载的vue,有几个依赖插件, 其中一个名为@vue/compiler-sfc,这个就是用于解析我们的单文件组件的,其中还包含一个名为@vue/compiler-dom的依赖库,下面解释这个库的作用
CDN使用vue
CDN引入的vue的js文件都需要能够解析html文件中哪些包含vue的语法部分并且替换掉他们,上述的@vue/compiler-dom就是用来干这个的,但是CDN的js文件包含编译器, 而经过Vite这种架构工具打包后完全可以去掉编译器,只留运行时vue文件就可以了
选项式API
vue提供一种基于组合式Api之上的选项式api,使用要比组合式写法简单很多,也更适合习惯面向对象编程的使用者,请看下面的例子
//这里将html的部分减去一些,避免一些无关紧要的内容
//样式部分如下
<style>
.counter {
display: flex;
align-items: center;
justify-content: center;
column-gap: 15px;
width: 120px;
margin: 100px;
padding: 10px;
border-radius: 8px;
background-color: #f5f5f5;
user-select: none;
cursor: pointer;
}
</style>
<body>
<div id="app">
<div class="counter">
<span @click="decrement">-</span>
<p>{{count}}</p>
<span @click="increment">+</span>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="./index.js" type="module"></script>
</body>
下面是index.js文件
const App = {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
mounted() {
console.log("挂载");
}
};
const app = Vue.createApp(App);
app.mount("#app");
//createApp传入的根组件,要是这个组件没有自己的template部分,就会把跟容器(在这里是#app)的`innerHTML`作为它的模板,也可以写成下面这样
const App = {
data() {
return {
count: 0
}
},
template: `<div class="counter">
<span @click="decrement">-</span>
<p>{{count}}</p>
<span @click="increment">+</span>
</div>`,
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
mounted() {
console.log("挂载");
}
};
const app = Vue.createApp(App);
const rootComponents = app.mount("#app");
const app = Vue.createApp(App);
app.mount("#app");
render和template
render
像上述代码中,我们是在dom中内嵌了vue语法,那么就一定要经过编译的阶段了, 我们可以定义render函数,直接写render函数可以不经过编译直接运行,在描述这点前,我们先介绍一下render, 按vue官方文档来说,render是字符串模板的一种替代,可以让你利用JavaScript的丰富表达力来完全编程式地声明组件最终的渲染输出, 预编译的模板如果单文件组件中的模板,会在构建时被编译为render选项,如果一个组件中同时存在render和template,则render将具有更高的优先级,
我们可以看,如果是让vue实时的编译内嵌vue语法的dom,会被编译成什么样子,
可以看到,当你在dom嵌入vue语法的时候,没有生成render函数,这个时候就需要你引入的cdn的vue版本中包含编译器,如果是编译的SFC模板结果是什么样的?
可以看到生成了render函数,这个render函数是可以直接被运行时的vue包执行的,不需要加入编译器,所以可以看到有没有render是两回事,有render就直接使用render,没有render,那么就会读取template字符串, 这个时候就需要编译器了 , 我们在这里尝试把上面在选项式API部分里写的例子用render函数写一遍, vue给我们提供了一个名为h函数的api,专门用于给开发者手动的编写render函数,具体可以看 [h函数](渲染选项 | Vue.js), 改写后的例子如下
const h = Vue.h;
const App = {
data() {
return {
count: 0
}
},
render(){
return h("div", {
class: "counter"
}, [
h("span", {
onClick: () => this.decrement()
}, "-"),
h("p", null, this.count),
h("span", {
onClick: () => this.increment()
}, "+")
]);
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
mounted() {
console.log("挂载");
}
};
const app = Vue.createApp(App);
app.mount("#app");
//index.js
我们可以看它在SFC中编译后的结果是什么样的,
可以看到,SFC编译插件基于没有做什么更改,我们可以直接把这个App组件配合vue运行时的版本使用,看下面的html代码
<style>
.counter {
display: flex;
align-items: center;
justify-content: center;
column-gap: 15px;
width: 120px;
margin: 100px;
padding: 10px;
border-radius: 8px;
background-color: #f5f5f5;
user-select: none;
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.5.22/vue.runtime.global.js"></script>
<script src="./index.js" type="module"></script>
</body>
template
在上面我们我们讨论template的时候说,如果根组件没有template和render, 那么根组件会把app.mount("#app")中#app代表的元素的innerHTML作为根组件的template,那么非根组件呢,非根组件就没有template了,那么非根组件怎么从html模板上获得template字符串呢,template接受两种形式,一种就是内嵌vue语法的描述dom的字符串,一种允许你将字符串以#开头,它会被vue作为querySelector选择器使用,将选中的元素的innerHTML作为模板字符字符串,这就让我们可以使用原生的template元素的书写源模板如下
<style>
.counter {
display: flex;
align-items: center;
justify-content: center;
column-gap: 15px;
width: 120px;
margin: 100px;
padding: 10px;
border-radius: 8px;
background-color: #f5f5f5;
user-select: none;
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
</div>
<template id="temp">
<p id="temp">当前的count为: {{count}}</p>
</template>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script src="./index.js" type="module"></script>
const Compo = {
props:{
count: 0
},
template: "#temp"
};
const App = {
data() {
return {
count: 0
}
},
template: `<div class="counter">
<span @click="decrement">-</span>
<p>{{count}}</p>
<span @click="increment">+</span>
</div>
<Compo :count></Compo>`,
components: {
Compo
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
mounted() {
console.log("挂载");
}
};
const app = Vue.createApp(App);
app.mount("#app");
这就让你在原生的HTML上也具有了组件化的能力,我们继续重构上面的代码,看看如果是在html上使用组件化是怎样的, 查看以下的例子
<style>
.counter {
display: flex;
align-items: center;
justify-content: center;
column-gap: 15px;
width: 120px;
margin: 100px;
padding: 10px;
border-radius: 8px;
background-color: #f5f5f5;
user-select: none;
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
</div>
<!-- App组件 -->
<template id="appTemplate">
<div class="counter">
<span @click="decrement">-</span>
<p>{{count}}</p>
<span @click="increment">+</span>
</div>
<Compo :count></Compo>
</template>
<!-- Compo 组件 -->
<template id="temp">
<p id="temp">当前的count为: {{count}}</p>
</template>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script src="./index.js" type="module"></script>
const Compo = {
props:{
count: 0
},
template: "#temp"
};
const App = {
data() {
return {
count: 0
}
},
template: "#appTemplate",
components: {
Compo
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
mounted() {
console.log("挂载");
}
};
const app = Vue.createApp(App);
app.mount("#app");