赞
踩
单页面应用程序(single page application)简称SPA,顾名思义指的是一个web网站只有唯一的一个HTML页面,所有功能与交互都在这唯一的一个页面内完成。
单页面应用程序将所有功能局限于一个web页面中,仅在该web页面初始化时加载相应的资源(HTML、JavaScript和CSS)。一旦页面加载完成了,SPA不会因为用户的操作而进行页面的重新加载或跳转,而是利用JavaScript动态地变换HTML的内容,从而实现页面与用户的交互。
SPA单页面应用程序最显著的3个优点:
良好的交互体验
良好的前后端工作分离模式
减轻服务器压力
SPA的缺点:
首屏加载慢,解决:路由懒加载、代码压缩、CDN加速、网络传输压缩
不利于SEO,解决:SSR服务器端渲染
vue官方提供了两种快速创建工程化的SPA项目的方式:
vite | vue-cli | |
支持的vue版本 | 仅支持3.x | 支持3.x和2.x |
是否基于webpack | 否 | 是 |
运行速度 | 快 | 较慢 |
功能完整度 | 小而巧 | 大而全 |
是否建议在企业级开发中使用 | 目前不建议 | 建议在企业级开发中使用 |
通过npm init vite-app 项目名称,这里如果报错Need to install the following packages:create-vite-app,则需要先执行npm i create-vite-app去安装
项目结构初始化完成后,cd 项目名称进入项目目录之下,运行npm i 下载依赖包,最后npm run dev就把项目运行起来了。
使用vite创建的项目结构如下:
在src这个项目源代码目录之下,包含了如下的文件和文件夹:
在工程化的项目中,vue要做的事情:通过main.js把App.vue渲染到index.html的指定区域中。
其中:
根据封装的思想,把页面上可重用的部分封装为组件,从而方便项目的开发和维护。
vue是一个完全支持组件化开发的框架。vue中规定组件的后缀名是.vue。
每个vue组件都是由三部分构成:
其中,每个组件中必须包含template模板结构
vue规定:每个组件对应的模板结构,需要定义到<template>节点中
注意:template是vue提供的容易标签,只起到包裹性质的作用,它不会被渲染为真正的DOM元素。
在组件的<template>节点中,支持使用前面所学的指令语法,来辅助开发者渲染当前的DOM结构。
在vue2.x的版本中,<template>节点内的DOM结构仅支持单个根节点,而在vue3.x的版本中,<template>中支持定义多个根节点
vue规定:组件内的<script>节点是可选的,开发者可以在<script>节点中封装组件的JavaScript业务逻辑。<script>节点的基本结构如下:
- <script>
- export default {
- // name属性指向得是当前组件的名称
- name:'MyApp'
- }
- </script>
其中name节点为当前组件定义一个名称,在使用vue-devtools进行项目调试的时候,自定义组件的名称可以清晰的区分每个组件
vue组件渲染期间需要用到的数据,可以定义在data节点中:
- <script>
- export default {
- // name属性指向得是当前组件的名称
- name:'MyApp',
- // 组件的数据(data方法中return出去的对象,就是当前组件渲染期间需要用到的数据对象)
- data(){
- return {
- username:'admin'
- }
- }
- }
- </script>
组件中的事件处理函数,必须定义到methods节点中,示例代码如下
- <template>
- <p>这是App.vue根组件---</p>
- <p>123----{{username}}</p>
- <hr>
- <p>count为{{count}}</p>
- <button @click="addCount">+1</button>
- </template>
- <script>
- export default {
- // name属性指向得是当前组件的名称
- name:'MyApp',
- // 组件的数据(data方法中return出去的对象,就是当前组件渲染期间需要用到的数据对象)
- data(){
- return {
- username:'admin',
- count:0
- }
- },
- methods:{
- addCount(){
- this.count+=1
- }
- }
- }
- </script>

