最近换了东家,比较忙、比较累,但博客的更新速度不能明显下降。Fighting!
Vue不是框架(前端框架往往需要解决路由、试图管理、数据持久化等流程),Vue只关注视图层。
使用webpack构建Vue项目,在配置文件中会看到如下代码:
module.exports = {
// ...
resolve: {
alias: {
// 使用完整版构建,而非运行时构建版本
'vue$': 'vue/dist/vue.esm.js'
}
}
}
// 需要编译器
new Vue({
template: '<div>{{ hi }}</div>'
})
// 不需要编译器
new Vue({
render (h) {
return h('div', this.hi)
}
})
所以,项目中往往我们需要采用完整版,而非运行时构建版本!
压缩Vue
npm安装的Vue,并不是压缩后的版本。然而,CommonJS 和 ES Module 构建同时保留原始的 process.env.NODE_ENV
检测,以决定它们应该运行在什么模式下。可以通过控制该环境变量,对Vue进行压缩,以减少最终文件的大小。
var webpack = require('webpack')
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
})
]
}
<script>
标签用在浏览器中。Unpkg CDN 的 https://unpkg.com/vue 默认文件就是运行时 + 编译器的 UMD 构建 (vue.js
)。pkg.main
) 是只包含运行时的 CommonJS 构建 (vue.runtime.common.js
)。pkg.module
) 是只包含运行时的 ES Module 构建 (vue.runtime.esm.js
)。这也是为什么上述需要通过别名指定加载Vue的完整版本!View(Dom) ==> DomListener(Vue) ==> Model(Pojo) View(Dom) <== Directives(Vue) <== Model(Pojo)
每个指令都会观察一片数据,管理其相关的Dom操作;数据变化,Dom操作自行发生变化!所有的元素都是响应式的!
需要注意的是:在Vue中数据流是单向的
<input v-model="message">
实现所谓的双向,不过是<input v-bind:value='message' v-on:input="message = arguments[0]"
的语法糖。数据传递机制完全和子父组件通信方式一致,所以要让组件的 v-model
生效,子组件它应该:
value
属性input
事件 components: {
'child-component': {
props: ['value'],
template: `
<div>
<input v-bind:value="value"
@input="updateValue($event.target.value)">
</input>
</div>
`,
methods: {
updateValue(value) {
// 同样需要触发input事件,只是父组件中无需显示声明@inpt
this.$emit('input', value);
}
}
}
}
完整示例参考地址:https://jsfiddle.net/381510688/uyppvvL0/
Vue将遍历Data对象所有的属性,并使用 Object.defineProperty (ES5方法,Vue只支持IE9+d的原因) 把属性全部转为 getter/setter。
每个组件实例都有相应的watcher实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter
被调用时,会通知watcher
重新计算,从而致使它关联的组件得以更新。
Vue**异步**执行DOM更新,如果同一个watcher被多次触发,只会一次推入到队列中。Vue在内部尝试对异步队列使用原生的 Promise.then
和 MutationObserver
,如果执行环境不支持,会采用setTimeout(fn, 0)
代替。
Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触DOM,但是有时我们确实要这么做。为了在数据变化之后等待Vue完成更新DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)
或者vm.$nextTick()
,这样回调函数在DOM更新完成后就会调用。
需要注意的是,对于对象添加新成员,需要如下处理:
// 全局方法
Vue.set(vm.someObject, 'b', 2);
// 实例方法
this.$set(this.someObject,'b',2);
可以使用 Object.assign()
或 _.extend()
方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性(开发中会经常遇到):
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
数组:改变原数组的方法,如:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
可以检测变化;不改变原数组的方法,如filter()、concat()、slice()
不会自动变化。
// 用新数组替换旧数组(Vue做了相关处理,非常高效的操作)
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
对于直接修改数组某一项值,或者修改其长度,可以通过以下方式实现:
Vue.set(example1.items, indexOfItem, newValue)
example1.items.splice(newLength)
computed vs methods vs watch
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
对于任何复杂逻辑,你都应当使用计算属性。Vue 知道 vm.reversedMessage
依赖于 vm.message
,因此当 vm.message
发生改变时,所有依赖于 vm.reversedMessage
的绑定也会更新。计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter。
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
运行 vm.fullName = 'John Doe'
时,setter 会被调用,vm.firstName
和 vm.lastName
也相应地会被更新
注意:三者中都不能使用箭头函数,箭头函数绑定了父级作用域的上下文,所以this
将不会按照期望指向Vue实例!
<div id="app">
<input type="text" v-model="message">
<p>{{computedMsg}}</p>
<p>{{methodMsg()}}</p>
<p>{{watchMsg}}</p>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
message: '',
watchMsg: ''
}
},
computed: {
computedMsg() {
return this.message.toUpperCase();
}
},
methods: {
methodMsg() {
return this.message.toUpperCase();
}
},
watch: {
message() {
return this.watchMsg = this.message.toUpperCase();
}
}
});
</script>
示例地址:https://jsfiddle.net/381510688/trmvsnve/