2 组件
约 1831 个字 437 行代码 2 张图片 预计阅读时间 12 分钟
1 生命周期
- 生命周期函数中
this
指向 Vue 实例对象
生命周期节点(都是钩子函数):
- 挂载
beforeCreate
阶段,初始化生命周期函数与事件,但数据代理还未开始 (不能访问 data & methods)created
数据监测与数据代理执行完毕(可以访问 data & methods 了)beforeMount
开始解析模版,在内存中生成虚拟 DOM(插值语法等指令尚未替换显示) 此时所有针对 DOM 的操作均不生效mounted
真实 DOM 完成挂载
- 更新:Model -> View 更新
beforeUpdate
新数据 + 旧页面updated
新数据 + 新页面
- 销毁 -
vm.$destroy()
beforeDestroy
所有 data、methods、指令均可用(数据能更新,但页面不会) 一般在此阶段关闭定时器、取消订阅消息、解绑自定义事件destroyed
vm被销毁,界面 DOM 保持最终的静止形态,和组件 vm 失去联系
挂载
一个不用 transition,硬用 Vue 实现透明度变化的 Sample
new Vue({
data: {
opacity: 1
},
methods: {
stop() {
this.$destroy(); // 销毁Vue实例,阻止继续变换 opacity
// 此时还没有关闭 timer
}
},
mounted() { // 挂载完毕 - 解析完成并把初始真实DOM放入页面
this.timer = setInterval(() => {
this.opacity -= 0.01;
if(this.opacity <= 0) this.opacity = 1;
}, 16)
},
beforeDestroy() { // 自杀 or 他杀 都会调用该函数
clearInterval(this.timer); // 清除定时器,停止透明度变换
}
})
另外根节点可以啥都不写,内容用 template
进行填充
new Vue({
el: '#root',
// template 中只能有一个标签(用 div 进行整体性包裹)-> root 将被替换
template: `
<div>
<h2> n = {{n}} </h2>
<button @click="n++"> n+1 </button>
</div>
`
})
2 组件
实现应用中 局部 功能的 代码 & 资源 的集合
- 组件的本质是由
Vue.extend()
生成的构造函数VueComponent
- 解析组件标签时,Vue 会自动创建该组件的实例对象
- 在组件配置项中,
this
指向VueComponent
内置关系
-
func.protoype
(显式原型)与entity.__proto__
(隐式原型)均指向同一个“原型对象”-
VueComponent.prototype.__proto__ === Vue.prototype
-> 组件原型对象的隐式原型是Vue实例的原型,即 Vue 实例为组件兜底
-
vc
也可以用vm
上的 数据 & 方法
-
-
我们一般通过“显示原型”往里面塞东西,“隐式原型“会自己从里面捞东西
2.1 非单文件组件
一个文件中包含多个组件 不能单独为组件定义样式
- 创建组件
- 注册组件
- 使用组件
2.2 组件嵌套
用 school 组件包裹 student 组件(在父组件中注册子组件)
const school = Vue.extend({
template: `
<div>
学生姓名:{{name}}
<student></student> // 在school的模版中使用
</div>`,
data() {
return { name: 'SeaBee' }
},
components: {
student: student
}
})
2.3 单文件组件
.vue
文件 - 只包含一个组件,一般采用大驼峰命名法MyVue.vue
可以通过
webpack / vue-cli
转变为js
文件
- 定义单文件组件
<template> // 组件结构 </template> <script> // 组件交互代码 - 数据 & 方法 ... // 组件需要暴露才能使用,一般使用“默认暴露” export const school = Vue.extend({}) // 分别暴露 // at end export {school}; // 统一暴露 export default school; // 默认暴露 // 优化为以下形式 export default { // 会默认调用 Vue.extend name: '与文件名一致', other_options }; </script> <style> // 组件样式 </style>
- 必须存在的组件
app.vue
// 用于汇总所有的组件 - 引入所有一级子组件 <template> <div> <School></School> <Student></Student> </div> </template> <script> // 引入组件 import School from './School'; // name - 相对路径 import Student from './Student'; export default { name: 'App', components: { // 注册组件 School, // 简写,等同于 School:School Student } } </script>
- 汇总
main.js
2.4 slot 插槽
默认插槽
-
定义插槽
-
使用插槽
使用者的
<comp>
中有内容:
- 有插槽:插槽替换为传入内容
- 无插槽:传入内容无意义
具名插槽
用于区分多个插槽
@ component.vue
<template>
<slot name="center">如果使用者未传入数据,就显示这句话</slot>
<slot name="footer">如果使用者未传入数据,就显示这句话</slot>
</template>
@ user.vue
<template>
<comp>
// 使用 slot 属性指定嵌入的插槽
<img slot="center" src="url">
// 多个标签挤一个 slot:可以一个个加 name
<a slot="footer" href="#">我放一个</a>
<a slot="footer" href="#">甚至两个!</a>
// 但一般用一个 div/template 包一下 - 方便布局
<div slot="footer">
<a href="#">我放一个</a>
<a href="#">甚至两个!</a>
</div>
</comp>
</template>
作用域插槽
不同的 tab 使用同一套数据进行渲染 —— 我们把数据绑给插槽实例
@ component.vue
<template>
<slot :g="games" msg="hello">如果使用者未传入数据,就显示这句话</slot>
// 实际上 games 被塞给了使用者定义的 scope 中
</template>
<template>
<comp> // 想要拿到数据必须使用 template 进行包裹!
<template scope="s_name"> // 命名作用域(数据在作用域上)
<h2>{{s_name.msg}}</h2>
<ul>
<li v:for="(g,idx) in s_name.games" :key="idx">{{g.name}}</li>
</ul>
</template>
</comp>
<comp> // 想要拿到数据必须使用 template 进行包裹!
<template scope="s_name"> // 命名作用域(数据在作用域上)
<ol>
<li v:for="(g,idx) in s_name.games" :key="idx">{{g.name}}</li>
</ol>
</template>
</comp>
</template>
3 路由
此处通过
vue-router
插件进行实现
- 路由是一组 key-value 的对应关系,需要“路由器”进行管理:
- key = 路径
- value = function / component
- 目的:实现单页面(SPA)应用 —— 维护路径,但不刷新整个页面
- 分类
- 前端路由 val -> component - 路径改变就显示对应组件
- 后端路由 val -> function - 服务器收到请求时根据路径匹配处理函数
3.1 基本使用
标签式导航
- 安装
vue-router
-npm install vue-router@3
(和vuex
一样的版本对应关系) - 导入并应用
- 在
src/router/index.js
创建 Router - 「标签式导航」将路由与指定组件的点击事件绑定(不能用
<a>
哩) - 指定变换组件的呈现位置
<router-view>
编程式导航
- 事件绑定在
@click
上
3.2 注意点
- 组件分类
我们一般把组件分成 路由组件 & 一般组件 两类,前者通过路由规则进行匹配
一般在
src/pages
下放置路由组件,在src/components
下放置一般组件 - 没有显示的路由组件被「销毁」了 路由组件会被频繁的 挂载/销毁
- 每个组件都有自己的
$route
属性,储存自己的路由信息(每个组件不一样) - 整个应用只有一个 Router,可以通过
$router
属性获取
3.3 嵌套路由
在最外层的展示区进行一个疯狂套娃
这里演示在 Home 里面套娃 News & Message
- 配置嵌套路由
- 指定二级路由在 Home 组件中的呈现位置
- 修改超链接
3.4 路由传参
Query 参数
-
传递参数
// 字符串写法 1. 使用 :to 实现 v-bind 2. 双引号套模版字符串 3. 需要动态的地方用 ${name} <router-link active-class="acitve" :to="`/home/news/detail?id=${m.id}`">{{m.title}}</router-link> // 对象写法 <router-link :to="{ path: '/home/news/detail', query: { id: m.id, title: m.title } }"> {{m.title}} </router-link> // 如果对多级路由进行命名的话,可以通过 name 进行跳转(必须使用对象方式) <router-link :to="{name: 'xiangqing'}">{{m.title}}</router-link>
-
接收参数 - 通过
this.$route.query.name
使用插值语法
Params 参数
-
在 router 中配置占位符
-
通过 URL 传递参数
-
通过
this.$route.params.name
获取对应参数
NavigationDuplicated
使用「编程式」路由进行跳转但「参数不变」时,会抛出
NavigationDuplicated
错误「声明式」导航不会出现该问题 =>
vue-router
底层完成了相关处理
=> 因为返回的 Promise 需要返回 res / err 回调,传入相关回调即可解决
this.$router.push({
name: 'search',
params: {keyWord: this.keyWord}
}, ()=>{}, ()=>{})
// 可以在 err 中捕获到异常,但是不显示「治标不治本」
// => 在别的组件中调用时还是报错
在组件中:
this
指向组件本身,一个 VueComponent 实例this.$router
是一个 VueRouter 实例,由入口文件注册路由时添加push
是 VueRouter 原型对象上的方法- 在实例中不存在,借用原型方法
- 上下文为 VueRouter 的一个实例
下面重写 push
方法:
// @ router.js
// 备份原有的 push 方法
let originPush = VueRouter.prototypr.push
// 重写 push & replace
VueRouter.prototype.push = function(location, resolve, reject) {
if(resolve && reject) { // 提供了两个回调函数
// 调用原始 push 函数,篡改上下文
originPush.call(this, location, resolve, reject)
} else { // 手工补全缺失的回调
originPush.call(this, location, ()=>{}, ()=>{})
}
}
// 对 replace 的处理类似
3.5 props 配置
简化插值语法内容
-
在 Router 中配置 props 属性
// 哪一层路由接收数据,就在哪一层配置 props export default new VueRouter({ routes: [ { path: '', component: Comp, // 1. 值为 Ob,所有键值对将以 props 形式传递给对应组件 props: { // 传递的是固定数据 a: 1, b: 'hello' }, // 2. 布尔值配置,true - 将所有 params 以 props 形式传给组件 props: true, // 3. 函数配置 - 兼容 query & params props($route) { return { id: $route.query.id, title: $route.params.title, } } } ] })
-
在对应组件中接收
小结
-
路由穿参的对象写法能否使用
path + params
形式?不能,
params
必须结合name
使用(即便已经占位) -
如何指定
params
可传可不传(配置中已经进行占位)?要求传递但跑路 => URL 出现问题
可在对应占位符后添加
?
表示可选,如/search/:keyWord?
-
如何解决
params
参数为 空串 的情况?默认情况下会导致 URL 出现问题
使用
undefined
解决:keyWord: this.keyWord || undefined
由于空串将被识别为
false
,此处将采用undefined
作为参数值=> 实际上看起来要配合占位符中的
?
进行使用 -
路由组件能否传递
props
参数?可以,而且有三种写法:
-
布尔值写法
在
router
中配置props: true
-
未在接收方
props
中声明的params
参数将出现在$attrs
-
反之,出现在接收方的
props
中,可以直接通过变量名进行访问
-
-
对象写法
-
函数写法
可将
query & params
均以props
形式传递给路由组件
-
3.6 浏览历史记录
- 每次点击
vue-router
都会触发 push 操作,将目标 URL 压到浏览器的历史记录栈中 - 采用
replace
操作将使得目标URL替换掉当前栈顶的记录<vue-router replace></vue-router>
3.7 缓存路由组件
让切走的组件不被销毁 —— 在 container 中操作
<keep-alive include="News"> -> 仅保持 News 不被销毁
「组件名」- 组件中 name 属性的值
不指定 include,则所有可能的子组件都被缓存
<router-view></router-view>
</keep-alive>
缓存多个时,可以使用以下写法:
:include=['News', 'Message']
3.8 俩新的钩子
// 因为 keep-alive 后组件不会销毁 => timer 一直在,so
activated() {
this.timer = setInterval(() => {
do sth;
}, 1000)
},
deactivated() {
clearInterval(this.timer)
}
3.9 路由守卫
满足一定条件才能进行路由跳转
全局前置
在 router 暴露之前添加路由守卫
const router = new VueRouter({}) // 不要直接默认暴露
// 添加全局前置路由守卫 -> 每次发生路由跳转前都会调用
router.beforeEach((to, from, next) => { // 不手动处理 next,则所有人都被阻断
if(to.path === '' || to.name === '') { // 跳转目标受限
if(condition) next() // 符合条件,允许跳转
else alert('无权限查看!')
} else {
next()
}
})
export default router
全局后置
前置写法得把每一个 path / name 都给列出来 —— 我人先裂开
我们尝试在路由配置中设置一个分量用于标识当前路径是否需要权限校验
=> 最后这个信息被塞在了
meta
下
const router = new VueRouter({
routes: [
{
path: '/home',
name: 'jia',
component: Home,
meta: {
isAuth: true // undifined -> false 不用写就行
}
}
]
})
// 前置守卫(优化版本)
router.beforeEach((to, from, next) => {
if(to.meta.isAuth) {
if(ok) next()
} else {
next()
}
})
// 后置守卫 - 切换之后执行,已经跳完了所以没有 next
router.afterEach((to, from) => {
// 可以联动式修改页面标题 —— 也塞在 meta 里
to.title = to.meta.title || 'total name'
})
独享路由守卫
组件内路由守卫
在
component.vue
中进行配置
export default {
// 直接在 template 中引入组件标签时不算
beforeRouteEnter(to, from, next) { // 通过路由规则进入(了)组件时
next() // 不写就没法进入组件orz
},
beforeRouteLeav(to, from, next) { // 通过路由规则离开组件前
next() // 不写就没有办法跑路
}
}
工作模式
- 哈希模式(默认)
#
后的内容作为 hash 值,而非请求路径存在- 兼容性好,刷新能行(hash值不会被作为文件路径)
- URL 通过第三方手机 app 分享可能会被标记为不合法
- history 模式
- 需要在 router 中手动配置
mode: 'history'
- 路径后没有
#
,所有都当路径 - 兼容性差 —— 因为不走网络请求,一刷新就报废(被当成文件路径了)
- 需要在 router 中手动配置