vue2
应用
v-model和.sync
v-model
html
<input v-model="searchText">
相当于
<input v-bind:value="searchText" v-on:input="searchText = $event">
组件绑定 v-model
html
<custom-input v-model="searchText"></custom-input>
相当于
<custom-input v-bind:value="searchText" v-on:input="searchText = $event"></custom-input>
js
Vue.component("custom-input", {
// 默认
// model: {
// prop: "value",
// event: "input",
// },
props: ["value"],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`,
});
组件绑定 type="checkbox"
html
<base-checkbox v-model="lovingVue"></base-checkbox>;
相当于
<base-checkbox v-bind:checked="lovingVue" v-on:change="lovingVue = $event"></base-checkbox>;
js
Vue.component("base-checkbox", {
model: {
//重新定义了v-model
prop: "checked",
event: "change",
},
props: {
checked: Boolean,
//来自v-bind:checked="lovingVue"
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`,
});
.sync 修饰符
- 是在 v-model 基础上的升级:
- v-model 为$emit('input', $event.target.value)
- async 为 this.$emit('update:title',value)
html
<text-document v-bind:title.sync="doc.title"> </text-document>
相当于
<text-document :title.sync="doc.title"> </text-document>
相当于
<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event"></text-document>
js
this.$emit("update:title", value);
event
.native
- 1、在一个组件的根元素上直接监听一个原生事件,使用.native修饰符
vue
<!-- 父组件 -->
<base-input v-on:focus.native="onFocus"></base-input>
- 2、focus事件绑定到了label元素上
html
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
- 3、focus不会触发,如果想要focus生效,可以将focus事件绑定到input上
js
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
- 4、如果绑定的是click.native/keyup.native事件,则会触发,对 <input>click和keyup事件,会冒泡到<label>上
vue
<base-input v-on:keyup.native="onKeyup" v-on:click.native="onKeyup"></base-input>
模板
watch
js
var vm = new Vue({
data: {
a: 1,
b: 2,
c: 3,
d: 4,
e: {
f: {
g: 5
}
}
},
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// 方法名
b: 'someMethod',
// 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
// 该回调将会在侦听开始之后被立即调用
d: {
handler: 'someMethod',
immediate: true
},
// 你可以传入回调数组,它们会被逐一调用
e: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
// watch vm.e.f's value: {g: 5}
'e.f': function (val, oldVal) { /* ... */ }
}
})
vm.a = 2 // => new: 2, old: 1
渲染函数 & JSX
js
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML attribute
attrs: {
id: 'foo'
},
// 组件 prop
props: {
myProp: 'bar'
},
// DOM property
domProps: {
innerHTML: 'baz'
},
// 事件监听器在 `on` 内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
on: {
click: this.clickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层 property
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
}
自定义指令
js
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
html
<input v-focus>
钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 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 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
样例
html
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
js
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
name: "demo"
value: "hello!"
expression: "message"
argument: "foo"
modifiers: {"a":true,"b":true}
vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder
value: "hello!"
expression: "message"
argument: "foo"
modifiers: {"a":true,"b":true}
vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder
指令 以及 属性
v-show和v-if &I
- 对比
- v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
- v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
- 相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
- 场景
- 如果需要频繁切换,则使用 v-show 较好;
- 如果在运行时绑定条件很少改变,则 v-if 会更合适。
data &I
- data 是函数
- 保证每个组件实例都有自己独立的数据副本(组件复用)
- 如果 data 是一个对象,那么所有组件实例将会共享同一个数据对象,这会导致状态污染和不可预测的行为(避免数据共享)
nextTick &I
- 核心原理:异步更新队列
- 数据变更: 当你修改 Vue 组件中的响应式数据时,Vue 会侦听到这些变化。
- 更新队列: Vue 不会立即更新 DOM,而是将这些更新放入一个队列中。
- 批量更新: 在同一个事件循环中,无论你修改了多少次数据,Vue 只会执行一次 DOM 更新。它会将队列中的所有更新合并,然后一次性应用到 DOM 上。
- nextTick: nextTick 允许你等待这个队列中的所有更新完成后再执行你的代码。
- nextTick 的作用:
- 访问更新后的 DOM: 由于 Vue 的异步更新机制,如果你在数据修改后立即访问 DOM,可能无法获取到最新的 DOM 状态 。nextTick 确保你的代码在 DOM 更新完成后执行,从而可以访问到最新的 DOM 状态 。
- 等待所有组件更新: nextTick 确保不仅当前组件的 DOM 更新完成,而且所有子组件的 DOM 也都更新完成。 nextTick 的实现:
- Vue 的 nextTick 内部使用了以下策略,按照优先级顺序:
- Promise.then: 如果浏览器支持 Promise,则使用 Promise.then。
- MutationObserver: 如果浏览器支持 MutationObserver,则使用 MutationObserver。MutationObserver 可以在 DOM 发生变化时异步执行回调函数。
- setTimeout: 如果以上都不支持,则使用 setTimeout(callback, 0)。
- 这些策略的共同目标是:将回调函数放入事件循环的下一个 "tick" 中执行,确保在 DOM 更新完成后执行。
vue-router
模式
- 模式
- createWebHashHistory
- createWebHistory
- createMemoryHistory
- 区别
- hash - 使用 url hash 变化记录路由地址
- history - 使用 H5 history API 来改 url 记录路由地址
- abstract - 不修改 url ,路由地址在内存中,但页面刷新会重新回到首页。
hash
- hash 的特点
- 会触发页面跳转,可使用浏览器的“后退” “前进”
- 但不会刷新页面,支持 SPA 必须的特性
- hash 不会被提交到 server 端(因此刷新页面也会命中当前页面,让前端根据 hash 处理路由)
- url 中的 hash ,是不会发送给 server 端的。前端
onhashchange
拿到自行处理。
js// 页面初次加载,获取 hash document.addEventListener('DOMContentLoaded', () => { console.log('hash', location.hash) }) // hash 变化,包括: // a. JS 修改 url // b. 手动修改 url 的 hash // c. 浏览器前进、后退 window.onhashchange = (event) => { console.log('old url', event.oldURL) console.log('new url', event.newURL) console.log('hash', location.hash) }
js// http://127.0.0.1:8881/hash.html?a=100&b=20#/aaa/bbb location.protocol // 'http:' location.hostname // '127.0.0.1' location.host // '127.0.0.1:8881' location.port // '8881' location.pathname // '/hash.html' location.search // '?a=100&b=20' location.hash // '#/aaa/bbb'
H5 history API
- 调用
history.pushState
window.onpopstate
- 问题:
- 由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id,就会得到一个 404 错误。这就尴尬了。
- 要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面(首页)。
- 参考
- 分析
- 按照 url 规范,不同的 url 对应不同的资源,例如:
https://github.com/
server 返回首页https://github.com/username/
server 返回用户页https://github.com/username/project1/
server 返回项目页
- 但是用了 SPA 的前端路由,就改变了这一规则,假如 github 用了的话:
https://github.com/
server 返回首页https://github.com/username/
server 返回首页,前端路由跳转到用户页https://github.com/username/project1/
server 返回首页,前端路由跳转到项目页
- 所以,从开发者的实现角度来看,前端路由是一个违反规则的形式。
- 但是从不关心后端,只关心前端页面的用户,或者浏览器来看,更喜欢
pushState
这种方式。
- 按照 url 规范,不同的 url 对应不同的资源,例如:
钩子函数
activated/mounted 触发
触发四种情况:
- this.$router.push
- 如果组件不存在,调用activated 和 mounted
- 如果组件存在,调用activated
- router-link(组件存在)
- 调用activated
- this.$router.replace(在当前路由,替换为其他路由,再替换回来,组件不存在)
- 调用mounted
- 页面全量刷新
- 调用mounted 总结:(只要涉及到这个页面)
- 缓存的:keep-alive、已加载的:activated
- 第一次加载:mounted