初始vue
Vue (读音 /vjuː/,类似于 view), 又叫Vue.js或者Vuejs, 是一套用于构建用户界面的渐进式框架
- 构建用户界面 --- vue的核心职责是为了方便开发者快速便捷的开发前端界面
- 渐进式框架 --- 我们可以在项目中一点点来引入和使用Vue,而不一定需要全部使用Vue来开发整个 项目
vue3带来的特点
-
源码通过monorepo的形式来管理源代码:
-
将许多项目的代码存储在同一个repository中
-
这样做的目的是多个包本身相互独立,可以有自己的功能逻辑、单元测试等,同时又在同一个仓库下方便管理
-
这样模块划分的更加清晰,可维护性、可扩展性更强
-
-
源码使用TypeScript来进行重写
-
在Vue2.x的时候,Vue使用Flow来进行类型检测
-
在Vue3.x的时候,Vue的源码全部使用TypeScript来进行重构,提升了vue对于ts的支持性
-
-
使用Proxy进行数据劫持
-
Vue2是使用Object.defineProperty来劫持数据的getter和setter方法的,
-
这种方式一致存在一个缺陷就是当给对象添加或者删除属性时,是无法劫持和监听的
-
Vue使用Proxy来实现数据的劫持,完美的解决了上述的问题
-
-
删除了一些不必要的API和特性
-
对编译方面进行了优化
-
如生成Block Tree、Slot编译优化、diff算法优化
-
编译后的包体积更小,性能更优
-
-
Options API 转变为 Composition API
-
Options API包括data、props、methods、computed、生命周期等等这些选项,存在比较大的问题是相关联的代码中的多个逻辑可能是定义和实现在不同的选项中,内聚度较差
-
Composition API可以将 相关联的代码 放到同一处 进行处理,而不需要在多个Options之间寻找,同时提升了相关逻辑的可复用性,便于对应逻辑的抽离
-
-
Hooks函数增加代码的复用性
- 在Vue2.x的时候,我们通常通过mixins在多个组件之间共享逻辑, 但mixins也是由一大堆的Options组成的,并且多个mixins会存在命名冲突的问题
- 在Vue3.x中,我们可以通过Hook函数,来将一部分独立的逻辑抽取出去,并且它们还可以做到是响应式的
vue初体验
vue的本质,就是一个封装良好的JavaScript的库,所以我们使用vue的第一步是引入vue
常见的引入方式:
- 在页面中通过CDN的方式来引入
- 下载Vue的JavaScript文件,本地引入
- 通过npm包管理工具安装使用它
- 直接通过Vue CLI创建项目,并使用它
Hello Vue案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello Vue</title>
</head>
<body>
<!--
定义挂载点 --- vue解析出来的内容会被挂载到挂载点的下边
1. 不一定需要是div或id选择器,但是一般挂载点都是一个id为app的div元素
2. 如果挂载点中原本有内容的时候,在被vue挂载的时候,原本挂载点中的内容会被清除掉
(如果挂载的节点没有模板内容,那么挂载节点中的内容默认就不会被覆盖)
-->
<div id="app">会被清除覆盖的数据内容</div>
<!-- 从CDN引入 引入以后,全局会存在一个对象Vue -->
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
// Vue.createApp用于创建vue的实例对象的方法
// 参数为一个配置对象
// 返回值是一个vue的实例 -- 在这里就是app(根组件)
const app = Vue.createApp({
// 模板,也就是界面所需要渲染的内容
template: '<h2>Hello Vue</h2>'
})
// mount方法就是将vue实例和挂载点进行关联
// 让vue知道解析出来的内容需要被挂载到那里去
// 参数是一个字符串,vue内容会通过'document.querySelector'的方式去获取节点
app.mount('#app')
</script>
</body>
</html>
vue的特点
-
vue是
声明式编程在软件开发中,有着2个常见的编程范式(方式),即
命令式编程和声明式编程,传统的开发方式实际上是命令式编程,而vue的开发范式是声明式编程
命令式编程
<!--
每完成一个操作,都需要通过JavaScript编写一条代码,来给浏览器一个指令,
也就是关注的是 “how to do”的编程范式就是命令式编程
-->
<!-- view部分 -->
<h2 id="count"></h2>
<button id="increment">+1</button>
<button id="decrement">-1</button>
<!-- model和controller部分 -->
<script>
const countElem = document.getElementById('count')
const incrementElem = document.getElementById('increment')
const decrementElem = document.getElementById('decrement')
let count = 0
countElem.innerHTML = count
incrementElem.addEventListener('click', () => {
count += 1
countElem.innerHTML = count
})
decrementElem.addEventListener('click', () => {
count -= 1
countElem.innerHTML = count
})
</script>
命令式编程存在这一些弊端
-
我们需要频繁去获取和操作dom,处理dom的兼容性问题,不但麻烦而且损失性能
例如在上述案例中,对于dom的操作都是
countElem.innerHTML = count, 而这个dom操作被重复执行了3次 -
状态的定义和数据逻辑没有很好的区分,这会使代码看起来比较凌乱
声明式编程
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
// 在vue的编程中, 我们只是声明(安装规则预先定义)好的了相关的状态和UI界面
// vue会自动去进行绑定(关联,引用),并在状态发生改变的时候,自动去更新界面
// 至于如何更新的,我们并不需要关心,即从how to do -> what to do
// 这种编程范式就是声明式编程
Vue.createApp({
// vue2中模板有且必须有一个根元素
// vue3中模板中可以有多个根元素
template: `
<h2>{{ counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
`,
// 这里之所以需要一个函数,是为了保证每一个组件实例对象的data都是一个新的对象,不会存在引用相同的情况
data() {
return {
// 在data中定义的数据:
// 1. 可以在模板中进行使用
// 2. 会被纳入vue的响应式系统中
counter: 0
}
},
methods: {
increment() {
// vue在解析的时候,会将data对象转换为proxy对象,挂载到vue的实例上
// 所以可以直接通过this关键字来直接访问定义在data中的数据(状态)
// vue3中的this本质上就是data函数返回的那个对象的代理对象
this.counter += 1
},
decrement() {
this.counter -= 1
}
}
}).mount('#app')
</script>
目前流行的前端框架都是声明式编程:我们只要按照规则预先声明好对应的状态和模板,框架会自动将我们的状态和事件在模板的对应位置进行关联,并且在状态发生改变的时候,自动去更新我们的模板并重新渲染。至于如何绑定和更新,我们完全不需要关系。方便我们把精力完全放置在构建界面和处理业务逻辑上
MVVM架构
软件中,常见的架构(软件结构)有MVC(Model – View –Controller)和 MVVM(Model-View-ViewModel)
MVVM是MVC的改进,可以在状态发生改变的时候自动更新界面
- 模板就是MVVM中的View, createApp的参数,即传入的配置对象就是Model,而vue框架就是VueModel层
- Vue在MVVM中的作用:
将model层的数据在view中对应位置进行绑定和将view中事件和model中定义的响应函数进行绑定
template
- template的值是需要渲染的模板内容,template的值里面有很多的HTML标签,这些标签会替换掉我们挂载到的元素(比如id为app的div)的innerHTML
- template会最终交给vue去进行解析和处理,所以模板中有一些奇怪的语法(模板语法),比如 {{}},比如 @click
template抽离
如果将模板直接是字符串的形式写在createApp的配置选项中的时候,不仅view和model的分离不强,而且没有提示,所以需要对template进行抽离
使用script标签,并且标记它的类型为 x-template
<!-- 这么写代码虽然抽离了,但是没有语法高亮 -->
<script type="x-template" id="template">
<h2>Hello Vue</h2>
</script>
<script>
Vue.createApp({
// 如果template被抽离,那么此时必须使用的时候id选择器
// vue在解析的时候,只有当template的值以#开头的时候,
// vue会使用document.querySelector方法去获取对应元素
// 将元素的innerHTML值作为vue的模板来解析
template: '#template'
}).mount('#app')
</script>
使用任意标签(通常使用template标签,因为不会被浏览器渲染),设置id
template元素是一种用于保存客户端内容的机制,该内容再加载页面时不会被呈现,但随后可以在运行时被JS获取
<template id="template">
<!-- 此时存在语法高亮 -->
<h2>Hello Vue</h2>
</template>
<script>
Vue.createApp({
template: '#template'
}).mount('#app')
</script>
<!--
如果不使用template标签,vue的template也会被正常解析,对应的内容会被挂载到挂载点下,
但是因为别的标签是需要在界面上渲染的,所以此时界面上除了解析后渲染出的结果,解析前的模板内容也会显示在界面上
所以并不合理也不推荐
-->
<div id="template">
<!-- 此时存在语法高亮 -->
<h2>Hello Vue</h2>
</div>
<script>
Vue.createApp({
template: '#template'
}).mount('#app')
</script>
data
data属性是传入一个函数,并且该函数需要返回一个对象:
在Vue2.x的时候,也可以传入一个对象(虽然官方推荐是一个函数, 不传函数会有警告);
在Vue3.x的时候,必须传入一个函数,否则就会直接在浏览器中报错
data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理:
- 所以我们在template中通过 {{counter}} 访问counter,可以从对象中获取到数据
- 所以我们修改counter的值时,template中的 {{counter}}也会发生改变
methods
methods属性是一个对象,通常我们会在这个对象中定义很多的方法:
- 这些方法可以被绑定到 template 模板中
- 在方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性
因为箭头函数没有内部this,所以在methods中定义函数的时候,
不要使用箭头函数,以免this指向发生错误。
// vue3实现methods中this绑定的伪代码
const publicThis = instance.proxy // 所以vue3的methods中的this,实际上是vue3的data返回的那个对象的proxy对象
const ctx = instance.ctx // ctx上绑定了修正过this的函数,这些函数才是在对应事件被触发的时候真正被回调的函数
if (methods) {
for (const key in methods) {
const methodHandler = methods[key] // 取出对应函数体
if (isFunction(methodHandler)) {
// 修正this指向
ctx[key] = methodHandler.bind(publicThis)
}
}
}