vue规定:组件内的<style>节点是可选的,开发者可以在<style>节点中编写样式美化当前组件的ui结构。
- <style lang="css">
- p {
- color: red;
- }
- </style>
其中lang="css"属性是可选的,他表示所使用的样式语言。默认只支持普通的css语法,可选值还有less,scss等
如果希望使用less语法编写组件的样式,可以按照如下两个步骤进行配置
组件之间可以进行相互引用,例如:
vue中组件的引用原则:先注册后使用。
vue中注册组件的方式分为“全局注册”和“局部注册”两种,其中:
在components目录下创建01.golbalReg文件夹,文件夹下包含Swiper.vue和Test.vue
- import { createApp } from 'vue'
- import App from './App.vue'
-
- //1.导入Swiper组件
- import Swiper from './components/01.globalReg/Swiper.vue'
- import Test from './components/01.globalReg/Test.vue'
-
- const app=createApp(App)
- //2.调用app实例的component()方法,在全局注册my-swiper和my-test两个组件
- app.component('my-swiper',Swiper)
- app.component('my-test',Test)
-
- app.mount('#app')
使用app.component()方法注册的全局组件,直接以标签的形式进行使用即可,例如:
- <template>
- <my-swiper></my-swiper>
- <my-test></my-test>
- </template>
在components目录下创建02.privateReg文件夹,文件夹下包含Search.vue
- <template>
- <my-swiper></my-swiper>
- <my-test></my-test>
- <my-search></my-search>
- </template>
- <script>
- //1.导入
- import Search from './components/02.privateReg/Search.vue'
-
- export default {
- name:'MyApp',
- data(){
- return {
- username:'admin',
- count:0
- }
- },
- methods:{
- addCount(){
- this.count+=1
- }
- },
- //2.通过components节点,为当前的组件注册私有子组件
- components:{
- 'my-search':Search
- }
- }

在进行组件的注册时,定义组件注册名称的方式有两种:
使用kebab-case命名法(俗称短横线命名法,例如:my-test)
使用PasscalCase命名法(俗称帕斯卡命名法或大驼峰命名法,例如MySwiper)
短横线命名法的特点:
必须严格按照短横线名称进行使用
帕斯卡命名法的特点:
既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用
注意:在实际开发中,推荐使用帕斯卡命名法为组件注册名称,因为它的适用性更强
在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的name属性作为注册后组件的名称,如:
- // 1.按需导入createApp函数
- import { createApp } from 'vue'
- // 2.导入待渲染的组件App.vue
- import App from './App.vue'
-
- import Test from './components/01.globalReg/Test.vue'
-
- // 3.调用createApp函数,创建SPA应用的实例
- const app=createApp(App)
-
- app.component(Test.name,Test)
-
- // 4.通过mount()把App组件的模板结构渲染到指定的el区域中
- app.mount('#app')
默认情况下,写在.vue组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。导致组件之间样式冲突的根本原因:
单页面应用程序中所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的
每个组件中的样式,都会影响整个index.html页面中的DOM元素
为了提高开发效率和开发体验,vue为style节点提供了scoped属性,从而防止组件之间的样式冲突问题:
- <style scoped>
- /* style节点的scoped属性,用来自动为每个组件分配唯一的“自定义属性”,并自动为当前组件的DOM标签和style样式应用这个自定义属性,防止组件的样式冲突问题 */
- p{
- color: red;
- }
- </style>
如果给当前组件的style节点添加了scoped属性,则当前组件的样式对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用/deep/深度选择器,
- <style scoped>
- /deep/ p{
- color: red;
- }
- </style>
vue3中/deep/被废除,使用:deep()
- <style scoped>
- :deep(p) {
- color: red;
- }
- </style>
为了提高组件的复用性,在封装vue组件时需要遵守如下的原则:
组件的DOM结构、style样式要尽量复用
组件重要展示的数据,尽量由组件的使用者提供
为了方便使用者为组件提供要展示的数据,vue组件提供了props的概念
props是组件的自定义属性,组件的使用者可以通过props把数据传递到子组件内部,供子组件内部进行使用。
在封装vue组件时,可以把动态的数据项声明为props自定义属性。自定义属性可以在当前组件的模板结构中直接被使用。
示例:App.vue代码如下:
- <template>
- <h1>这是App根组件</h1>
- <hr>
- <my-article title="轻轻滴我走了" author="徐志摩"></my-article>
- </template>
-
- <script>
- import MyArticle from './Article.vue'
- export default {
- name:'MyApp',
- components:{
- MyArticle
- }
- }
- </script>
Article.vue代码如下:
- <template>
- <div>
- <h3>标题{{title}}</h3>
- <h5>作者:{{author}}</h5>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyArtical',
- props:['title','author']
- }
- </script>
在App中就可以使用Article中的自定义属性
如果父组件给子组件传递了未声明的props属性,则这些属性会被忽略,无法被子组件使用。
可以使用v-bind属性绑定指令的形式,为组件动态绑定props的值。
- <template>
- <h1>这是App根组件</h1>
- <hr>
- <my-article :title="info.title" :author="info.author"></my-article>
- </template>
-
- <script>
- import MyArticle from './Article.vue'
- export default {
- name:'MyApp',
- data(){
- return {
- info:{
- title:'123',
- author:'456'
- }
- }
- },
- components:{
- MyArticle
- }
- }
- </script>

