Vue
响应式
只有当实例被创建时就已经存在于 data 中的 property 才是响应式的
1 | var data = { name: "yck" }; |
响应式原理
Vue2.0
响应式主要是三个部分,Observer 进行数据劫持,Dep 进行依赖收集发生变化触发观察者,Watcher 生成观察者更新视图,将观察者的实例挂载到 Dep 类中,(Dep.target = Watcher 的实例).数据变化,调用回调更新视图.
- 首先,利用
Object.defineProperty
将传入的对象属性添加 getter,setter. - 当数据读取时, 在 getter 中进行依赖收集(Dep),也就是把当前对象的 watcher 实例添加到依赖中,
- 在 setter 中设置依赖通知,在数据变化的时候,依赖通知会通知所有的 watcher 实例执行更新,比如 watch 和 computed 就执行自定义的方法.
Vue3.0
Vue3 最大的变化时是 Observer 响应式中使用了 Proxy.
解决的 Vue2 中:
- 对属性的添加,删除的监测(之前使用$set)
- 对数组基于下标的修改,.length 修改的监测(数组内部变化,之前重写了数组方法)
- 对 Map,Set 等的支持
依赖收集
Vue3 通过track
收集依赖,通过trigger
触发更新.本质上通过weakMap
, Map
,Set
实现.
模板字符串
1 | `(${monthLine.label})`; |
数据传递
1 | v-model='value' |
给父组件传值
1 | //第一种 |
监听的顺序
关于$emit
和$on
的顺序问题,注意是先开启$on
进行监听,然后触发$emit
.否则触发的时候,$on
并没有执行.也就监听不到.
比如使用uni.$emit
进行触发时,如果跳转到另一个页面,$on
是监听不到的,因为已经先触发了emit
.
正确使用方法: 比如需要回退,那么先打开监听$on
,然后在另一页面触发后,回退到该页面,该页面就已经监听到了.
组件传值
子组件通过$emit给父组件传值,附带参数. this.$emit('change', data)
.
传递多个参数可以使用函数,this.$emit('change', {data0, data1})
父组件在页面接收时不用写接收参数.@change='change()'
括号里不用写参数.函数里再写.e
nextTick
- 在
created
中进行 DOM 操作一定要放在Vue.nextTick
中 - 在数据变化后要执行某个操作,而这个操作需要使用随数据改变而改变 DOM 结构时,要放在
Vue.nextTick()
中. - 操作数组中的 ref 时,注意 ref 的 VueComponent 节点渲染也是数组,要取数组的第一个,即后面要加
[0]
.
1 | this.$nextTick(() => this.$refs[`sub${index}`][0].init()); |
数据劫持
Vue2 中无法使用Object.defineproperty
给数据添加getter/setter
属性,但是这个是 Object 的方法,不能操作数组.就要对数组方法重写.即数据劫持.
要重写的是改变数组的方法,一共 7 个.push,pop,shift,unshift,sort,splice,reserve
.
生命周期
created: DOM 创建但是未挂载,所以这里访问不了 DOM,一般用作调用 api.Vue3 中的 setup 语法题里也获取不到 DOM.
mounted: 页面挂载后,这里就可以拿到 DOM 了.
动态参数
v-
指令后面可以跟一个动态参数,用方括号[]
包裹.里面是一个 js 表达式的结果.
1 | //bind后的动态参数表达响应式的绑定这个值 |
约束
动态参数预期要一个字符串,异常情况下值为null
.这个null
会被用于移除绑定.
空格和引号在方括号里是无效的.可以用计算属性替代这种写法.
这里不要用大写,因为会被浏览器强制转换.
动态 class 与 style
v-bind:class
接收一个对象以动态切换 class.
1 | <div v=bind:class="{active: idActice, 'text-danger': hasError }"></div> |
计算属性
计算属性所计算的值不用在data
中声明.可以说是在computed
中声明的一个参数.利用this
中的参数去处理之后得到这个值.处理完要return
出去.
原理
动态 class 和计算属性
1 | <div :class="classObject"></div> |
数组语法
1 | <div :class="[activeClass, errorClass]"></div> |
数组语法加对象语法
1 | <div :class="[{ active: isActive }, errorClass]"></div> |
绑定内联样式
对象语法
1 | <div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> |
数组语法
1 | <div :style="[activeColor, baseStyle]"></div> |
v-for
v-for
第一个参数是 value,第二个可以是键名name
,第三个可以是索引index
.
key
key 的特殊属性用于虚拟 dom 算法中对比新旧 nodes,辨识 VNodes.
如果不使用 key,Vue 会就地复用相同类型元素.可能会造成组件没有重新渲染.
使用 key,它会基于 key 的变化重新排列元素顺序,并会移除 key 不存在的元素.
下列场景适合使用 key:
- 完整触发组件的生命周期钩子
- 触发过渡
1 | <transition> |
当 text 发生改变时, 总是会被替换而不是被修改,因此会触发过渡。
搭配计算属性
1 | <li v-for="n in evenNumbers">{{ n }}</li> |
多维数组循环使用方法代替计算属性
1 | <ul v-for"set in sets"> |
v-for 和 v-if 搭配
不推荐二者在同一个元素使用,而且v-for
优先级比v-if
高.
但是如果想要部分渲染节点,不符合条件的不渲染可以用 v-if.
v-on
v-on
可以传事件名,接参数,也可以访问原始 DOM 事件.
1 | <button @click="greet"></button> |
v-model
在自定义事件上的 v-model 可以拆分为
1 | <input v-bind:value="search" v-on:input="search=$event.target.value" /> |
在组件上的v-model
可以拆分为
1 | <custom-input |
computed VS methods VS watch
点击 button,value
改变,watch 监听这个value
,变化后在 watch 中导致 watchValue 变化.
1 | <template> |
watch
这里 watch 的一个特点是,最初绑定的时候是不会执行的,要等到 firstName 改变时才执行监听计算。那我们想要一开始就让他最初绑定的时候就执行改怎么办呢?我们需要修改一下我们的 watch 写法,修改过后的 watch 代码如下:
1 |
|
注意到 handler 了吗,我们给 firstName 绑定了一个 handler 方法,之前我们写的 watch 方法其实默认写的就是这个 handler,Vue.js 会去处理这个逻辑,最终编译出来其实就是这个 handler。
而 immediate:true 代表如果在 wacth 里声明了 firstName 之后,就会立即先去执行里面的 handler 方法,如果为 false 就跟我们以前的效果一样,不会在绑定的时候就执行。
箭头函数不能在 watch 中写
1 | // 错误写法 |
箭头函数会改变 this 的指向,在 Vue 组件中,最好不要随便使用箭头函数。特别是 watch 以及生命周期中。!文档也有相应的提示
computed 和 watch 区别
计算属性是根据组件数据派生出新数据.使用方式是设置一个函数,返回计算后的结果.具备缓存性质,如果依赖项不变,是不会重新计算的.
监听器 watch 可以监听某个响应式数据的变化并执行副作用.使用方式是传递一个函数,并执行副作用.一般用于请求接口.
场景上:
计算属性一般是在模板中表达式过于复杂就可以写在计算属性中.
监听器是在状态变化后做一些额外的 DOM 操作或者异步请求.
动态组件
通过component
加is
属性.
1 | <component :is="currentTabComponent"></component> |
currentTabComponent
可以是已注册组件的名字或一个组件的选项对象.
is
的其他用处
比如在table
中,加入自定义组件会失效.
1 | //失效 |
另外,如果从以下条件使用模板,不存在限制.
字符串(例如: template:"..."
)
单文件组件(.vue
)<script type="text/x-template">
非 prop 的 Attribute
指传向一个组件,但是该组件并没有相应 prop 定义的 attribute.
禁用 Attribute 继承
如果不希望组件的根元素继承 attribute,可以设置inheritAttrs: false
.
禁用继承和$attrs
可以手动决定这些attribute
会被赋予那个元素.
1 | <template> |
注意
inheritAttrs: false
选项不会影响style
和class
的绑定.
这个模式允许使用基础组件的时候更像是使用原始的 HTML 元素,而不会担心哪个元素是真正的根元素.
1 | <base-input |
$listeners
Vue 提供的一个属性,是个对象.包含了作用在这个组件上的所有监听器.
在一个组件的根元素上无法监听到原生事件时可以使用.
1 | <template> |
.sync
修饰符
.sync
相当于update:myPropsName
的模式.
比如更新 title
1 | this.$emit('update:title', newTitle) |
注意带有
.sync
修饰符的v-bind
不能和表达式一起使用(例如v-bind:title.sync="doc.title + '!'"
是无效的).取而代之的是只能使用想要绑定的 property 名,类似v-model
.
多个 prop
1 | <text-document v-bind:sync="doc"></text-document> |
插槽
规则
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的.
v-slot 只能添加在
<template>
上
作用域插槽
如果想在插槽中访问到子组件的数据,就得用作用域插槽.
1 | //把要访问的数据绑定到slot上 //子组件 |
解构插槽 Prop
1 | //上面示例中,父级的slotProps可以解构 |
动态插槽
1 | <base-layout> |
动态组件&异步组件
动态组件上使用 keep-alive
is 动态切换时会重新渲染,如果不需要重新渲染可以用keep-alive
缓存下来.
1 | <keep-alive> |
异步组件
就是异步导入,返回 Promise
1 | //标准写法 |
加载状态
1 | const AsyncComponent = () => ({ |
处理边界情况
也就是操作其他组件内部或者手动操作 DOM 的情况.
根实例
1 | this.$root.foo; |
父组件实例
this.$parent.xxx
子组件实例或子元素
1 | <base-input ref="usernameInput"></base-input> |
$refs
只会在组件渲染完成之后生效.并且不是响应式的.仅作为一个操作子组件的应急方案.所以应该避免在模板或计算属性中访问$refs
.
依赖注入
如果层级过多,使用$parents
不方便.就要使用依赖注入,即provide
和inject
.
1 | //父级组件中声明一个要给子组件的参数,放到provide中 |
可以把依赖注入看做一部分”大范围有效的 prop”.
程序化的事件监听器
$on(eventName, eventHandler)
侦听一个事件$once(eventName, eventHandler)
一次性侦听一个事件$off(eventName, eventHandler)
停止侦听一个事件
用法示例:
1 | //声明周期结束前销毁方法 |
循环利用
递归组件
组件是可以在他们自己的模板中调用自身的.不过只能通过name
来做这件事.为了避免无限循环,确保递归调用是条件性的(例如使用一个最终会得到false
的v-if
.
组件之间的循环引用
当首先组件 A 依赖组件 B,,然后 B 又依赖 A,如此往复.形成了循环.那么需要做一个点,A 需要 B,但不需要先解析 B.
1 | //tree-folder |
处理方法
1 | //在beforeCreate时注册它 |
或者在本地注册时,使用异步导入,
1 | components: { |
mixin
递归合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行”合并”.
数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先.
1 | var mixin = { |
同名钩子函数合并为一个数组,混入对象的钩子在组件自身的钩子之前调用.
如果created
和mixin
中有同名方法,两个生命周期都会执行,执行的方法按照页面的方法执行.
值为对象的选项,如methods
,components
和directives
,将被合并为同一个对象.两个对象键名冲突时,取组件对象的键值对.
全局混入
1 | Vue.mixin({ |
自定义指令
钩子函数
一个指令定义对象可以提供以下钩子函数(均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用.在这里可以进行一次性的初始化设置.inserted
被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)update
所在组件的 VNode 更新时调用.但是可能发生在其子 VNode 更新之前.指令的值可能发生了改变,也可能没有.componentUpdated
指令所在组件的 VNode 及其子 VNode 全部更新后调用.unbind
只调用一次,指令与元素解绑时调用.
钩子函数参数
el
指令所绑定的元素,可以用来直接操作 DOMbinding
一个对象.包含以下 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
钩子可用.除了
el
之外,其他参数都应该是只读的,切勿进行修改.如果需要在钩子之间共享数据,建议通过元素的dataset
来进行.
动态指令参数
用法示例
创建一个自定义指令,用来通过固定布局将元素固定在页面上.
1 | <div id="baseexample"> |
1 | Vue.directive("pin", { |
这会把该元素固定到距顶部 200px 位置.如果需要固定在左侧,可以使用动态参数实现.
1 | <div id="example"> |
对象字面量
1 | <div v-demo="{ color:'white', text: 'hello!' }"></div> |
渲染函数 & JSX
通过 render 函数去解决重复而冗长的代码.