watch 和 computed 和 methods 区别是什么?
computed
计算属性,在模板中双向绑定一些数据或表达式时,如果表达式过长,或逻辑更为复杂,就会变得臃肿,难以维护和阅读
<div>
{{text.split(',').reverse().join(',')}}
</div>
这里表达式包含3个操作,遇到复杂的逻辑时应该使用计算属性
所有的计算属性都以函数的形式写在 Vue 实例内的 computed 选项中,最终返回计算后的结果
计算属性可以依赖多个 Vue 实例的数据,只要其中任一数据变化,计算属性就会重新执行,视图也会更新,也可以依赖其他计算属性
上述例子,利用了 getter 来读取,当手动修改计算属性的值时,就会触发 setter 函数,执行一些操作
methods vs computed
调用 methods 里的方法也可以与计算属性起到同样的作用
在页面中调用方法,只要页面重新渲染,方法就会重新执行,不需要渲染,则不需要重新执行
计算属性:不管渲染不渲染,只要计算属性依赖的数据未发生变化,就永远不变
计算属性是基于它的依赖缓存的,一个计算属性所依赖的数据发生变化时,它才会重新取值,所以 text 只要不改变,计算属性也就不更新
使用场景
使用计算属性还是 methods 取决于你是否需要缓存,当遍历大数组和做大量计算时,应当使用计算属性,除非你不需要缓存。
watch
监听器,侦听一个特定的值,若该值发生变化时执行对应的函数,例如分页组件中,我们可以监听当前页码,当页码变化时执行对应的获取数据的函数。
和computed 的区别:
都是在依赖的值变化之后,去执行回调,
- 如果一个值依赖多个属性(多对一),用computed更加方便一些,如果一个值变化后会引起一系列操作,或者一个值的变化会引起一系列值得变化(一对多),用 watch 更加方便一些。
watch
的回调里面会传入监听属性的新旧值,通过这两个值可以做一些特定的操作。computed
通常就是简单的计算。
有哪些生命周期钩子函数?分别有什么用?
在不同阶段被调用
beforeCreated()
第一个生命周期钩子,在 Vue 实例初始化后,创建组件前,立即被调用,(尚未设置计算属性,观察者,事件,数据属性和操作等内容)
created()
第二个生命周期钩子,在这个阶段,已经设置了计算属性,观察者,事件,数据属性和操作,但 Vue 实例尚未挂载到页面上
beforeMount()
这是在 DOM 上挂载实例之前的那一刻,模板已经编译(data 中的数据和模板生成 html),但无法操作 DOM
mounted()
挂载完成后(模板渲染到了页面中),请求数据一般也在这里进行,DOM 的操作放在这里
beforeUpdate()
数据更新前调用
updated()
当数据发生改变后,被调用
beforeDestroy()
在 Vue 实例被销毁之前,被调用
destroyed()
这是最后一个生命周期钩子,所有的子 Vue 实例都已经被销毁。
Vue 数据响应式(双向绑定)怎么做到的
使用Object.defineProperty
把属性全部转为getter/setter ,受现代 JavaScript 的限制 (而且Object.observe
也已经被废弃),Vue无法检测到对象属性的添加或删除,使用vm.$set
,这也是全局Vue.set
方法的别名 来解决
this.$set(this.someObject,'b',2)
如何实现组件间通信?
父子组件:使用 props 传递数据
- 在组件中使用 props 来从父组件中接收参数
- props 来自父级,而组件中的 data return 的数据是组件自己的数据,其作用域对应的组件本身,可以在 template, computed, methods 中直接使用
- props 的值有两种,一种是字符串数组,一种是对象
- 可以使用 v-bind 动态绑定父组件中的内容
这里涉及到 单向数据流 的概念,通过 props 传递数据是单向的,父组件中数据变化时会传递给子组件,但是反过来不行,其目的是解耦,避免子组件无意中修改了父组件的状态
父组件传递初始值进来,子组件将它作为初始值保存进来,在自己的作用域下可以随意使用和修改,这种情况可以在组件 data 内再声明一个数据,引用父组件的 prop
<div id="app">
<my-comp init-count="666"></my-comp>
</div>
<script>
var app = new Vue({
el: '#app',
components: {
'my-comp': {
props: ['init-count'],
template: '<div>{{init-count}}</div>',
data: function() {
return {
count: this.initCount
}
}
}
}
})
</script>
子组件给父组件传递数据:自定义事件
子组件用 $emit() 来触发事件,父组件用 $on() 来监听子组件的事件(利用了观察者模式)
- 自定义事件
- 在子组件中用 $emit() 来触发事件,第一个参数是事件名,后面的参数是要传递的数据
- 在自定义事件中用一个参数来接收
//模板
<div id="app">
<p>您的余额是{{total}}</p>
<btn-component @change="handleTotal"></btn-component>
</div>
//逻辑
var app = new Vue({
el: '#app',
data: {
total: 0
},
components: {
'btn-component': {
template: '<div>
<button @click="handleIncrease">+1</button>
<button @click="handleReduce">-1</button>
</div>',
data: function() {
return {
count: 0
}
},
methods: {
handleIncrease: function() {
this.count++
//触发 change 对应的方法,this.count 为参数
this.$emit('change',this.count)
},
handleReduce: function() {
this.count--
this.$emit('change',this.count)
}
}
}
},
methods: {
//参数 total 为 传递过来的 this.count
handleTotal: function(total) {
this.total = total
}
}
})
非父组件间的通信
使用 bus = new Vue() 来通信,eventBus.$on 和 eventBus.$emit 是主要API
根组件创建 bus
var app = new Vue({
el: '#app',
data: {
bus: new Vue()
}
})
子组件
Vue.component('my-acomponent',{
template:'<div><button @click="handle">点击我向B组件传递数据</button></div>',
data: function() {
return {
aaa: '我是来自A组件的内容'
}
},
methods: {
handle: function() {
this.$root.bus.$emit('lala',this.aaa)
}
}
})
Vue.component('my-bcomponent',{
template: '<div>我是B组件</div>',
created: function() {
//B组件在实例创建的时候就监听 lala 事件
this.$root.bus.$on('lala',function(value){
alert(value)
})
}
})
使用 Vuex 通信
用来管理状态,共享数据,在各个组件之间管理外部状态
获取状态
- 引入 vuex, 并通过 use 方法使用它
- 创建状态仓库
- 通过 this.$store.state.xxx 直接拿到需要的数据
import Vuex from 'vuex'
Vue.use(Vuex)
//创建状态仓库
var store = new Vuex.Store({
state: {
XXX: xxx
}
})
//直接通过 this.$store.state.xxx 拿到全局状态
改变状态
- 通过 mutations
//创建状态仓库
var store = new Vuex.Store({
state: {
XXX: xxx
},
mutations: {
//定义状态改变函数,直接改变 state
a: function (state) {
//xxxx
}
}
})
//调用改变状态的函数
//this.$store.commit('a')
2. 通过 actions
actions 提交的是 mutation, 而不是直接变更状态
//创建状态仓库
var store = new Vuex.Store({
state: {
XXX: xxx
},
mutations: {
//定义状态改变函数,直接改变 state
a: function (state) {
//xxxx
}
},
actions: {
b: function (context) {
//只能对 mutation 进行操作
context.commit('a')
}
}
})
//调用 actions 中的方法
//this.$store.dispatch('b')
为啥要用 actions 呢
actions 中可以包含异步操作,但是 mutation 中只能包含同步操作
3. getters
对数据进行处理(点击数据减少,最小为0)
...
getters: {
c(state) {
//对 state 中的数据做处理(最小为0,不能为负数)
}
}
//调用 getters 中的方法
//this.$store.getters.c
VueRouter 怎么使用
Vue Router 是 Vue.js 官方的路由管理器
History 模式
这种模式充分利用history.pushState
API 来完成 URL 跳转而无须重新加载页面。
const router = new VueRouter({
mode: 'history',
routes: [...]
})
当你使用 history 模式时,URL 就像正常的 url,例如http://yoursite.com/user/id
,也好看!
导航/路由守卫
https://zhuanlan.zhihu.com/p/59889754
https://www.jianshu.com/p/dcf5ce5f3ed7
场景:有些页面必须要登录才能操作,例如登录状态下才能编辑/创建博客
路由元信息,配置路由时,加一个 meta以及导航守卫去控制路由对应组件的入口,
路由懒加载
常用API
router-link: 支持用户在具有路由功能的应用中 (点击) 导航,通过to
属性指定目标地址
router-view: <router-view>
组件是一个 functional 组件,渲染路径匹配到的视图组件
this.$router.push:
- 想要导航到不同的 URL,使用这个方法,这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
- 当你点击
<router-link>
(声明式)时,这个方法会在内部调用,所以说,点击<router-link :to="...">
等同于调用router.push(...)
(编程式)。
this.$router.replace: 跟router.push
很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
this.$route.params:获取到路径路径参数及所对应的值({路径参数:值})