单页面应用程序(英文名:Single Page Application)简称 SPA,顾
名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的
功能与交互都在这唯一的一个页面内完成。
单页面应用程序将所有的功能局限于一个 web 页面中,仅在该 web 页面初始化时加载相应的资源( HTML、JavaScript 和 CSS)。
一旦页面加载完成了,SPA 不会因为用户的操作而进行页面的重新加载或跳转。而是利用 JavaScript 动态地变换HTML 的内容,从而实现页面与用户的交互。
SPA 单页面应用程序最显著的 3 个优点如下:
① 良好的交互体验
单页应用的内容的改变不需要重新加载整个页面
获取数据也是通过 Ajax 异步获取
没有页面之间的跳转,不会出现“白屏现象”
② 良好的前后端工作分离模式
后端专注于提供 API 接口,更易实现 API 接口的复用
前端专注于页面的渲染,更利于前端工程化的发展
③ 减轻服务器的压力
任何一种技术都有自己的局限性,对于 SPA 单页面应用程序来说,主要的缺点有如下两个:
① 首屏加载慢,但可以通过以下方式解决:
② 不利于 SEO,但可以通过以下方式解决:
vue 官方提供了两种快速创建工程化的 SPA 项目的方式:
① 基于 vite 创建 SPA 项目
② 基于 vue-cli 创建 SPA 项目
vite官网:https://cn.vitejs.dev/
按照顺序执行如下的命令,即可基于 vite 创建 vue 3.x 的工程化项目:
npm init vite-app '项目名称'
cd code1
npm install
npm run dev
使用vite创建的项目目录:
其中:
node_modules 目录用来存放第三方依赖包
public 是公共的静态资源目录
src 是项目的源代码目录(程序员写的所有代码都要放在此目录下)
.gitignore 是 Git 的忽略文件
index.html 是 SPA 单页面应用程序中唯一的 HTML 页面
package.json 是项目的包管理配置文件
在 src 这个项目源代码目录之下,包含了如下的文件和文件夹:
其中:
assets 目录用来存放项目中所有的静态资源文件(css、fonts等)
components 目录用来存放项目中所有的自定义组件
App.vue 是项目的根组件
index.css 是项目的全局样式表文件
main.js 是整个项目的打包入口文件
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
其中:
① App.vue 用来编写待渲染的模板结构
② index.html 中需要预留一个 el 区域
③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中
清空 App.vue 的默认内容,并书写如下的模板结构:
这是app.vue根组件
3.2 在 index.html 中预留 el 区域
打开 index.html 页面,确认预留了 el 区域:
<body>
<div id="app">div>
<script type="module" src="/src/main.js">script>
body>
按照 vue 3.x 的标准用法,把 App.vue 中的模板内容渲染到 index.html 页面的 el 区域中:
//main.js
// 1. 从vue中按需导入createApp函数
// createApp函数的作用 :创建vue的spa
import { createApp } from 'vue'
// 2. 导入待渲染的App组件
import App from './App.vue'
import './index.css'
// 3. 调用createAPP()函数,返回值是SPA实例
// 同时将导入的APP 组件作为参数传给 createApp函数(将APP渲染到index.html上)
const spa_app=createApp(App);
// 4. 调用spa_app实例的mount方法,用来指定vue实际要控制的区域
spa_app.mount('#app');
//main.js
// 1. 从vue中按需导入createApp函数
// createApp函数的作用 :创建vue的spa
import { createApp } from 'vue'
// 2. 导入待渲染的App组件
import App from './App.vue'
import './index.css'
// 3. 调用createAPP()函数,返回值是SPA实例
// 同时将导入的APP 组件作为参数传给 createApp函数(将APP渲染到index.html上)
const spa_app=createApp(App);
// 4. 调用spa_app实例的mount方法,用来指定vue实际要控制的区域
spa_app.mount('#app');
组件化开发指的是:根据封装的思想,把页面上可重用的部分封装为组件,从而方便项目的开发和维护。
例如:http://www.ibootstrap.cn/ 所展示的效果,就契合了组件化开发的思想。用户可以通过拖拽组件的方式,快速生成一个页面的布局结构。
前端组件化开发的好处主要体现在以下两方面:
vue 是一个完全支持组件化开发的框架。**vue 中规定组件的后缀名是 .vue。**之前接触到的 App.vue 文件本质上就是一个 vue 的组件。
每个 .vue 组件都由 3 部分构成,分别是:
template -> 组件的模板结构
script -> 组件的 JavaScript 行为
style -> 组件的样式
其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。
vue规定:每个组件对应的模板结构,需要定义到 节点中。
注意: 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素。
在template中使用指令
在组件的 节点中,支持使用前面所学的指令语法,来辅助开发者渲染当前组件的 DOM 结构。
代码示例如下:
在template中定义根节点
在 vue 2.x 的版本中, 节点内的 DOM 结构仅支持单个根节点:
但是,在 vue 3.x 的版本中, 中支持定义多个根节点:
vue 规定:组件内的 节点是可选的,开发者可以在
节点中封装组件的 JavaScript 业务逻辑。
这是app.vue根组件
3.1 script 中的 name 节点
可以通过 name 节点为当前组件定义一个名称:
<script>
export default {
// name属性指向的是当前组件的名称(建议:每个单词的首字母大写)
name:'MyAPP'
}
</script>
在使用 vue-devtools 进行项目调试的时候,自定义的组件名称可以清晰的区分每个组件:
3.2 script 中的 data 节点
vue 组件渲染期间需要用到的数据,可以定义在 data 节点中:
这是app.vue根组件 {{username}}
组件中的 data 必须是函数!
vue 规定:组件中的 data 必须是一个函数,不能直接指向一个数据对象。因此在组件中定义 data 数据节点。
3.3 script 中的 methods 节点
组件中的事件处理函数,必须定义到 methods 节点中。
这是app.vue根组件 {{count}}
vue 规定:组件内的 节点是可选的,开发者可以在
这是app.vue根组件 {{count}}
其中 标签上的 lang=“css” 属性是可选的,它表示所使用的样式语言。默认只支持普通的 css 语法,可选值还有 less、scss 等。
如果希望使用 less 语法编写组件的 style 样式,可以按照如下两个步骤进行配置:
① 运行 npm install less -D
命令安装依赖包,从而提供 less 语法的编译支持
② 在 标签上添加 lang=“less” 属性,即可使用 less 语法编写组件的样式
组件之间可以进行相互的引用,例如:
vue 中组件的引用原则:先注册后使用。
vue 中注册组件的方式分为“全局注册”和“局部注册”两种,其中:
被全局注册的组件,可以在全局任何一个组件内使用
被局部注册的组件,只能在当前注册的范围内使用
首先准备好两个组件文件如 Swiper.vue
和Test.vue
,并放在components文件夹下。
接着在main.js中导入这两个组件,并使用app.component()全局注册组件,app.component()的第一个参数是为组件自定义的名字,第二个参数是需要被注册的组件。
//main.js
// 1.按需导入createApp函数
import {createApp} from 'vue'
// 2.导入待渲染的app.vue 组件
import App from './App.vue'
// 全局注册组件 1.导入需要被全局注册的组件
import Swiper from './components/01.globalReg/Swiper.vue'
// 3.创建实例:调用createApp函数,并将App作为参数
const app=createApp(App);
// 全局注册组件 2.调用 app.component()方法来全局注册组件
app.component('my-swiper',Swiper);
// 4.调用mount方法把APP组件的模板结构,渲染到指定的el区域中。
app.mount('#app');
接着可以在其他组件中,以标签的形式,使用刚才注册的全局组件即可
//App.vue
这是app.vue根组件 {{ count }}
这是app.vue根组件 {{ count }}
被全局注册的组件,可以在全局任何一个组件内使用
被局部注册的组件,只能在当前注册的范围内使用
在进行组件的注册时,定义组件注册名称的方式有两种:
① 使用 kebab-case 命名法(俗称短横线命名法,例如 my-swiper 和 my-search)
② 使用 PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper 和 MySearch)
短横线命名法的特点:
帕斯卡命名法的特点:
除了可以直接提供组件的注册名称:
//components/swiper.vue
Swiper轮播图组件
//main.js
import Swiper from './components/01.globalReg/Swiper.vue'
app.component('my-swiper',Swiper);
还可以把组件的 name 属性作为注册后组件的名称,示例代码如下:
//main.js
import Swiper from './components/01.globalReg/Swiper.vue'
app.component(Swiper.name,Swiper);
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。导致组件之间样式冲突的根本原因是:
① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的。
② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素 。
App.vue组件
App.vue中的p
App.vue中的p
在父组件中p标签文字变红了,子组件同样的p标签文字同样变红了。
为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:
App.vue组件
App.vue中的p
App.vue中的p
为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:
style节点的 scoped属性,用来自动为每个组件分配唯一的“自定义属性”,并自动为当前组件的DOM标签和style样式应用这个自定义属性,防止组件的样式冲突问题。
App.vue组件
App.vue中的p
App.vue中的p
list.vue组件
list.vue中的p
list.vue中的p
如果给当前组件的 style 节点添加了scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用**:deep()
深度选择器**。
注意:/deep/
是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep()
替代 /deep/
。
为了提高组件的复用性,在封装 vue 组件时需要遵守如下的原则:
组件的 DOM 结构、Style 样式要尽量复用
组件中要展示的数据,尽量由组件的使用者提供
为了方便使用者为组件提供要展示的数据,vue 组件提供了 props 的概念。
props 是组件的自定义属性,组件的使用者可以通过 props 把数据传递到子组件内部,供子组件内部进行使用。代码示例如下:
props 的作用:父组件通过 props 向子组件传递要展示的数据。
props 的好处:提高了组件的复用性。
在封装 vue 组件时,可以把动态的数据项声明为 props 自定义属性。自定义属性可以在当前组件的模板结构中被直接使用。示例代码如下:
//article.vue
标题:{{title}}
作者: {{author}}
这是App.vue 根组件
如果父组件给子组件传递了子组件并没有声明的 props 属性,则这些属性会被忽略,无法被子组件使用,示例代码如下:
标题:{{title}}
作者: {{author}}
App.vue的代码同上个例子,故省略。最终渲染结果:
可以使用 v-bind 属性绑定的形式,为组件动态绑定 props 的值,示例代码如下:
// App.vue
这是App.vue 根组件
组件中如果使用“camelCase (驼峰命名法)”声明了 props 属性的名称,则有两种方式为其绑定属性的值:
//article.vue
发布时间: {{pubTime}}
//====================================
// App.vue
这是App.vue 根组件
在封装组件时对外界传递过来的 props 数据进行合法性的校验,从而防止数据不合法的问题。
使用数组类型的 props 节点的缺点:无法为每个 prop 指定具体的数据类型。
使用对象类型的 props 节点,可以对每个 prop 进行数据类型的校验,示意图如下:
数量:{{count}}
状态:{{state}}
对象类型的 props 节点提供了多种数据验证方案,例如:
① 基础的类型检查
可以直接为组件的 prop 属性指定基础的校验类型,从而防止组件的使用者为其绑定错误类型的数据:
② 多个可能的类型
如果某个 prop 属性值的类型不唯一,此时可以通过数组的形式,为其指定多个可能的类型,示例代码如下:
③ 必填项校验
如果组件的某个 prop 属性是必填项,必须让组件的使用者为其传递属性的值。此时,可以通过如下的方式将其设置为必填项:
④ 属性默认值
在封装组件时,可以为某个 prop 属性指定默认值。示例代码如下:
⑤ 自定义验证函数
在封装组件时,可以为 prop 属性指定自定义的验证validator
函数,从而对 prop 属性的值进行更加精确的控制:
在实际开发中经常会遇到动态操作元素样式的需求。因此,vue 允许开发者通过 v-bind 属性绑定指令,为元素动态绑定 class 属性的值和行内的 style 样式。
可以通过三元表达式,动态的为元素绑定 class 的类名。示例代码如下:
MyStyle组件
如果元素需要动态绑定多个 class 的类名,此时可以使用数组的语法格式:
MyStyle组件
使用数组语法动态绑定 class 会导致模板结构臃肿的问题。此时可以使用对象语法进行简化:
MyStyle组件
:style
的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
今天好热
封装要求:
① 允许用户自定义 title 标题
② 允许用户自定义 bgcolor 背景色
③ 允许用户自定义 color 文本颜色
④ MyHeader 组件需要在页面顶部进行 fixed 固定定位,且 z-index 等于 999
//App.vue
App根组件
//MyHeader.vue
{{title ||'Header-container'}}
在封装组件时,为了让组件的使用者可以监听到组件内状态的变化,此时需要用到组件的自定义事件
① 声明自定义事件 (但这步可以省略)
开发者为自定义组件封装的自定义事件,必须事先在 emits
节点中声明,示例代码如下:
//counter.vue
Counter 组件
② 触发自定义事件
在 emits 节点下声明的自定义事件,可以通过 this.$emit('自定义事件的名称')
方法进行触发,示例代码如下:
//counter.vue
Counter 组件
③ 监听自定义事件
在使用自定义的组件时,可以通过 v-on 的形式监听自定义事件。
//App.vue
在调用 this.$emit()
方法触发自定义事件时,可以通过第 2 个参数为自定义事件传参,示例代码如下:
//counter.vue
Counter 组件
//App.vue
v-model 是双向数据绑定指令,当需要维护组件内外数据的同步时,可以在组件上使用 v-model 指令。示意图如下:
① 父组件通过 v-bind: 属性绑定的形式,把数据传递给子组件
② 子组件中,通过 props 接收父组件传递过来的数据
//父组件
APP根组件----{{count}}
//子组件
count值是:{{number}}
① 在 v-bind: 指令之前添加 v-model 指令
② 在子组件中声明 emits 自定义事件,格式为 update:xxx
③ 调用 $emit() 触发自定义事件,更新父组件中的数据
// 父组件
APP根组件----{{count}}
// 子组件
count值是:{{number}}
计算属性本质上就是一个 function 函数,它可以实时监听 data 中数据的变化,并 return 一个计算后的新值,供组件渲染 DOM 时使用。
计算属性需要以 function 函数的形式声明到组件的 computed 选项中,示例代码如下:
{{count}}乘以2的值为:{{plus}}
注意:计算属性侧重于得到一个计算的结果,因此计算属性中必须有 return 返回值!
① 计算属性必须定义在 computed 节点中
② 计算属性必须是一个 function 函数
③ 计算属性必须有返回值
④ 计算属性必须当做普通属性使用
相对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算。因此计算属性的性能更好 :
{{ count }}乘以2的值为:{{ plus }} {{time()}}
{{ count }}乘以2的值为:{{ plus }} {{time()}}
{{ count }}乘以2的值为:{{ plus }} {{time()}}
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。例如监视用户名的变化并发起请求,判断用户名是否可用 。
2.watch 侦听器的基本语法
开发者需要在 watch 节点下,定义自己的侦听器。实例代码如下:
watch 侦听器的用法
监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用。
首先需要下载axios
:
npm install axios
watch 侦听器的用法
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。比如,,设置shopid的默认值为1,会发现此时控制台并没有打印店铺信息。
import axios from 'axios'
export default {
name:'MyWatch',
data(){
return {
shopid:1
}
},
watch:{
async shopid(newValue,oldValue){
//解析出data,并重命名为res
const {data:res}=await axios.get('https://www.escook.cn/shops/'+newValue);
console.log(res);
}
}
}
如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。实例代码如下:
export default {
name:'MyWatch',
data(){
return {
shopid:1
}
},
watch:{
// async shopid(newValue,oldValue){
// //解析出data,并重命名为res
// const {data:res}=await axios.get('https://www.escook.cn/shops/'+newValue);
// console.log(res);
// }
// 1.监听shopid值的变化,此时的shopid变成了对象
shopid:{
// 2.handler属性为固定写法,当username变化时,调用handler
async handler(newValue,oldValue){
const {data:res}=await axios.get('https://www.escook.cn/shops/'+newValue);
console.log(res);
},
// 3.表示组件加载完毕后调用一次当前的watch侦听器
immediate:true
}
}
}
</script>
当 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项,代码示例如下:
开启deep选项后,watch会监听对象中的每一个属性值,任何一个属性值发生变化,都会触发handler处理函数。如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:
计算属性和侦听器侧重的应用场景不同:
计算属性侧重于监听多个值的变化,最终计算并返回一个新值。
侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值。
组件的生命周期指的是:组件从创建 -> 运行(渲染) -> 销毁的整个过程,强调的是一个时间段。
vue 框架为组件内置了不同时刻的生命周期函数,**生命周期函数会伴随着组件的运行而自动调用。**例如:
① 当组件在内存中被创建完毕之后,会自动调用 created 函数
② 当组件被成功的渲染到页面上之后,会自动调用 mounted 函数
③ 当组件被销毁完毕之后,会自动调用 unmounted 函数
当在父组件中使用子组件后,会调用created函数和mounted函数,当使用v-if隐藏子组件后,会调用unmounted函数
// app.vue
根组件
//LifeCycle.vue
LifeCycle 组件
当组件的 data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证 View 视图展示的数据和Model 数据源保持一致。
当组件被重新渲染完毕之后,会自动调用 updated
生命周期函数。
LifeCycle 组件---{{count}}
注意:在实际开发中,created 是最常用的生命周期函数!
可以参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程:
在项目开发中,组件之间的关系分为如下 3 种:
① 父子关系
② 兄弟关系
③ 后代关系
父子组件之间的数据共享又分为:
① 父 -> 子共享数据
父组件通过 v-bind 属性绑定向子组件共享数据。同时,子组件需要使用 props 接收数据。示例代码如下:
// 父组件
App 根组件--{{count}}
// 子组件
子组件 {{num}}
② 子 -> 父共享数据
子组件通过自定义事件的方式向父组件共享数据。示例代码如下:
// 父组件
App 根组件--{{count}}
// 子组件
子组件 {{num}}
③ 父 <-> 子双向数据同步
父组件在使用子组件期间,可以使用 v-model 指令维护组件内外数据的双向同步:
使用v-model
后,父组件便不需要监听子组件自定义的事件。
// 父组件
App 根组件--{{count}}
// 子组件
子组件 {{num}}
兄弟组件之间实现数据共享的方案是 EventBus
。可以借助于第三方的包 mitt
来创建 eventBus 对象,从而实现兄弟组件之间的数据共享。示意图如下:
在项目中运行如下的命令,安装 mitt 依赖包:
npm install mitt
在项目中创建公共的 eventBus 模块
如下:
// 创建一个JS文件 eventBus.js
// 导入mitt包
import mitt from 'mitt';
// 创建EventBus的实例对象
const bus=mitt();
// 将EventBus的实例对象共享出去
export default bus;
在数据接收方,调用 bus.on('事件名称', 事件处理函数)
方法注册一个自定义事件。示例代码如下:
// 数据接收方
在数据发送方,调用 bus.emit('事件名称', 要发送的数据)
方法触发自定义事件。示例代码如下:
数据发送
count的值:{{count}}
后代关系组件之间共享数据,指的是父节点的组件向其子孙组件共享数据。此时组件之间的嵌套关系比较复杂,可以使用 provide
和 inject
实现后代关系组件之间的数据共享。
父节点的组件可以通过 provide
方法,对其子孙组件共享数据:
子孙节点可以使用 inject
数组,接收父级节点向下共享的数据。示例代码如下:
如果父节点修改了数据,但子节点接收的值并没有改变。因此,父节点使用 provide 向下共享数据时,可以结合 computed 函数向下共享响应式的数据。示例代码如下:
如果父级节点共享的是响应式的数据,则子孙节点必须以 .value 的形式进行使用。示例代码如下:
第三层级 {{color.value}}
vuex 是终极的组件之间的数据共享方案。在企业级的 vue 项目开发中,vuex 可以让组件之间的数据共享变得高效、清晰、且易于维护。
1.父子关系
① 父 -> 子 属性绑定
② 子 -> 父 事件绑定
③ 父 <-> 子 组件上的 v-model
2.兄弟关系
④ EventBus
3.后代关系
⑤ provide & inject
4.全局数据共享
⑥ vuex
在实际项目开发中,几乎每个组件中都会用到 axios 发起数据请求。此时会遇到如下两个问题:
① 每个组件中都需要导入 axios(代码臃肿)
② 每次发请求都需要填写完整的请求路径(不利于后期的维护)
在 main.js 入口文件中,通过 app.config.globalProperties
全局挂载 axios,其中$http为自定义属性,示例代码如下:
import { createApp } from 'vue'
import App from './components/06.network/App.vue'
import './index.css'
import axios from 'axios';
const app=createApp(App);
axios.defaults.baseURL='http://www.escook.cn';
// 全局挂载axios
app.config.globalProperties.$http=axios;
app.mount('#app');
组件便可以简便地使用axios
:
Get Info 组件
ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。
如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:
App根组件
使用$refs拿到组件的引用后,可以调用组件的方法。
//父组件
App根组件
//被引用的组件
counter组件---{{count}}
通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。当文本框展示出来之后,如果希望它立即获得焦点,则尝试为其添加 ref 引用,并调用原生 DOM 对象的.focus() 方法,但由于组件的dom元素是异步更新的,当执行.focus()方法时,还没有获取到对应的dom元素。因此,会在控制台看到报错。
App 根组件
组件的 $nextTick(回调函数)
方法,会**把回调函数推迟到下一个 DOM 更新周期之后执行。**通俗的理解是:等组件的DOM 异步地重新渲染完成后,再执行 回调函数,从而能保证 回调函数可以操作到最新的 DOM 元素。
动态组件指的是动态切换组件的显示与隐藏。vue 提供了一个内置的
组件,专门用来实现组件的动态渲染。
①
是组件的占位符
② 通过 is
属性动态指定要渲染的组件名称
③
App 根组件
在home组件声明了一个计数的变量count=0,点击按钮count的时候,count值会增加。但是当切换到了moive组件,再切换回home组件后,会发现count的值恢复到0了。
默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的
组件保持动态组件的状态。组件切换后,并没有被销毁。
插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
在封装组件时,可以通过
元素定义插槽,从而为用户预留内容占位符。示例代码如下:
//MyCom.vue
这是第一个p标签
最后一个p标签
//App.vue
hhhhhhh
如果在封装组件时没有预留任何
插槽,则用户提供的任何自定义内容都会被丢弃。
这是第一个p标签
最后一个p标签
hhhhhhh
封装组件时,可以为预留的
插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。
这是第一个p标签
这是后备内容
最后一个p标签
如果在封装组件时需要预留多个插槽节点,则需要为每个
插槽指定具体的 name 名称。这种带有具体名称的插槽叫做“具名插槽”。示例代码如下:
注意:没有指定 name 名称的插槽,会有隐含的名称叫做 “default”。
在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。示例代码如下:
APP 根组件
文章标题
文章内容
结尾
只有默认插槽使用的时候可以省略template,其余的插槽均不能省略template。
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
。例如 v-slot:header
可以被重写为 #header
。
文章标题
文章内容
结尾
在封装组件的过程中,可以为预留的
插槽绑定 props 数据,这种带有 props 数据的
叫做“作用域插槽”。示例代码如下:
这是MyCom组件
接收插槽中的数据:
{{scope}}
作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。示例代码如下:
{{ info.address }}
向用户提供mytable组件和table中的数据,但是用户想要按照自己的想法渲染表格。在封装 MyTable 组件的过程中,可以通过作用域插槽把表格每一行的数据传递给组件的使用者。
//mytable.vue
Id
Name
State
在使用 MyTable 组件时,自定义单元格的渲染方式,并接收作用域插槽对外提供的数据。示例代码如下:
{{user.id}}
{{user.name}}
vue 官方提供了 v-for、v-model、v-if 等常用的内置指令。除此之外 vue 还允许开发者自定义指vue 中的自定义指令分为两类,分别是:
私有自定义指令
全局自定义指令
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。示例代码如下:
directives:{
// 自定义一个私有指令
// 声明指令的时候不需要加v- ,使用自定义指令的时候需要加v-
focus:{
// 当被绑定的元素插入到dom中时,自动触发mounted函数
mounted(el) {
// 让绑定的元素获得焦点
el.focus()
},
}
}
在使用自定义指令时,需要加上 v- 前缀。示例代码如下:
全局共享的自定义指令需要通过“单页面应用程序的实例对象”进行声明,示例代码如下:
//main.js
import { createApp } from 'vue'
import App from './components/05.directive/App.vue'
import './index.css'
const app=createApp(App);
//===========================
//注册一个全局自定义指令,'v-focus'
app.directive('focus',{
mounted(el){
el.focus();
}
})
//===========================
app.mount('#app');
mounted 函数只在元素第一次插入 DOM 时被调用,当 DOM 更新时 mounted 函数不会被触发。 **updated函数会在每次 DOM 更新完成后被调用。**示例代码如下:
directives: {
focus: {
mounted(el) {
//第一次插入dom时触发这个函数
el.focus();
},
updated(el) {
// 每次dom更新时 都会触发updated函数
el.focus();
},
},
},
注意:在 vue2 的项目中使用自定义指令时,【 mounted -> bind 】【 updated -> update 】
如果 mounted 和updated 函数中的逻辑完全相同,则可以简写成如下格式:
//全局
app.directive('focus',(el)=>{
// 在mounted和updated时都会被触发
el.focus();
})
//===============
// 私有指令
directives:{
focus(el){
el.focus();
}
}
在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值,示例代码如下:
App 根组件
app.directive('color',(el,binding)=>{
// binding.value 便可以获取通过等号为指令绑定的值
el.style.color=binding.value;
})