组件中如果使用“camelCase(驼峰命名法)”声明了props属性的名称,则有两种方式为其绑定属性的值:
- //短横线
- <my-article pub-time="1979"></my-article>
- //驼峰
- <my-article pubTime="1979"></my-article>
在实际开发中经常遇到动态操作元素样式的需求。因此,vue允许开发者通过v-bind属性绑定指令,为元素动态绑定class属性的值和行内的style样式。
可以通过三元表达式,动态的为元素绑定class的类名,示例如下:
- <template>
- <div>
- <h3 class="thin" :class="isItalic ? 'italic':''">这是Style组件</h3>
- <button @click="isItalic=!isItalic">Toggle Italic</button>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyStyle',
- data(){
- return {
- // 字体是否倾斜
- isItalic:true,
- }
- },
- }
- </script>
-
- <style lang="less">
- .thin {
- font-weight: 200;
- }
- .italic {
- font-style: italic;
- color: red;
- }
-
- </style>

如果元素要动态绑定多个class的类名,此时可以使用数组的语法格式:
- <template>
- <div>
- <h2 class="thin" :class="[isItalic ? 'italic':'', isDelete ?'delete':'']">这是Style组件</h2>
- <button @click="isItalic=!isItalic">Toggle Italic</button>
- <button @click="isDelete=!isDelete">Toggle Delete</button>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyStyle',
- data(){
- return {
- // 字体是否倾斜
- isItalic:true,
- isDelete:true
- }
- },
- }
- </script>
-
- <style lang="less">
- .thin {
- font-weight: 200;
- }
- .italic {
- font-style: italic;
- }
- .delete{
- text-decoration: line-through;
- }
-
- </style>

使用数组语法动态绑定class会导致模板结构臃肿的问题。此时可以使用对象语法进行简化:
- <template>
- <div>
- <h3 class="thin" :class="classObj">这是Style组件</h3>
- <button @click="classObj.italic=!classObj.italic">Toggle Italic</button>
- <button @click="classObj.delete=!classObj.delete">Toggle Delete</button>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyStyle',
- data(){
- return {
- classObj:{
- italic:false,
- delete:false
- }
- }
- },
- }
- </script>
-
- <style lang="less">
- .thin {
- font-weight: 200;
- }
- .italic {
- font-style: italic;
- }
- .delete{
- text-decoration: line-through;
- }
-
- </style>

:style的对象语法十分直观——看着像css,但其实是一个JavaScript对象。css property名可以用驼峰式或短横线分隔来命名:
- <template>
- <div :style="{color:active,fontSize:fsize+'px','background-color':bgc}">我要赚大钱</div>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyStyle',
- data(){
- return {
- active:'red',
- fsize:30,
- bgc:'pink'
- }
- },
- }
- </script>

在封装组件时对外界传递过来的props数据进行合法性的校验,从而防止数据不合法的问题。使用props节点,可以对每个prop进行数据类型的校验。
- <template>
- <div>
- <p>数量{{count}}</p>
- <p>状态{{state}}</p>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyCount',
- props:{
- count:Number,
- state:Boolean
- }
- }
- </script>

对象类型的props节点提供了多种数据验证的方案,例如:
可以直接为组件的prop属性指定基础的校验类型,从而防止组件的使用者为其绑定错误类型的数据:
如果某个prop属性值的类型不唯一,此时可以通过数组的形式,为其指定多个可能的类型,示例代码如下:
- export default {
- name:'MyCount',
- props:{
- propA:[String,Number],
- }
- }
如果组件的某个prop属性是必填项,必须让组件的使用者为其传递属性的值。此时,可以通过如下的方式将其设置为必填项
- export default {
- name:'MyCount',
- props:{
- propB:{
- type:String,
- required:true
- }
- }
- }
在封装组件时,可以为某个prop属性指定默认值。
- export default {
- name:'MyCount',
- props:{
- propB:{
- type:String,
- default:'abc'
- }
- }
- }
在封装组件时,可以为prop属性指定自定义的验证函数,从而对prop属性的值进行更加精确的控制:
- export default {
- name:'MyCount',
- //通过配置对象的形式,来定义info属性的验证规则
- props:{
- info:{
- //通过validator函数,对info属性值进行校验,属性的值通过形参value进行接收
- validator(value){
- //info属性的值,必须匹配下列字符串中的一个
- return ['success','warning','danger'].indexOf(value)!==-1
- }
- }
- }
- }
计算属性本质上就是一个function函数,它可以实时监听data中数据的变化,并return一个计算后的新值,供组件渲染DOM时使用。
计算属性需要以function函数的形式声明到组件的computed选项中,示例如下:
- <template>
- <div>
- <input type="text" v-model.number="count">
- <p>{{count}}乘以2的值为:{{plus}}</p>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyCount',
- data(){
- return {
- count:1
- }
- },
- computed:{
- plus(){ //计算属性,监听data中count值的变化,自动计算出count*2之后的值
- return this.count*2
- }
- }
- }
- </script>

