Vue 入门基础笔记
Vue 实例上的属性
组件树
$parent
:用来访问组件实例的父实例
$root
: 用来访问当前组件树的根实例
$children
:用来访问当前组件实例的直接子组件实例
$refs
:用来访问 ref 指令的子组件
DOM 访问
$el
:用来挂载当前组件实例的 dom 元素
$els
:用来访问$el 元素中使用了 v-el 指令的 DOM 元素
数据访问
$data
:用来访问组件实例观察的数据对象
$options
:用来访问组件实例化时的初始化选项对象
DOM 方法的使用
$appendTo(elementOrSelector, callback)
:将 el 所指的 DOM 元素插入目标元素
$before(elementOrSelector, callback)
:将 el 所指的 DOM 元素或片段插入目标元素之前
$after(elementOrSelector, callback)
:将 el 所指的 DOM 元素或片段插入目标元素之后
$remove(callback)
:将 el 所指的 DOM 元素或片段从 DOM 中删除
$nextTick(callback)
:用来在下一次 DOM 更新循环后执行指定的回调函数
1 | // vue 的 渲染过程是异步的 |
可以看到 text 值的变化是 0 5 10 15 … 而并没有出现 0 1 2 3 … 这样连续的变化
event 方法的使用
1.监听
$on(event, callback)
:监听实例的自定义事件
$once(event, callback)
:同上,但只能触发一次
$watch(property,callback(new, old))
: 监听属性的变化,拿到变化前后的值
1 | // 第一种写法 |
1 | // 第二种写法 |
// 两种写法的结果一样,只是第二种需要在组件销毁手动销毁$watch
2.触发
$dispatch(event,args)
:派发事件,先在当前实例触发,再沿父链一层层向上,对应的监听函数返回 false 停止
$broadcast(event,args)
:广播事件,遍历当前实例的$children,如果对应的监听函数返回 false,就停止
$emit(event, args)
:触发事件
3.删除
$off(event, callback)
:删除时间监听
4.其他
$forceUpdate()
:强制组件刷新
$set(ele,attr,value)
:给对象设置属性
$delete(ele,attr,value)
:删除对象属性
1 | <template> |
Vue 生命周期
vue 官方生命周期
1 | render (h) { |
如果要修改 data 里面的值,最早只能放到 create 生命周期中
Vue 数据绑定
1 | <template> |
v-model 的修饰符
来自官网的例子:
1 .number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type=”number”时,HTML 输入元素的值也总会返回字符串。
2 .trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<input v-model.trim="msg">
3 .lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步(当输入框失去焦点):
1 | <!-- 在“change”时而非“input”时更新 --> |
数组和对象的注意事项
数组
由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
- 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:vm.items.length = newLength
1 | var vm = new Vue({ |
为了解决第一类问题,以下两种方式都可以实现和 ·vm.items[indexOfItem] = newValue· 相同的效果,同时也将触发状态更新:
1 | // Vue.set |
你也可以使用 vm.$set
实例方法,该方法是全局方法 Vue.set
的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决第二类问题,你可以使用 splice:
vm.items.splice(newLength)
对象
Vue 不能检测对象属性的添加或删除:
1 | var vm = new Vue({ |
对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, key, value)
方法向嵌套对象添加响应式属性。例如,对于:
1 | var vm = new Vue({ |
你可以添加一个新的 age 属性到嵌套的 userProfile 对象:Vue.set(vm.userProfile, 'age', 27)
你还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:vm.$set(vm.userProfile, 'age', 27)
有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign()
或 _.extend()
。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:
1 | Object.assign(vm.userProfile, { |
你应该这样做:
1 | vm.userProfile = Object.assign({}, vm.userProfile, { |
computed 计算属性
计算属性的使用
1 | <template> |
双向绑定的计算属性与 Vuex
1 | // vuex state是无法直接修改的,官方给出了 v-model 的解决方案 |
如果在方法或者生命周期中使用了计算属性,则必须设置一个 set
watch 监听器
watch 简单使用
1 | <div id="demo">{{ fullName }}</div> |
1 | watch: { |
监听对象属性的变化
1 | <div id="demo">{{ obj.a }}</div> |
1 | // 这样写就能监听到属性值的变化 |
Vue 组件
Vue 组件中的 data 为什么必须是函数
在 Vue 组件中 data 必须是函数,但是在 new Vue()
中 data 可以是一个对象
1 | Vue.component('MyComponent', { |
上面定义了一个 MyComponent 组件,在这里我们可以把这个组件看成一个构造函数。在其他页面引入,并注册组件时,实际上是对这个构造函数的一个引用。当在模板中正真使用组件时类似于实例化了一个组件对象。
1 | // 模拟一下 |
可以看出,两个实例组件对象的 data 是一模一样的,一个改变也会导致另一个改变,这在实际开发中是不符合组件式思想的。
1 | // 模拟一下 |
用 Vue.use() 定义全局组件
1 | // 定义一个 button 组件 |
1 | // button.js |
1 | // main.js |
完成上面的步骤就可以在全局使用 button 组件了,其实最重要的Vue.component('Button',ButtonComponent)
,Vue.use(Button)
会执行 install 方法,也可以直接在main.js
使用Vue.component()
注册全局组件。
props
1 | <template> |
子组件是不能直接修改 props 的。
Vue 组件 extend
使用 Vue.extend
就是构造了一个 Vue 构造函数的“子类”。它的参数是一个包含组件选项的对象,其中data
选项必须是函数。
1 | import Vue from 'vue' |
1 | const component2 = { |
Vue 组件高级属性
Vue 组件插槽
通常我们会向一个组件中传入一些自定义的内容,这个时候就可以用到插槽。插槽内可以包含任何模板代码,包括 HTML 或者是一个组件。
1 | // 定义一个带插槽的组件 |
具名插槽
1 | <div class="container"> |
具名插槽的使用:
第一种:在一个父组件的 <template>
元素上使用 slot
特性
1 | <base-layout> |
第二种:直接在普通元素上使用
1 | <base-layout> |
插槽的默认内容
在插槽中可以设置一个默认内容,如果用户没有设置新的内容,则会显示默认内容
1 | <button> |
作用域插槽
2.1.0+ 新增 在 2.5.0+,slot-scope
不再限制在 <template>
元素上使用,而可以用在插槽内的任何元素或组件上。
1 | const component = { |
provide/inject 跨级组件交互
2.2.0 新增
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
1 | // 父级组件提供 'foo' |
如果是注入一个父级组件内部的值,provide 需要作为一个函数,类似于 data
1 | const component = { |
如果要监听父级组件的属性值的变化,从而自动更新子组件的值,需要手动实现监听
1 | const component = { |
Vue 的 render
Vue-router
router 构建选项
重定向:
1 | { |
History 模式:
1 | const router = new VueRouter({ |
vue-router
默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。
给个警告页:
1 | const router = new VueRouter({ |
base
1 | const router = new VueRouter({ |
当访问localhost:8080/hello
会变成localhost:8080/base/hello
,所有的路由路径都会加上/base
,当然手动删除/base
还是可以打开页面
linkActiveClass 和 linkExactActiveClass
1 | <router-link to="/app">app</router-link> |
router-link
在页面中会渲染成a
标签,点击之后会添加两个类名:router-link-exact-active
和 router-link-active
1 | const router = new VueRouter({ |
这相当于是重新命名了两个类名。
两者的不同点:
1 | <router-link to="/login">login</router-link> |
上面这两个路由有一部分/login
是相同的,在点击了login exact
路由调转到/login/exact
后:
/login
上还保留了router-link-active
类名
scrollBehavior
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
1 | const router = new VueRouter({ |
scrollBehavior
方法接收 to
和 from
路由对象。第三个参数 savedPosition
当且仅当 popstate
导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
parseQuery 和 stringifyQuery
提供自定义查询字符串的解析/反解析函数。覆盖默认行为。
1 | const router = new VueRouter({ |
fallback
当浏览器不支持 history.pushState
控制路由是否应该回退到 hash
模式。默认值为 true。
在 IE9 中,设置为 false 会使得每个 router-link
导航都触发整页刷新。它可用于工作在 IE9 下的服务端渲染应用,因为一个 hash 模式的 URL 并不支持服务端渲染。
1 | const router = new VueRouter({ |
路由元信息
1 | const router = new VueRouter({ |
那么如何访问这个 meta
字段呢?
首先,我们称呼 routes
配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录
例如,根据上面的路由配置,/foo/bar
这个URL
将会匹配父路由记录以及子路由记录。
一个路由匹配到的所有路由记录会暴露为 $route
对象 (还有在导航守卫中的路由对象) 的 $route.matched
数组。因此,我们需要遍历 $route.matched
来检查路由记录中的 meta
字段。
下面例子展示在全局导航守卫中检查元字段:
1 | router.beforeEach((to, from, next) => { |
命名视图
在一个路由下展示多个视图组件,用的并不多
1 | // 在这个页面中要分别展示三个视图 |
1 | const router = new VueRouter({ |
导航守卫
路由改变时,按顺序触发的钩子函数
全局守卫
1 | const router = new VueRouter({ ... }) |
每个守卫方法接收三个参数:
to: Route
: 即将要进入的目标 路由对象from: Route
: 当前导航正要离开的 路由对象next: Function
: 一定要调用该方法来resolve
这个钩子。执行效果依赖next
方法的调用参数。
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed
(确认的)。next(false)
: 中断当前的导航。如果浏览器的URL
改变了 (可能是用户手动或者浏览器后退按钮),那么URL
地址会重置到from
路由对应的地址。next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如replace: true、name: 'home'
之类的选项以及任何用在router-link
的to
prop
或router.push
中的选项。next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
确保要调用 next
方法,否则钩子就不会被 resolved
。
路由对象
一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records)。
路由对象是不可变 (immutable) 的,每次成功的导航后都会产生一个新的对象。
路由对象属性:
- $route.path
类型: string
字符串,对应当前路由的路径,总是解析为绝对路径,如 “/foo/bar”。
- $route.params
类型: Object
一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
- $route.query
类型: Object
一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。
- $route.hash
类型: string
当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
- $route.fullPath
类型: string
完成解析后的 URL,包含查询参数和 hash 的完整路径。
- $route.matched
类型: Array
一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。
1 | const router = new VueRouter({ |
当 URL 为 /foo/bar,$route.matched
将会是一个包含从上到下的所有对象 (副本)。
- $route.name
当前路由的名称,如果有的话。(查看命名路由)
- $route.redirectedFrom
如果存在重定向,即为重定向来源的路由的名字
全局后置钩子
1 | router.afterEach((to, from) => { |
路由独享的守卫
1 | const router = new VueRouter({ |
组件内的守卫
1 | const Foo = { |
beforeRouteEnter 守卫不能访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
1 | beforeRouteEnter (to, from, next) { |
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发
DOM
更新。 - 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
异步路由
在路由文件中,直接 import 所有组件势必造成页面首次渲染时间变长,异步路由,当进入对应的路由才加载对应的页面。
1 | const router = new VueRouter({ |
这种写法需要安装syntax-dynamic-import
,并在.babelrc
进行配置
1 | // .babelrc |
Vux
以下内容来自官网:https://vuex.vuejs.org/zh/
简单使用 vuex
1 | // store.js |
1 | // main.js |
1 | // 任意组件 |
核心概念
State
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
大白话: state 就相当于是个全局对象,通过 Vue.use(Vuex)全局注册了 vuex 之后,在任意组件中可以用this.$store.state
拿到该对象
Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态。
1 | computed: { |
当state
中的count
变化时,自动会更新computed
,从而改变相关DOM
mapState 辅助函数
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用mapState
辅助函数帮助我们生成计算属性,让你少按几次键:
1 | // 在单独构建的版本中辅助函数为 Vuex.mapState |
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
1 | computed: mapState([ |
Getter
Getter 就是 vuex 种 state 的 computed,通过 state 派生出新的 state,而且它会被缓存起来,只有依赖的 state 发生变化才会重新计算
1 | export default { |
mapGetters 辅助函数
getter 的使用和 state 类似,可以把它看成 state 来用。
1 | import { mapGetters } from 'vuex' |
如果想给 getter 换个名字,方法和 state 一样,不重复
Mutation
Mutation 必须是同步的
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
1 | const store = new Vuex.Store({ |
你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:store.commit('increment')
提交载荷(传参)
你可以向 store.commit
传入额外的参数,即 mutation
的 载荷(payload):
1 | // ... |
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
1 | // ... |
对象风格的提交方式
提交 mutation
的另一种方式是直接使用包含 type 属性的对象:
1 | store.commit({ |
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:
1 | mutations: { |
使用常量替代 Mutation 事件类型
使用常量替代 mutation
事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter
之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app
包含的 mutation
一目了然:
1 | // mutation-types.js |
在组件中提交 Mutation
你可以在组件中使用 this.$store.commit('xxx')
提交 mutation
,或者使用 mapMutations
辅助函数将组件中的 methods
映射为 store.commit
调用(需要在根节点注入 store)。
1 | import { mapMutations } from 'vuex' |
Action
Action 可以包含异步操作
Action 跟 Mutation 类似,Action 是调用commit
方法,提交mutation
的。
1 | const store = new Vuex.Store({ |
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit
提交一个 mutation
,或者通过 context.state
和 context.getters
来获取 state
和 getters
。
实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):
1 | actions: { |
实际代码:
在组件中分发 Action
你在组件中使用 this.$store.dispatch('xxx')
分发 action
,或者使用 mapActions
辅助函数将组件的 methods
映射为 store.dispatch
调用(需要先在根节点注入 store):
1 | import { mapActions } from 'vuex' |
严格模式
开启严格模式,仅需在创建 store 的时候传入 strict: true:
1 | const store = new Vuex.Store({ |
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
开发环境与发布环境
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
1 | const store = new Vuex.Store({ |