-
watch
和computed
和methods
区别是什么?- 翻译一遍,说出作用,再找不同
computed
:计算属性
watch
:监听属性,一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。
methods
:methods
将被混入到Vue
实例中。可以直接通过VM
实例访问这些方法,或者在指令表达式中使用。 - 要点:
- 最大区别是
computed
有缓存:如果computed
属性依赖的属性没有变化,那么computed
属性就不会重新计算。watch
在每次监听的值变化时候,都会执行回调。 -
computed
是计算出一个属性,而watch
则可能是做别的事情(如上报数据)
- 最大区别是
- 翻译一遍,说出作用,再找不同
-
Vue
有哪些生命周期钩子函数?分别有什么用?
注:不要在生命周期函数中使用箭头函数
,因为箭头函数没有this
。使用会导致this作为变量一直向上级词法作用域查找,直至找到为止,从而引发类如Uncaught TypeError: Cannot read property of undefined
或Uncaught TypeError: this.myMethod is not a function
之类的错误。-
beforeCreate
:在实例初始化之后,数据观测(data observer
)和event/watcher
事件配置之前被调用。 -
created
:在实例创建完成后被立即调用,在这一步实例已经完成以下配置:数据观测(data observer
)、属性和方法的运算、event/watch
事件回调,然而挂载还没开始,$el
属性目前还不可见。 -
beforeMount
:在挂载开始之前被调用,相关的渲染函数首次被调用 。 -
mounted
:el
被新创建的vm.$el
替换,挂载成功。一般在这个钩子中请求数据 -
beforeUpdate
:数据更新前调用。 -
updated
:组件 DOM已经更新,组件更新完毕。 -
beforeDestroy
:整个页面,应用被销毁前调用,此时实例仍然完全可用。 -
destroyed
:Vue
实例销毁后调用,Vue
实例指示的所有东西都会解绑定,所有时间监听器会被移除,所有子实例也会被销毁。
-
-
Vue
如何实现组件间通信?- 父子组件:
$emit('xxx',data)
$on('xxx',function(){})
- 任意组件,爷孙组件:使用
var eventBus = new Vue()
来通信,eventBus.$on
和eventBus.$emit
是主要API - 任意组件:使用
Vuex
通信
- 父子组件:
-
Vue
数据响应式怎么做到的?- 这篇文章我觉得讲的比较明了《详解Vue响应式原理》
-
Vue 2.0
版本 主要通过Object.defineProperty
实现- 使用
Object.defineProperty
把这些属性全部转为getter/setter
,每个组件实例都对应一个watcher
实例,它会在组件渲染的过程中把“接触”过的数据property
记录为依赖。之后当依赖项的setter
触发时,会通知watcher
,从而使它关联的组件重新渲染。 -
Vue
不能检测到对象属性的添加或删除,解决方法是手动调用Vue.set(object,key,value)
或者this.$set(this.object,key,value)
- 使用
-
Vue 3.0
主要通过Proxy
实现,区别就在于Proxy
代理的是整个对象,而Object.defineProperty
代理的是对象的某个属性,需要遍历整个对象的属性。
-
Vue.set
是做什么用的?
添加或者删除一个对象属性时候用 -
Vuex
你怎么用的?-
Vuex
是一个专为Vue.js
应用程序开发的状态管理工具 - 核心概念:
2.1State
:声明定义数据,唯一的数据源。 从store 实例中读取状态最简单的方法就是在计算属性中返回某个状态。
Vuex
通过store
选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用Vue.use(Vuex)
):
-
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
2.2 Getters
:可以认为是 store
的计算属性,getter
的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。Getter
接受 state
作为其第一个参数:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
Getter 也可以接受其他 getter 作为第二个参数:
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
注意:getter
在通过属性访问时是作为 Vue
的响应式系统的一部分缓存其中的,getter
在通过方法访问时,每次都会去进行调用,而不会缓存结果。
2.3 Mutation
:更改Vuex
的 store
中的状态的唯一方法是提交 mutation
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment//事件类型type (state) {
// 变更状态
state.count++
}//回调函数 (handler)
}
})
store.commit('increment') //提交一个`mutation`来触发状态的更改
可以向 store.commit
传入额外的参数,即 mutation
的 载荷(payload
):
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
或者
store.commit({
type: 'increment',
amount: 10
})
注意:Mutation
必须是同步函数!!因为当 mutation
触发的时候,回调函数还没有被调用
2.4 Actions
:Action
类似于 mutation
,不同在于:1.Action
提交的是 mutation
,而不是直接变更状态。2.Action
可以包含任意异步操作。
Action
函数接受一个与store
实例具有相同方法和属性的 context
对象,因此你可以调用 context.commit
提交一个 mutation
,或者通过 context.state
和 context.getters
来获取state
和 getters
:
...
actions: {
increment (context) {
context.commit('increment')
}
}
实践中,我们常常用参数解构来简化代码,特别是需要commit
很多次的时候:
actions: {
increment ({ commit }) {
commit('increment')
}
}
//异步
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
Action
通过 store.dispatch
方法触发:
store.dispatch('increment')
//or
store.dispatch({
type: 'increment',
payload: payload
})
2.5 Modules
:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store
对象就有可能变得相当臃肿。
为了解决以上问题,Vuex
允许我们将 store
分割成模块(module
)。每个模块拥有自己的 state、mutation、action、getter
、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
对于模块内部的mutation
和 getter
,接收的第一个参数是模块的局部状态对象。
对于模块内部的 action
,局部状态通过 context.state
暴露出来,根节点状态则为 context.rootState
(第三个参数):
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
默认情况下,模块内部的 action
、mutation
和 getter
是注册在全局命名空间的——这样使得多个模块能够对同一 mutation
或 action
作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter
、action
及 mutation
都会自动根据模块注册的路径调整命名:
const moduleA = {
namespaced: true,
state: {
count: 3
},
mutations: {
increment (state) {
state.count++
}
}
...
}
store.commit('a/increment') //才能访问到对应的`mutation`
-
VueRouter
你怎么用的?-
Vue Router
是Vue.js
官方的路由管理器。 - 核心概念:
-
- 动态路由匹配:我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如现在有个
User
组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在vue-router
的路由路径中使用“动态路径参数
”(dynamic segment
) 来达到这个效果:
const User = {
template: '<div>User</div>'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
现在呢,像 /user/foo 和 /user/bar 都将映射到相同的路由。当匹配到一个路由时,参数值会被设置到 this.$route.params
,可以在每个组件内使用。于是,我们可以更新User
的模板,输出当前用户的 ID:const User = { template: '<div>User {{ $route.params.id }}</div>' }
常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*):
{
// 会匹配所有路径
path: '*'
}
{
// 会匹配以 `/user-` 开头的任意路径
path: '/user-*'
}
当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。当使用一个通配符时,$route.params
内会自动添加一个名为 pathMatch
参数。它包含了 URL 通过通配符被匹配的部分:// 给出一个路由 { path: '/user-*' } this.$router.push('/user-admin') this.$route.params.pathMatch // 'admin'
- History 模式:
vue-router
默认的是hash模式
----使用URL
的hash
来模拟一个完整的URL
,于是当URL
改变时,页面不会重新加载。如果不想要这种带hash
的UR
L的话,可以使用History模式
,这种模式利用的是history.pushState
API来完成URL
的跳转,因此不会重新加载页面。不过History模式
需要后台的支持,需要服务端增加一个覆盖所有情况的候选资源,当URL
匹配不到任何静态资源,则应该返回app
依赖的页面。
const router = new VueRouter({
mode: 'history',
routes: [...]
})
- 路由懒加载:当打包构建应用时,
JavaScript
包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。结合webpack
的代码分块功能,只需要import('./Foo.vue')//返回Promise
就可以实现懒加载。
结合vue的异步组件和webpack,就能定义一个能够被 Webpack 自动代码分割的异步组件:
const Foo = () => import('./Foo.vue')
- 重定向和别名:
重定向:重定向通过 routes 配置来完成,从/a重定向到/b:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
别名:/a
的别名是 /b
,意味着,当用户访问/b
时,URL
会保持为/b
,但是路由匹配则为/a
,就像用户访问 /a
一样。
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
代码为上述的路由配置。 ''别名''的作用是让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。
3. 常用 API:
router-link
: 当被点击后,内部会立刻把 to
的值传到router.push()
<router-link to="home">Home</router-link> //字符串
<router-link :to="'home'">Home</router-link> //v-bind的简略写法
<router-link :to="{ path: 'home' }">Home</router-link> //对象
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link> //命名的路由
<router-link :to="{ path: 'register', query: { plan: 'private' }}"
>Register</router-link
>//带查询参数 /register?plan=private
设置 replace
属性的话,当点击时,会调用 router.replace()
而不是 router.push()
,于是导航后不会留下 history
记录。
<router-link :to="{ path: '/abc'}" replace></router-link>
router-view
: 命名视图,有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。如果 router-view 没有设置名字,那么默认为 default。
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
路由配置:
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
router.push/router.replace/router.go/router.back/router.forward
:动态的导航到一个新 URL
router.push(location, onComplete?, onAbort?)
router.push(location).then(onComplete).catch(onAbort)
router.replace(location, onComplete?, onAbort?)
router.replace(location).then(onComplete).catch(onAbort)
router.go(n)
router.back()
router.forward()
router.onError
:注册一个回调,该回调会在路由导航过程中出错时被调用。
router.onError(callback)
注意被调用的错误必须是下列情形中的一种:
1.错误在一个路由守卫函数中被同步抛出;
2.错误在一个路由守卫函数中通过调用 next(err)
的方式异步捕获并处理;
3.渲染一个路由的过程中,需要尝试解析一个异步组件时发生错误
- 什么是导航守卫?
- 概念: 导航守卫:
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。简单来说,导航守卫就是路由跳转期间的钩子函数,它分为全局的、单个路由独享的、组件内部的三种。 - 导航守卫分类:
1.全局的
全局前置守卫
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
全局解析守卫
在 2.5.0+ 你可以用router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子
router.afterEach((to, from) => {
// ...
})
router.afterEach
不会接受 next 函数也不会改变导航本身。
2.路由独享的守卫
目前只有一个钩子函数beforeEnter
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
这些守卫与全局前置守卫router.beforeEach
的方法参数是一样的。
3.组件内部的守卫
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
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。不过,你可以通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteLeave
通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
beforeRouteLeave (to, from, next) {
const answer = window.confirm('你确定要离开嘛? 你还未保存更改!')
if (answer) {
next()
} else {
next(false)
}
}
导航参数解析
每个守卫方法接收三个参数(router.afterEach
没有next
):
to: Route
即将要进入的目标 路由对象。
from: Route
当前导航正要离开的路由。
next: Function
一定要调用该方法来resolve
这个钩子,如果不写这个方法,路由将不会跳转。执行效果依赖next
方法的调用参数:
-next()
进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
-next(false)
中断当前的导航。如果浏览器的URL
改变了 (可能是用户手动或者浏览器后退按钮),那么URL
地址会重置到from
路由对应的地址。
-next('/')
或者next({ path: '/' })
跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。 可传递的参数和router-link :to='参数'
或者router.push('参数')
中的参数是一样的。
-next(error)
(2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
- 在beforeRouteEnter
中next(vm => {//通过 vm 访问组件实例})
完整的导航解析流程
当点击跳转路由时:
1. 导航被触发。
2. 在失活的组件内调用beforeRouteLeave
守卫。
3. 调用全局的beforeEach
守卫。
4. 在重用的组件内调用beforeRouteUpdate
守卫 (2.2+)。
5. 在路由配置里调用beforeEnter
。
6. 解析异步路由组件。
7. 在被激活的组件内调用beforeRouteEnter
。
8. 调用全局的beforeResolve
守卫 (2.5+)。
9. 导航被确认。
10. 调用全局的afterEach
钩子。
11. 触发DOM
更新。
12. 用创建好的实例调用beforeRouteEnter
守卫中传给next
的回调函数。