一、 自定义指令
指令:为了操作底层dom,作者给留的方案
实际应用:可以通过指令什么时候在dom创建完成,从而进行依赖dom的初始化工作。
指令的生命周期:
| bind | 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置 |
|---|---|
| inserted | 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中) |
| update | 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下) |
| componentUpdated | 指令所在组件的 VNode 及其子 VNode 全部更新后调用 |
| unbind | 只调用一次,指令与元素解绑时调用 |
参数:
el:指令所绑定的元素,可以用来直接操作 DOM。
binding:一个对象,包含以下 property:
name:指令名,不包括 v- 前缀。value:指令的绑定值.。例如:v-my-directive=“1 + 1” 中,绑定值为 2。oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true,
bar: true }。
vnode:Vue 编译生成的虚拟节点。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
注册全局指令
<script>
// 自定义hello指令
Vue.directive("hello", {
// el:dom节点
// binding:对象
inserted(el, binding) {
// 被绑定元素第一次插入到父节点时调用
el.style.background = binding.value;
},
// 更新状态时改变
update(el, binding) {
el.style.background = binding.value;
}
})
// 自定义指令简写方式, 更改和插入时都调用箭头函数
Vue.directive("hello", (el, binding) => {
el.style.background = binding.value;
})
</script>
组件中也接受一个 directives 的选项注册局部指令:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.style.background=binding.value;
}
}
}
如果指令需要多个值,可以传入一个 JavaScript 对象。注意:指令函数能够接受所有合法的 JavaScript 表达式。
v-demo="{ color: 'white', text: 'hello!' }"
nextTick函数
函数使用的状态改变上树后就会立即执行nextTic函数,且执行一次(一次性的监听工作),在应对某些特定需求时有奇效。
this.$nextTick(() => {
console.log("我比update执行的都晚,且执行一次");
})
Vue3自定义组件指令
<script>
var obj = {
data() {},
}
// 实例化
var app = Vue.createApp(obj);
// 注册组件
app.directive("组件名", {})
// 挂载
app.mount("#container");
</script>
二、生命周期(组件)
beforeCreate
类型:Function
在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。
created
类型:Function
在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。 然而,挂载阶段还没开始,且 $el property 目前尚不可用。
beforeMount
类型:Function
在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。
mounted
类型:Function
实例被挂载后调用,这时 el 被新创建的 vm.e l 替 换 了 。 如 果 根 实 例 挂 载 到 了 一 个 文 档 内 的 元 素 上 , 当 m o u n t e d 被 调 用 时 v m . el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.el替换了。如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.el 也在文档内。
注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick:
mounted: function () {
this.$nextTick(function () {
// 仅在整个视图都被渲染之后才会运行的代码
})
}
该钩子在服务器端渲染期间不被调用。
beforeUpdate
类型:Function
在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行。
updated
类型:Function
在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
注意,updated 不会保证所有的子组件也都被重新渲染完毕。该钩子在服务器端渲染期间不被调用。
如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick:
updated: function () {
this.$nextTick(function () {
// 仅在整个视图都被重新渲染之后才会运行的代码
})
}
activated
类型:Function
被 keep-alive 缓存的组件激活时调用。
该钩子在服务器端渲染期间不被调用。
deactivated
类型:Function
被 keep-alive 缓存的组件失活时调用。
该钩子在服务器端渲染期间不被调用。
beforeDestroy
类型:Function
实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。
destroyed
类型:Function
实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。该钩子在服务器端渲染期间不被调用。
errorCaptured 2.5.0+ 新增
类型:(err: Error, vm: Component, info: string) => ?boolean
在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。
三、单文件组件
在很多 Vue 项目中,我们使用 Vue.component 来定义全局组件,紧接着用 new Vue({ el: '#container '}) 在每个页面内指定一个容器元素。这种方式在复杂的项目中,或者你的前端完全由 JavaScript 驱动的时候,下面这些缺点将变得非常明显:
- 全局定义:强制要求每个 component 中的命名不得重复。
- 字符串模板 :缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的
\。 - 不支持 CSS :意味着当 HTML 和 JavaScript组件化时,CSS 明显被遗漏 。
- 没有构建步骤 : 限制只能使用 HTML 和 ES5 JavaScript,而不能使用预处理器。
文件扩展名为 .vue 的单文件组件为以上所有问题提供了解决方法,单文件组件在实现各种功能的方式上与非单文件组件的实现方式语法大部分一致。
一个单文件组件的基本结构
<template>
<div>
···
</div>
</template>
<script>
// es6 导出规范 -babel(es6 ==> es)会自动将es6向下转
export default {
data() {},
methods: {}
}
</script>
<style scoped>
···
</style>
使用navbar组件
在使用navbar组件时,还要在局部注册,因为 navbar.vue只相当于原始注册组件中大括号内的内容。
<script>
// 导入navbar组件
import navbar from 'Navbar.vue文件的路径'
// Vue不是全局的,哪里用哪里引入
import Vue from 'vue'
export default ({
// 局部注册navbar组件
components: {
// navbar: navbar
// 简写
navbar
},
}
</script>
四、反向代理
当在用Vue开发项目时,遇到跨域问题,可以用反向代理解决
devServer: {
proxy: {
// 代理:只要是“别名/请求地址的前几个字符”开头的请求就将target拼接到请求前边
"别名/请求地址的前几个字符": {
target: "目的域名",
changeOrigin: true,
//只有在给请求取别名后才会用到这一选项
//功能是将别名去掉
pathRewrite: {
'别名': ''
}
}
}
}
原理是:当需要发出请求时,先将请求发给本机后端,再由本机后端将请求发送给服务器,然后由服务器将数据响应给后端,再将数据传给前端。
五、路由
使用vue.router开发项目时,路由配置文件终会有如下内容
1、引入组件
import Vue from 'vue'
import VueRouter from 'vue-router'
import Films from '自己的组件路径'
Vue.use(VueRouter)//注册路由插件,会同时定义两个全局组件router-view router-link
这样的引入方式会使项目所有的JavaScript在一个js文件里,项目上线后,若只打开主页,也会下载所有的JavaScript代码,当JavaScript代码非常多时会影响整个项目的效率。而下面的这种引入方法可以解决这个问题,在打开页面时之下再相应的JavaScript代码。
在配置路由表时引入,这就是懒加载
const routes = [
{
path: '路径',
component: () => import('组件路径')
}
]
2、配置路由表
//一级路由
const routes = [
{
path: '路径',
component: () => import('组件路径')
// 嵌套路由
children: [
{
path: '路径',
component: () => import('组件路径')
},
{
path: '路径',
component: () => import('组件路径')
},
···
]
}
···
]
重定向
“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b
*是万能匹配符,他是一个备用选项,当请求路径与所有其他路径都不匹配时,就会与他匹配,所以它的优先级应该很低,也就是在路由表中位置在最后。当与他匹配成功后就会将请求路径重定向为指定路径。
// 重定向
{
path: '*',
redirect: '指定路径'
}
动态路由
代码中“路径”是固定的,后边的“/:变量名”的值是可变的。也就是说在匹配时只要路径部分匹配成功且路径后边有内容,就会与该path匹配成功。参数值会被设置到 this.$route.params,可以在每个组件内使用。
{
// 动态路径参数 以冒号开头
path: '路径/:变量名', //动态二级路由
component: () => import('组件路径名')
}
命名路由
有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。
{
// 给路由命名
name: '名字',
path: '路径',
component: () => import('组件路径')
}
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
router.push({ name: 'user', params: {} })
别名
/a 的别名是 /b,意味着,当用户访问 /b时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
3、编程式导航
location.href="#/detail";
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
router.go()方法
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
HTML5 History 模式
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。url会带有#,例:http://yoursite.com/#/user/id
当你使用 history 模式时,URL 就像正常的 url,例:http://yoursite.com/user/id
const router = new VueRouter({
mode: 'history',
routes: [...]
})
3、路由拦截
每个守卫方法接收三个参数:
- to: Route: 即将要进入的目标 路由对象
- from: Route: 当前导航正要离开的路由
- next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
- next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from 路由对应的地址。
- next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
- next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向
- next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给
router.onError() 注册过的回调。
全局守卫(拦截)
//注册全局前置守卫
router.beforeEach((to, from, next) => {
// 是否需要验证
if (需要验证) {
// 是否通过凭证
if (通过验证) {
next();
} else {
// 写法一
// next('***')
// 写法二
next({
path: '****',
query: {
// redirect是随便起的名字
// 记录请求从哪来
redirect: to.fullPath
}
})
}
} else {
next();
}
})
//注册全局后置守卫
router.afterEach((to, from) => {
// ...
})
路由独享守卫(局部拦截,只拦截当前路径)
{
path: '',
component: ***,
beforeEnter: (to, from, next) => {
if (验证通过) {
next();//执行下一步
} else {
// 写法一
// next('***')
// 写法二
next({
path: '***',
query: {
// redirect是随便起的名字
// 记录请求从哪来
redirect: to.fullPath
}
})
}
}
}
组件内的守卫
最后,你可以在路由组件内直接定义以下路由导航守卫:
- beforeRouteEnter
- beforeRouteUpdate (2.2 新增)
- beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
注意:beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
Tip
加油啊,兄弟们!!!