<<玩转 vue3>>笔记(1)
前言
学习前端势在必行, 这里是一个初学者(只写过 JS+BootStrap)学习 vue3 的学习笔记, 课程在 玩转 Vue 3 全家桶 (geekbang.org)
为什么是 vue3
前端发展历程
- 纯静态网页: 纯静态, 手动更新页面
- 模板语言网页: 直接嵌入后端数据, 数据更新需要重新刷新页面
- ajax: 前端异步获取数据并动态刷新
- jquery+bootStrap: 通过 jquery 操作 dom, bootStrap 做基本的响应式和栅格处理
- angularJs+nodeJs: MVVM 模式, 前端可以入侵到后端,
数据驱动页面, 数据变化则页面自动变化, 而不需要操作 dom, 开发者只需要关注数据的变化, 对 dom 的修改由框架完成 - 百花齐放: angularJs/vue/react.....
三大框架
- angularJs
- vue
- react
实现原理
- angular : 脏检查, 每次交互都检查数据是否变化, 从而更新 dom
- vue: 响应式, 对于每个需要变化的数据都建立一个 watcher 监听数据的属性, 有变化时才通知修改对应的 dom
- react: 虚拟 dom, 通过 js 来生成虚拟的 dom, diff 检测数据更新直接更改虚拟 dom, 更快速
vue 和 angular 区别
- angular 通过 diff 来自己进行数据变化的感知, vue 框架本身在数据变化时会主动通知
vue 和 react 区别
vue为了实现数据感知, 需要在框架内生成若干 watcher, 数量多就影响性能.
react 生成虚拟 dom, 每次需要对虚拟 dom 进行 diff 来得知数据变化, 当虚拟 dom 很大则影响性能
- react 使用将虚拟 dom 分片的方式将 dom 切片, 在浏览器空闲时再进行 diff, 每次计算一片, 当浏览器需要计算时下次让给浏览器, 解决卡顿
- vue2 引入了虚拟 dom, 取 react 之长, 对于组件之间的变化, 才通过 watcher 通知, 对于组件内的变化, 通过虚拟 dom 来更新, 组件数量不会很多, 解决了 watcher 多导致的性能问题, 同时, 每个组件都单独的虚拟 dom, 也避免虚拟 dom 大导致的性能问题
- react 将 jsx 编译成 js 执行, 所以语法都是 js 本身的语法和特征
- vue 是自己的语法, vue 通过语法检测数据是否是需要监听的, 在 vue3中做到了极致, vue3对代码进行精准的分类, 只有需要监听的数据更改才进行虚拟 dom 的修改, 不需要监听的数据, 越过了虚拟 dom 检测, 速度更快
小知识
Q: vue 在引入虚拟 dom 后, 需不需要 react 的分片来提高性能?
A: 不需要, vue 的虚拟 dom 是组件级别, 所以虚拟 dom 不大, 进行 diff 不会有性能问题
清单应用
编写一个清单应用, 类似于各种 TODO 程序, 用户在输入框中写要做的事情, 回车会加入到下面的若干 TODO 列表中, 同时列表某一条可以点击完成, 呈现出不同的效果
思想的转变
接触过传统的 JS+BootStrop 的开发者来讲, 可能对于这种需求, 思路是找到输入框并进行监听, 当用户输入后, 先在 dom 中找到对应元素, 然后进行修改dom, 这种思路需要转变
对于 MVVM 方式的框架来讲, 我们只需要关注数据是怎么变化, 而不是 dom 怎么操作
列表的加载
我们先进行数据的展示, 之前说过, 我们只关注数据, 在 vue 中, 我们可以在 dom 中使用 {{}} 来进行已定义的数据展示, 使用 v-for可以循环列表类型的数据
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<ul>
<!-- v-for 循环 todos, 每个值叫 todo, li 的值为每个 todo -->
<li v-for="todo in todos"> {{todo}} </li>
</ul>
</div>
</body>
<script>
// 建立变量 App
const App = {
// data() 返回监听的变量
data() {
return {
todos: ["吃饭", "睡觉"],
}
},
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
</html>
数据的追加
MVVM 中, 数据的修改, 框架会自己监听并重新渲染 dom
使用@keydown.enter监听键盘回车事件, 执行方法addTodo
在这里, 你会发现, 当数据 todos 进行变化后, 相对应的 dom 会自己发生变化, 这就是 MVVM 的奇妙之处, 数据驱动页面
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 监听变量 title 的值-->
<h2>{{title}}</h2>
<!-- v-model 将 input 值绑定到变量 title -->
<!-- @keydown.enter 在回车键时触发方法 addTodo -->
<input type="text" v-model="title" @keydown.enter="addTodo">
<ul>
<!-- v-for 循环 todos, 每个值叫 todo, li 的值为每个 todo -->
<li v-for="todo in todos"> {{todo}} </li>
</ul>
</div>
</body>
<script>
// 建立变量 App
const App = {
// data() 返回监听的变量
data() {
return {
title: "",
todos: ["吃饭", "睡觉"],
}
},
// methods 设置方法
methods: {
// 设置 addTodo 方法
addTodo() {
// this 指的是自己, 也就是 App
// 如果 title 为空, 不添加, 目的是防止无输入直接回车
if (this.title === ""){
return
}
this.todos.push(this.title) // 给 todos 列表新增一个值为用户输入的 title 值
this.title = "" // 将 title 设置为空
}
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
</html>
添加完成按钮和效果
TODO 都是可以进行选中完成的, 我们也要实现, 为了给每个 todo 添加是否完成的标识, 我们将 todo 的类型从字符串变成对象
还是那句话, 数据驱动页面, 当我们的 checkbox 的 checked 属性发生变化时, 也会重新渲染对应的 dom, 导致 class 也动态的增减
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 监听变量 title 的值-->
<h2>{{title}}</h2>
<!-- v-model 将 input 值绑定到变量 title -->
<!-- @keydown.enter 在回车键时触发方法 addTodo -->
<input type="text" v-model="title" @keydown.enter="addTodo">
<ul>
<!-- v-for 循环 todos, 每个值叫 todo, li 的值为每个 todo -->
<li v-for="todo in todos">
<!-- 当 type 为 checkbox 时, v-mode 绑定的变量如果是数组时, 作为 checkbox 的 value 使用, 当为 bool 时, 作为 checkbox 的 checked 值使用 -->
<!-- 使用 todo.done 属性标识是否选中 -->
<input type="checkbox" v-model="todo.done">
<!-- vue 中, : 用来传递数据, 这里的意思是, 如果 todo.done 为真, 则将 css done 传递给 class -->
<span :class="{done:todo.done}">{{todo.text}}</span>
</li>
</ul>
</div>
</body>
<script>
// 建立变量 App
const App = {
// data() 返回监听的变量
data() {
return {
title: "",
todos: [
{ done: false, text: "吃饭" },
{ done: true, text: "睡觉" }
],
}
},
// methods 设置方法
methods: {
// 设置 addTodo 方法
addTodo() {
// this 指的是自己, 也就是 App
// 如果 title 为空, 不添加, 目的是防止无输入直接回车
if (this.title === "") {
return
}
this.todos.push({
done: false,
text: this.title
}) // 给 todos 列表新增一个值为用户输入的 title 值
this.title = "" // 将 title 设置为空
}
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
<style>
/* 颜色变灰, 中间加划线 */
.done {
color: gray;
text-decoration: line-through;
}
</style>
</html>
添加统计
在页面添加未完成的数量和全部数量
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 监听变量 title 的值-->
<h2>{{title}}</h2>
<!-- v-model 将 input 值绑定到变量 title -->
<!-- @keydown.enter 在回车键时触发方法 addTodo -->
<input type="text" v-model="title" @keydown.enter="addTodo">
<ul>
<!-- v-for 循环 todos, 每个值叫 todo, li 的值为每个 todo -->
<li v-for="todo in todos">
<!-- 当 type 为 checkbox 时, v-mode 绑定的变量如果是数组时, 作为 checkbox 的 value 使用, 当为 bool 时, 作为 checkbox 的 checked 值使用 -->
<!-- 使用 todo.done 属性标识是否选中 -->
<input type="checkbox" v-model="todo.done">
<!-- vue 中, : 用来传递数据, 这里的意思是, 如果 todo.done 为真, 则将 css done 传递给 class -->
<span :class="{done:todo.done}">{{todo.text}}</span>
</li>
</ul>
<!-- 在 html 中写代码的方式虽然可行, 但是不推荐!, 并且性能不好! -->
<div>
<!-- .filter 对 todos 进行筛选, () 内是筛选规则, v 是每个 todo, 如果为 true 则此 v 筛选通过 -->
<!-- 这里统计未完成数量, 因此对 v.done 进行反转, 如果 done 为 true, 反转为 false, 筛选失败, 反之成功 -->
{{todos.filter(v=>!v.done).length}}
/
<!-- .length 计算列表长度 -->
{{todos.length}}
</div>
</div>
</body>
<script>
// 建立变量 App
const App = {
// data() 返回监听的变量
data() {
return {
title: "",
todos: [
{ done: false, text: "吃饭" },
{ done: true, text: "睡觉" }
],
}
},
// methods 设置方法
methods: {
// 设置 addTodo 方法
addTodo() {
// this 指的是自己, 也就是 App
// 如果 title 为空, 不添加, 目的是防止无输入直接回车
if (this.title === "") {
return
}
this.todos.push({
done: false,
text: this.title
}) // 给 todos 列表新增一个值为用户输入的 title 值
this.title = "" // 将 title 设置为空
}
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
<style>
/* 颜色变灰, 中间加划线 */
.done {
color: gray;
text-decoration: line-through;
}
</style>
</html>
如同注释所说, 虽然在html 中编写代码的方式可以实现, 但是并不推荐, 有如下问题:
- 把 JS 代码放置进了 body 中, 不美观
- 使用 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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 监听变量 title 的值-->
<h2>{{title}}</h2>
<!-- v-model 将 input 值绑定到变量 title -->
<!-- @keydown.enter 在回车键时触发方法 addTodo -->
<input type="text" v-model="title" @keydown.enter="addTodo">
<ul>
<!-- v-for 循环 todos, 每个值叫 todo, li 的值为每个 todo -->
<li v-for="todo in todos">
<!-- 当 type 为 checkbox 时, v-mode 绑定的变量如果是数组时, 作为 checkbox 的 value 使用, 当为 bool 时, 作为 checkbox 的 checked 值使用 -->
<!-- 使用 todo.done 属性标识是否选中 -->
<input type="checkbox" v-model="todo.done">
<!-- vue 中, : 用来传递数据, 这里的意思是, 如果 todo.done 为真, 则将 css done 传递给 class -->
<span :class="{done:todo.done}">{{todo.text}}</span>
</li>
</ul>
<div>
<!-- 直接调用计算属性 -->
{{active}}/{{all}}
</div>
</div>
</body>
<script>
// 建立变量 App
const App = {
// data() 返回监听的变量
data() {
return {
title: "",
todos: [
{ done: false, text: "吃饭" },
{ done: true, text: "睡觉" }
],
}
},
// methods 设置方法
methods: {
// 设置 addTodo 方法
addTodo() {
// this 指的是自己, 也就是 App
// 如果 title 为空, 不添加, 目的是防止无输入直接回车
if (this.title === "") {
return
}
this.todos.push({
done: false,
text: this.title
}) // 给 todos 列表新增一个值为用户输入的 title 值
this.title = "" // 将 title 设置为空
}
},
// computed 设置 vue 的计算属性
computed: {
// active 属性, 返回 done 为 false 的长度
active() {
return this.todos.filter(v => !v.done).length
},
// all 属性, 返回总长度
all() {
return this.todos.length
}
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
<style>
/* 颜色变灰, 中间加划线 */
.done {
color: gray;
text-decoration: line-through;
}
</style>
</html>
全选
我们需要加入一个全选框, 当点击全选时, 所有todo 都选中, 并且, 当手动将所有 todo 选中时, 全选框默认变为勾选状态
计算属性, 不止可以用来做数据统计, 也可以修改对应的数据, 例如
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 监听变量 title 的值-->
<h2>{{title}}</h2>
<!-- v-model 将 input 值绑定到变量 title -->
<!-- @keydown.enter 在回车键时触发方法 addTodo -->
<input type="text" v-model="title" @keydown.enter="addTodo">
<div>
<!-- checkbox 绑定 allDone 计算属性, 生成时调用 get 方法, 返回 bool, 作为 checkbox 的 checked 使用 -->
<!-- 当点击 checkbox 时, 计算属性发生变化, 将 checked 作为参数调用 allDone 的 set 方法 -->
全选<input type="checkbox" v-model="allDone">
<span>{{active}}/{{all}}</span>
</div>
<ul>
<!-- v-for 循环 todos, 每个值叫 todo, li 的值为每个 todo -->
<li v-for="todo in todos">
<!-- 当 type 为 checkbox 时, v-mode 绑定的变量如果是数组时, 作为 checkbox 的 value 使用, 当为 bool 时, 作为 checkbox 的 checked 值使用 -->
<!-- 使用 todo.done 属性标识是否选中 -->
<input type="checkbox" v-model="todo.done">
<!-- vue 中, : 用来传递数据, 这里的意思是, 如果 todo.done 为真, 则将 css done 传递给 class -->
<span :class="{done:todo.done}">{{todo.text}}</span>
</li>
</ul>
<div>
<!-- 直接调用计算属性 -->
{{active}}/{{all}}
</div>
</div>
</body>
<script>
// 建立变量 App
const App = {
// data() 返回监听的变量
data() {
return {
title: "",
todos: [
{ done: false, text: "吃饭" },
{ done: true, text: "睡觉" }
],
}
},
// methods 设置方法
methods: {
// 设置 addTodo 方法
addTodo() {
// this 指的是自己, 也就是 App
// 如果 title 为空, 不添加, 目的是防止无输入直接回车
if (this.title === "") {
return
}
this.todos.push({
done: false,
text: this.title
}) // 给 todos 列表新增一个值为用户输入的 title 值
this.title = "" // 将 title 设置为空
}
},
// computed 设置 vue 的计算属性
computed: {
// active 属性, 返回 done 为 false 的长度
active() {
return this.todos.filter(v => !v.done).length
},
// all 属性, 返回总长度
all() {
return this.todos.length
},
// 计算属性为对象时, 可以通过定义 get 和 set 方法来修改属性
allDone: {
// get 在获取计算属性时触发
get() {
// 调用 this.active 计算属性, active 返回的是 done 为 false 的长度, 这里与0进行比较, 为0代表全部勾选, 返回 true
return this.active === 0
},
// set 在计算属性发生变化时触发
set(val) {
console.log(val)
// forEach 是循环
// 循环 todos , 将每个 done 设置为 val
this.todos.forEach(todo => {
todo.done = val
});
}
}
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
<style>
/* 颜色变灰, 中间加划线 */
.done {
color: gray;
text-decoration: line-through;
}
</style>
</html>
清空按钮
快完成了, 我们添加一个清空按钮在输入框之后, 要求点击清空则将所有已完成条目删除, 而当没有已完成条目时, 按钮不展示
使用 v-if 来进行条件筛选渲染
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 监听变量 title 的值-->
<h2>{{title}}</h2>
<!-- v-model 将 input 值绑定到变量 title -->
<!-- @keydown.enter 在回车键时触发方法 addTodo -->
<input type="text" v-model="title" @keydown.enter="addTodo">
<!-- v-if 作为条件筛选, 当值为 true 才显示该 dom -->
<!-- @click 在鼠标点击时触发, 这里调用 clear 方法 -->
<button v-if="active<all" @click="clear">清理</button>
<div>
<!-- checkbox 绑定 allDone 计算属性, 生成时调用 get 方法, 返回 bool, 作为 checkbox 的 checked 使用 -->
<!-- 当点击 checkbox 时, 计算属性发生变化, 将 checked 作为参数调用 allDone 的 set 方法 -->
全选<input type="checkbox" v-model="allDone">
<span>{{active}}/{{all}}</span>
</div>
<ul>
<!-- v-for 循环 todos, 每个值叫 todo, li 的值为每个 todo -->
<li v-for="todo in todos">
<!-- 当 type 为 checkbox 时, v-mode 绑定的变量如果是数组时, 作为 checkbox 的 value 使用, 当为 bool 时, 作为 checkbox 的 checked 值使用 -->
<!-- 使用 todo.done 属性标识是否选中 -->
<input type="checkbox" v-model="todo.done">
<!-- vue 中, : 用来传递数据, 这里的意思是, 如果 todo.done 为真, 则将 css done 传递给 class -->
<span :class="{done:todo.done}">{{todo.text}}</span>
</li>
</ul>
<div>
<!-- 直接调用计算属性 -->
{{active}}/{{all}}
</div>
</div>
</body>
<script>
// 建立变量 App
const App = {
// data() 返回监听的变量
data() {
return {
title: "",
todos: [
{ done: false, text: "吃饭" },
{ done: true, text: "睡觉" }
],
}
},
// methods 设置方法
methods: {
// 设置 addTodo 方法
addTodo() {
// this 指的是自己, 也就是 App
// 如果 title 为空, 不添加, 目的是防止无输入直接回车
if (this.title === "") {
return
}
this.todos.push({
done: false,
text: this.title
}) // 给 todos 列表新增一个值为用户输入的 title 值
this.title = "" // 将 title 设置为空
},
clear() {
// 将 todos 赋值为老的 todos 中 done 为 false 的部分
// 去除了已经完成的条目
this.todos = this.todos.filter(v => !v.done)
}
},
// computed 设置 vue 的计算属性
computed: {
// active 属性, 返回 done 为 false 的长度
active() {
return this.todos.filter(v => !v.done).length
},
// all 属性, 返回总长度
all() {
return this.todos.length
},
// 计算属性为对象时, 可以通过定义 get 和 set 方法来修改属性
allDone: {
// get 在获取计算属性时触发
get() {
// 调用 this.active 计算属性, active 返回的是 done 为 false 的长度, 这里与0进行比较, 为0代表全部勾选, 返回 true
return this.active === 0
},
// set 在计算属性发生变化时触发
set(val) {
console.log(val)
// forEach 是循环
// 循环 todos , 将每个 done 设置为 val
this.todos.forEach(todo => {
todo.done = val
});
}
}
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
<style>
/* 颜色变灰, 中间加划线 */
.done {
color: gray;
text-decoration: line-through;
}
</style>
</html>
添加空置提示
最后一步!, 我们为这个清单应用添加最后一个功能: 当清单为空时, 展示目前没有数据
我们这里搭配使用 v-if 与 v-else, 来做分支处理, 当然还可以搭配 v-else-if 针对多分支处理
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 监听变量 title 的值-->
<h2>{{title}}</h2>
<!-- v-model 将 input 值绑定到变量 title -->
<!-- @keydown.enter 在回车键时触发方法 addTodo -->
<input type="text" v-model="title" @keydown.enter="addTodo">
<!-- v-if 作为条件筛选, 当值为 true 才显示该 dom -->
<!-- @click 在鼠标点击时触发, 这里调用 clear 方法 -->
<button v-if="active<all" @click="clear">清理</button>
<div>
<!-- checkbox 绑定 allDone 计算属性, 生成时调用 get 方法, 返回 bool, 作为 checkbox 的 checked 使用 -->
<!-- 当点击 checkbox 时, 计算属性发生变化, 将 checked 作为参数调用 allDone 的 set 方法 -->
全选<input type="checkbox" v-model="allDone">
<span>{{active}}/{{all}}</span>
</div>
<!-- 如果长度不为0代表目前有数据, 展示数据 -->
<ul v-if="all!==0">
<!-- v-for 循环 todos, 每个值叫 todo, li 的值为每个 todo -->
<li v-for="todo in todos">
<!-- 当 type 为 checkbox 时, v-mode 绑定的变量如果是数组时, 作为 checkbox 的 value 使用, 当为 bool 时, 作为 checkbox 的 checked 值使用 -->
<!-- 使用 todo.done 属性标识是否选中 -->
<input type="checkbox" v-model="todo.done">
<!-- vue 中, : 用来传递数据, 这里的意思是, 如果 todo.done 为真, 则将 css done 传递给 class -->
<span :class="{done:todo.done}">{{todo.text}}</span>
</li>
</ul>
<!-- v-else 必须紧跟在 v-if 的 dom 之后, 当 if 不通过执行渲染 else 内容 -->
<div v-else>
暂无数据
</div>
<div>
<!-- 直接调用计算属性 -->
{{active}}/{{all}}
</div>
</div>
</body>
<script>
// 建立变量 App
const App = {
// data() 返回监听的变量
data() {
return {
title: "",
todos: [
{ done: false, text: "吃饭" },
{ done: true, text: "睡觉" }
],
}
},
// methods 设置方法
methods: {
// 设置 addTodo 方法
addTodo() {
// this 指的是自己, 也就是 App
// 如果 title 为空, 不添加, 目的是防止无输入直接回车
if (this.title === "") {
return
}
this.todos.push({
done: false,
text: this.title
}) // 给 todos 列表新增一个值为用户输入的 title 值
this.title = "" // 将 title 设置为空
},
clear() {
// 将 todos 赋值为老的 todos 中 done 为 false 的部分
// 去除了已经完成的条目
this.todos = this.todos.filter(v => !v.done)
}
},
// computed 设置 vue 的计算属性
computed: {
// active 属性, 返回 done 为 false 的长度
active() {
return this.todos.filter(v => !v.done).length
},
// all 属性, 返回总长度
all() {
return this.todos.length
},
// 计算属性为对象时, 可以通过定义 get 和 set 方法来修改属性
allDone: {
// get 在获取计算属性时触发
get() {
// 调用 this.active 计算属性, active 返回的是 done 为 false 的长度, 这里与0进行比较, 为0代表全部勾选, 返回 true
return this.active === 0
},
// set 在计算属性发生变化时触发
set(val) {
console.log(val)
// forEach 是循环
// 循环 todos , 将每个 done 设置为 val
this.todos.forEach(todo => {
todo.done = val
});
}
}
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
<style>
/* 颜色变灰, 中间加划线 */
.done {
color: gray;
text-decoration: line-through;
}
</style>
</html>
初探 vue3 新特性
相比于 vue2, vue3 的优势是什么?
vue2 的历史遗留问题
- vue2 使用
Flow.js作为类型校验, 但是Flow.js已经停止维护, 整个社区都在使用TypeScript来构建基础库, vue 也需要这样 - vue2 内部运行时, 直接执行浏览器的 API, 这样在跨端时, 就需要适配多端,的否则会出现问题
- vue2 并不是真正意义上的代理(响应式), 而是基于
Object.defineProperty()实现的, 这是对某个属性进行拦截, 有一些缺陷, 比如无法监听删除(vue 使用 $delete 辅助才能达到效果) - vue2 使用 OptionApi, 在代码比较多时, 对于功能的修改, 需要兼顾
data,methods代码块, 比较麻烦
vue3的新特性
RFC 机制
这与代码无关, 这是 vue 团队的开发工作方式, 对于新的功能和语法, 先放置在 github 征求意见, 任何人都可以讨论和尝试实现
响应式系统
之前说过, vue2使用Object.defineProperty()实现响应式, 而开发者必须将defineProperty监听的数据明确的写在代码中, 这是因为, defineProperty 对于不存在的属性无法拦截, 因此必须在data中声明监听变量
而 vue3 可以使用 proxy , 他的代码类似于
new Proxy(obj, {
get(){},
set(){}
})
proxy对 obj 是什么属性不做关心, 统一拦截, 还可以监听更多的格式数据, 例如Set/Map等
需要注意的是, Proxy不支持 IE11 以下的浏览器
Proxy是浏览器的新特性, 这代表着, 框架随着更新会和浏览器相辅相成, 一起为前端提供更多可能.
自定义渲染
vue2 内部的模块都是揉和在一起的, 导致扩展比较困难, 而 vue3 使用流行的 monorepo 的拆包管理方式, 将模块剥离, 进行解耦.
全部模块使用 TS 进行重构
TS(TypeScript)带来了系统类型, 能让代码的提示更为智能, 同时提高代码的健壮性
这里博主也推荐大家有空学习一下 TS, 类型注解可以让代码的编写更为得心应手.
并且 TypeScript 也是目前前端流行的技术, 很多框架都已经使用 TS 来进行底层的编写
Compostion API
Composition API 作者将他称之为组合 API, 从 DEMO 来了解他们的不同
比如我们使用 vue2 来编写一个累加器, 并且有一个计算属性显示累加器 x2的结果
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 点击触发 add 函数 count*2, double 计算*2结果 -->
<h1 @click="add">{{count}} x 2 = {{double}}</h1>
</div>
</body>
<script>
// 建立变量 App
const App = {
data(){
return{
count: 1
}
},
methods: {
add(){
this.count += 1
}
},
computed:{
double(){
return this.count * 2
}
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
</html>
而在 vue3 里, 我们可以使用 setup 来写
<!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>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<!-- 点击触发 add 函数 count*2, double 计算*2结果 -->
<h1 @click="add">{{state.count}} x 2 = {{double}}</h1>
</div>
</body>
<script>
// 导入所需的模块
const {reactive, computed} = Vue
// 建立变量 App
let App = {
// setup 这里返回 App 的一些方法和变量等
setup(){
// 新建 state 对象
const state = reactive({
count: 1 // count 属性
})
// 新建 add 方法
function add(){
// count 属性+1
state.count += 1
}
// 创建 double 的计算属性
const double = computed(()=>state.count * 2)
return {state, add, double} // 返回到外层
}
}
// 创建 App, 绑定到 id app
Vue.createApp(App).mount("#app")
</script>
</html>
使用新版的组合 api 之后, 表面看代码反而繁琐了, 但是之前的 OptionsAPI 有几个严重的问题:
- 所有的数据都挂载在 this 里, 因此对于类型推倒很不友好, 并且在清理和代码分块时很难受
- 新增功能都要修改 data, method 块, 维护困难
- 代码难以复用, 因为代码都糅杂在一起, 还可能会出现命名冲突
而使用组合 API 之后, 好处多多: - api 都是通过 import 引入, 不需要的模块无需引入
- 将某块功能所有的 methods/data 封装在一个独立的函数中, 复用很容易, 也没有冲突问题
- 组合 api 新增的 return 等语句, 在实际项目中可清除
新增组件
vue3新增了若干组件, 比如: - Fragment: 不再要求有一个唯一的根节点, 清理无用的 div 标签
- Teleport: 允许组件渲染在别的元素内, 在开发弹窗组件时特别有用
- Suspense: 异步请求组件
Vite
Vite 跟 vue3 并不是绑定关系, 和 vue 也不是强制绑定, Vite 的竞品是 Webpack, 主要提升开发的体验
传统的 webpack, 在打包时, 是将所有的代码和页面打包完成再启动, 可能需要几分钟, 而 Vite 是阶梯式打包, 按需加载, 这样大大提升了开发体验
vue2 如何升级到 vue3
原作者说了很多, 大致是使用一些工具来进行升级
但是我个人认为, 如果是已经完成或者开发中的 vue2 项目, 不建议升级到 vue3, 我做过一些项目, 我认为前端组件的版本升级带来的问题很多, 尤其是不兼容问题. 所以我不建议对已有的项目进行升级
而对于新的项目, 可以使用 vue3来从头开始
搭建 vue3 项目的第一步
环境准备
你必须首先安装以下软件:
- NodeJs
初始化代码
找到你的工作目录, 命令行输入
npm init @vitejs/app
初始化会让你输入项目的名字, 这里我们输入 student
➜ Vue npm init @vitejs/app
@vitejs/create-app is deprecated, use npm init vite instead
? Project name: › student
之后选择 vue 的项目
? Select a framework: › - Use arrow-keys. Return to submit.
vanilla
❯ vue
react
preact
lit
svelte
回车后再选择 vue(javaScript) 或者 vue-ts(typeScript语言), 这里我们先选择普通的 vue, 方便学习, 之后学习了 TS 之后, 可以选择 vue-ts
? Select a variant: › - Use arrow-keys. Return to submit.
❯ vue
vue-ts
之后就创建成功了项目
Scaffolding project in /Users/chenming/Work/Code/Vue/student...
Done. Now run:
cd student
npm install
npm run dev
而后在当前文件夹就生成了一个新的文件夹 student
我们使用 vscode 打开 student 目录
现在目录下就会生成若干文件
➜ student tree
.
├── README.md
├── index.html 入口文件
├── package.json
├── public 资源文件夹
│ └── favicon.ico
├── src 源码
│ ├── App.vue 组件
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js 入口
└── vite.config.js vite 配置
4 directories, 9 files
运行
此时脚手架就算搭建完成, 然后我们执行命令来安装依赖
npm install
执行完命令后, 会在当前路径下生成新的文件夹node_modules, 里面存放下载的依赖文件, 通常, 该文件夹不应上传到代码仓库, 而是由开发者本地生成
再执行命令, 在本地启动 dev 环境
npm run dev
> student@0.0.0 dev
> vite
vite v2.9.14 dev server running at:
> Local: http://localhost:3000/
> Network: use `--host` to expose
ready in 177ms.
而后浏览器打开网址 http://localhost:3000/, 会出现欢迎页面代表成功
同时, 我们修改文件 src/APP.vue 的内容, 会发现网页会同步刷新, 这样就给我们的开发提供了很大的便利
同时, 因为我们的开发是多页面, 同时要和后端进行交互, 因此我们还需要安装两个组件来帮助我们完成需求, 分别是vuex(管理数据)和vue-router(管理路由)
npm install vuex@next vue-router@next

浙公网安备 33010602011771号