注意:计算属性侧重于得到一个计算的结果,因此计算属性中必须有return返回值!
想对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖发生变化时,才会重新进行运算。因此计算属性的性能更好
在封装组件时,为了让组件的使用者可以监听到组件内状态的变化,此时需要用到组件的自定义事件。
Counter.vue代码如下:
- <template>
- <div>
- <p>count的值{{count}}</p>
- <button @click="add">+1</button>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyCounter',
- data(){
- return{
- count:0
- }
- },
- // 1.声明自定义事件
- emits:['countChange'],
- methods:{
- add(){
- this.count+=1
- // 2.this.$emit()触发自定义事件
- this.$emit('countChange')
- }
- }
- }
- </script>

App.vue代码如下:
- <template>
- <div>
- <h1>app根组件</h1>
- <!-- 3.监听自定义事件 -->
- <my-counter @countChange="getCount"></my-counter>
- </div>
- </template>
-
- <script>
- import MyCounter from './Counter.vue'
- export default {
- name:'MyApp',
- components:{
- MyCounter
- },
- methods:{
- // 自定义事件的处理函数
- getCount(){
- console.log('触发了countchange');
- }
- }
- }
- </script>

在调用this.$emit()方法触发自定义事件时,可以通过第二个参数为自定义事件传参,
- <template>
- <div>
- <p>count的值{{count}}</p>
- <button @click="add">+1</button>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyCounter',
- data(){
- return{
- count:0
- }
- },
- // 1.声明自定义事件
- emits:['countChange'],
- methods:{
- add(){
- this.count+=1
- // 2.this.$emit()触发自定义事件
- this.$emit('countChange',this.count)
- }
- }
- }
- </script>

- <template>
- <div>
- <h1>app根组件</h1>
- <!-- 3.监听自定义事件 -->
- <my-counter @countChange="getCount"></my-counter>
- </div>
- </template>
-
- <script>
- import MyCounter from './Counter.vue'
- export default {
- name:'MyApp',
- components:{
- MyCounter
- },
- methods:{
- // 自定义事件的处理函数
- getCount(val){
- console.log('触发了countchange',val);
- }
- }
- }
- </script>

v-model是双向数据绑定指令,当需要维护组件内外数据的同步时,可以在组件上使用v-model指令。示意图如下:
父组件通过v-bind属性绑定的形式,把数据传递给子组件,子组件中,通过props接收父组件传递过来的数据。例如将父组件的count值传给子组件,代码如下
父组件App.vue
- <template>
- <div>
- <h1>App根组件---{{count}}</h1>
- <button @click="count+=1">+1</button>
- <hr>
- <my-counter :count="count"></my-counter>
- </div>
- </template>
-
- <script>
- import MyCounter from './Counter.vue'
- export default {
- name:'MyApp',
- data(){
- return {
- count:0
- }
- },
- components:{
- MyCounter
- }
- }
- </script>

子组件Counter.vue
- <template>
- <div>
- <p>count的值{{count}}</p>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyCounter',
- props:['count']
- }
- </script>
在v-bind指令之前添加v-model指令,在子组件中声明emits自定义事件,格式为update:xxx,调用$emit()触发自定义事件,更新父组件中的数据。
- <template>
- <div>
- <h1>App根组件---{{count}}</h1>
- <button @click="count+=1">+1</button>
- <hr>
- <my-counter v-model:count="count"></my-counter>
- </div>
- </template>
-
- <script>
- import MyCounter from './Counter.vue'
- export default {
- name:'MyApp',
- data(){
- return {
- count:0
- }
- },
- components:{
- MyCounter
- }
- }
- </script>

- <template>
- <div>
- <p>count的值{{count}}</p>
- <button @click="add">+1</button>
- </div>
- </template>
-
- <script>
- export default {
- name:'MyCounter',
- props:['count'],
- emits:['update:count'],
- methods:{
- add(){
- this.$emit("update:count",this.count+1)
- }
- }
- }
- </script>

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。