Vue3于2020年09月18日正式发布,映入我视野的新东西除了Vue3以外,还有yarn和vite。他们两个本身的功能并不是新东西,yarn对标的是npm包管理工具,vite对标的webpack包打包工具。
与vue2不同的是,vue3工程中没有 build 文件, 相应的build/index.js的配置改由了项目根目录下vue.config.js承担
Vue3新特性
-
template标签下面可以有多个节点了,终于不用先写一个DIV了
-
setup函数可以代替之前的data,methods,computed,watch,Mounted等对象,但是props声明还是在外面。
相比于Vue采用的OPTIONS API,Vue3使用的为Composition-API
-
vue3.0允许一个组件有多个根节点
创建一个组件:通常,我们通过在最外层包裹一层 div 来创建一个组件,但这个div元素一般没有任何使用价值,就只是让模板符合单根需求。1
2
3
4
5
6<template>
<div> <!--只是来包装一下, 如果不加这一行会报错-->
<div>Node 1</div>
<div>Node 2</div>
</div>
</template>但在Vue3中支持多根即多个
<div>
、 甚至不写div也没问题1
2
3
4
5
6
7
8<template>
<div>
{{ msg1 }}
</div>
<div>
{{ msg2 }}
</div>
</template>原因是Vue.js 3.0 支持了 Fragments 的语法,具体原理没分析。
Composition-Api
setup() 函数
一组低侵入式的、函数式的 API,使得我们能够更灵活地「组合」组件的逻辑。
在学习 Composition-Api 之前,我们需要先了解一下 setup() 函数。 setup() 是 Vue3 中的新增内容。它为基于 Composition API 的新特性提供了统一的入口。
在Vue3中,定义 methods、watch、computed、data数据 等都放在了 setup() 函数中
-
执行时机
setup()函数会在created()生命周期之前执行。 -
参数
- setup() 函数的第一个参数是 props ,组件接收的 props 数据可以在 setup() 函数内访问到。
- context 是 setup() 的第二个参数,它是一个上下文对象,可以通过 context 来访问Vue的实例 this 。
注意:在 setup() 函数中访问不到Vue的 this 实例, 需要通过context来取
具体函数
数据
在Vue2.x的版本中,我们只需要在 data() 中定义一个数据就能将它变为响应式数据,在 Vue3.0 中,需要用 reactive 函数或者 ref 来创建响应式数据。
reactive()
reactive() 函数接收一个普通的对象,返回出一个响应式对象。
1 | // 在组件库中引入 reactive |
ref()
ref() 函数可以根据给定的值来创建一个响应式的数据对象,返回值是一个对象,且只包含一个 .value 属性。
1 | // 引入 ref |
ref 的注意事项
- 在 setup() 函数内,由 ref() 创建的响应式数据返回的是对象,所以需要用 .value 来访问;
- 而在 setup() 函数外部则不需要 .value ,直接访问即可。 ==>
<template>
中直接使用响应式数据: 响应式对象里面如果有ref包装的值类型。则Vue会实现自动拆箱 - 可以在 reactive 对象中访问 ref() 函数创建的响应式数据。
- 新的 ref() 会覆盖旧的 ref() 。
- 选择 reactive 还是 ref
- 如, new Date().getMonth()返回的是Number, 通过
const m = ref(new Date().getMonth())
可以直接拆包获得值,而不是使用reactive。但需要 - 注意的是,使用m的时候,应该使用
m.value
- 如, new Date().getMonth()返回的是Number, 通过
- reactive无需.value来获得值,而是直接通过
xx.prop
获取即可
Vue3 —— 创建响应式数据使用 reactive 还是 ref ?(本周更新)
computed()
computed() 用来创建计算属性,返回值是一个 ref() 实例。按照惯例,使用前需要先引入。
- computed创建只读计算属性
1 | const count = ref(1) |
- computed创建可读可写计算属性
1 |
|
readonly()
传入一个响应式对象、普通对象或 ref ,返回一个只读的对象代理。这个代理是深层次的,对象内部的数据也是只读的。
1 | const state = reactive({ count: 0 }) |
函数
以前是写在methods对象中,现在是声明函数后放回
侦听
watch()
composition-api 中的 watch 和 Vue2.x 中是一样的,watch 需要侦听数据,并执行它的侦听回调。默认情况下初次渲染不执行。
应用场景: 监听路由变化(路由变化的监听在Vue生命周期函数中实现都不合适,放watch中比较合适)
1 | // 侦听一个 getter |
watch 与 watchEffect 的不同
- watch 初次渲染不执行
- watch 侦听的更具体
- watch 可以访问侦听数据变化前后的值
from: Vue3 的新特性(二) —— Composition-Api
总结:
- Composition API 的入口在 setup() 函数中
- reactive 响应式对象
- ref 接收一个参数并返回响应式对象
- 原先在 Vue2 中的 methods,watch,component、data 均写在 setup() 函数,使用之前需要自行导入
- 回归了 function xxx 定义函数
注:
- OnMounted()、watch()、computed()中间传的都是匿名函数,而不是对象
- 在setup中取值不能使用this, this在composition中是undefine.
- 所以vue2代码用到vue3中时得把this删了(比如function), data的话还得加
.value
- 双向绑定的变量在setup中定义时要加ref,修改变量的值时要加value
Vue2->Vue3废弃的API
-
filters过滤器:由于
1
<p>{{ accountBalance | currencyUSD }}</p>
打破了大括号内的表达式“只是 JavaScript”的假设,这不仅有学习成本,而且有实现成本。==>在 3.x 中,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。
Vue optionalAPI可以使用
- this.$store.state
1 | computed: { |
- this.$refs
1 | this.$refs.emailRef.focus |
- this.$data
- this.$axios
附录
导入vuex和vue-router
vue-router使用
yarn add vue-router@next --dev
, 参数说明packageName@tag
orpackageName@version
创建router/index.js
1 | import { createRouter, createWebHistory } from "vue-router"; |
- app下进行router跳转
1 | <template> |
router-link 实现路由之间的跳转,如点击show超链接则会跳转到
http://localhost:8080/show
1
2
3
4
5
6
7
8
9
10
11
12 > <!-- 字符串 -->
> <router-link to="home">Home</router-link>
> <!-- 渲染结果 -->
> <a href="home">Home</a>
>
> <!-- 同上 -->
> <router-link :to="{ path: 'home' }">Home</router-link>
> <!-- 命名的路由 -->
> <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
> <!-- 带查询参数,下面的结果为 /register?plan=private -->
> <router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>
>
视图:router-view 当路由的path 与访问的地址相符时,会将指定的组件内容替换该 router-view,如果不点击而是直接将URL换成
http://localhost:8080/show
也会显示ShowList.vue的内容(为什么是ShowList.vue?是因为router/index.js中将path=''/show'
与ShowList.vue绑定了)
- 子vue中点击按钮后进行router跳转
1 | // vue2写法 |
编程式调用方式:
1 | // 字符串 |
更多
$router
: 是路由操作对象,只写对象$route
: 路由信息对象,只读对象
1 | //操作 路由跳转 |
query传参要用path来引入,params传参要用name来引入
1 | // path+query==> /second?queryId=2222&queryName=query |
-
params是路由的一部分, 必须要在路由后面添加参数名。query是拼接在url后面的参数,没有也没关系。
-
params一旦设置在路由,params就是路由的一部分,如果这个路由有params传参,但是在跳转的时候没有传这个参数,会导致跳转失败或者页面会没有内容。(最重要的一点,params刷新会消失。query则不会)
1
2parmas:https://blog.csdn.net/xxx
query: https://blog.csdn.net?xxxfrom:https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html
采坑:
1
2:to="{path:'invest/scattered',query: {productId: list.id}}"
to="invest/scattered?productId=1" // 如果不加冒号访问动态参数即to="{path:'invest/scattered',query: {productId: list.id}}",则会乱码冒号可以传动态的参数(也可以传静态的);不加冒号是静态的路径;动态路由也可以以静态路由的形式写,参数固定如
:to="{name: 'antd', params: { id: 2 } }"
==>to="/antd/2"
;参数为变量:1
2<router-link :to="{name: 'antd', params: { id: 2 }}">antd</router-link>
<router-link :to="`/antd/${uid}`">antd</router-link>更推荐指定url的写法,因为就不用考虑name还是path了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// App.vue
<router-link :to="`/antd?name=mrli`">antd</router-link>
// 要跳转的 Antd.vue
{{ $route.query.name }}
// App.vue
<router-link :to="`/antd/3`">antd</router-link>
// 要跳转的 Antd.vue
{{ $route.params.id }}
// 还需要router/index.js中定义, 不然$route.params.id无法将那个值对应到id上
{
path: "/antd/:id",
name: "antd",
component: AntdCom
},
from : Vue3实战系列:结合 Ant-Design-of-Vue 实践 Composition API
踩坑:
-
嵌套子路由无法直接通过URL访问:报错原因是
GET http://localhost:3000/dashboard/src/main.js net::ERR_ABORTED 404 (Not Found)
- 后经过测试查明,createWebHashHistory是可以直接跳转的,但createWebHistory是不能直接跳到嵌套子路由的
vuex
-
安装
yarn add vuex@next --dev
-
编写
store/index.js
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40import {createStore} from 'vuex'
export default createStore({
state: {
tagsList: [],
collapse: false
},
mutations: {
closeCurrentTag(state, data) {
for (let i = 0, len = state.tagsList.length; i < len; i++) {
const item = state.tagsList[i];
if (item.path === data.$route.fullPath) {
if (i < len - 1) {
data
.$router
.push(state.tagsList[i + 1].path);
} else if (i > 0) {
data
.$router
.push(state.tagsList[i - 1].path);
} else {
data
.$router
.push("/");
}
state
.tagsList
.splice(i, 1);
break;
}
}
},
// 侧边栏折叠
handleCollapse(state, data) {
state.collapse = data;
}
},
actions: {},
modules: {}
}) -
在组件中使用
1
2
3const collapseChage = () => {
store.commit("handleCollapse", !collapse.value);
};
分析Vue3项目
Vue Color Avatar
index.js 中的 v-cloak 指令
首先
yarn dev
本地运行后进入页面,会有一个Loading, coming soon
的页面加载过程,之后就会取消掉。然后在项目入口的index.html中看到了实现——v-cloak
功能:使用 v-cloak 指令设置样式,这些样式会在 Vue 实例编译结束时,从绑定的 HTML 元素上被移除。
场景:当网络较慢,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码(如下图)。我们可以使用 v-cloak 指令来解决这一问题。
我们使用 v-cloak 指令来解决屏幕闪动的问题吧O(∩_∩)O~,js 不变,在 div 中加入 v-cloak 指令。
1 | // html |
1 | # css: |
使用 v-cloak 指令之后的效果(demo):
在简单项目中,使用 v-cloak 指令是解决屏幕闪动的好方法。但在大型、工程化的项目中(webpack、vue-router)只有一个空的 div 元素,元素中的内容是通过路由挂载来实现的,这时我们就不需要用到 v-cloak 指令咯。
from: https://www.jianshu.com/p/f56cde007210
注:index.html中定义的元素,在Vue实例加载完成后,将都不会显示,因此比较适合用来做Loading页面
1 | <div id="app" v-cloak> |
toRefs+解包
1 | setup(){ |
vue 3.0 给input 获取焦点
1 | // 给table 循环的input 点击的时候获取焦点 |
vue2中的写法,通过this.$ref.xxx.focus()
来实现
1 | <div> |
关于import
默认情况下,JavaScript中在模块内的所有声明都是本地的,外部无法访问。如果需要公开模块中部分声明的内容,并让其它模块加以使用,这个时候就需要导出功能,最简单的方式是添加export关键字导出模块。
- 默认导出导入: 每个模块仅有一个default的导出,导出内容可以是一个function、class,object等。因为这种方式被当做主要的导出内容,导入方式最为简单。
- ▲注意:由于一个模块仅仅只允许导出一个default对象,实际导出的是一个default命名的变量。
- 因此导入的时候import后的名字, 实际上是给这个default命名的导出暴露的内容进行重命名,所以import后可以是任意变量名称,且不需要{}。
- named导出导入export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
- import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(即暴露接口模块)对外接口的名称相同。
- 如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
总结:
- 如果是默认导出export default则可以省略花括号,且import的模块名是随意的
- 如果是非export default导出的接口内容,在引用时须加花括号,且引用的模块名与导出时的命名必须相同
本质是因为:一个模块中只允许一个默认导出export default,但允许多个命名导出export;
from 格式:
Js import的具体查找规则为:
① 如果X是内置模块,则直接返回该模块。如require(‘http’)。
② 如果X以./
、/
、../
的相对路径开头,则
1. 根据X所在的父模块,确定X的绝对路径。
2. 将X当做文件,依次查找下面的文件,如果找到,则直接返回。
1. X
2. X.js
3. X.json
4. X.node
3. 将X当做目录,依次查找下面的文件,如果找到,则直接返回。
1. X/package.json(查找main字段中的文件,规则同上)
2. X/index.js
3. X/index.json
4. X/index.node
③ 如果X不带路径:
1. 根据X所在的父模块,确定X可能的安装目录。
2. 依次在每个目录中,将X当成文件名或目录名加载。
④ “not found”
因此,以下三种效果都是一样的,都可以成功
1 | import router from './router/index.js' // 符合 `②21` |
既然如此,那么肯定有先后顺序,因此,我们做个实验。main.js为根目录下有个vu.js(目标为②22), 还有个vu的文件夹,其中包含个index.js(目标为②32)。
1 | mrli@VM-4-7-ubuntu:~/import_test$ tree |
注意:本地测试时是node运行,而Node和浏览器端所支持的模块规范不同。
条目 | Node | 浏览器 |
---|---|---|
模块规范 | CommonJS | ES6 |
导出 | modules.exports |
export, export default;modules.exports |
引入 | require | import;require |
在windows下用node编写的测试文件如下
1 |
|
输出的结果为:显示为 同级目录xx.js优先级高于xx/index.js
1 | mrli@VM-4-7-ubuntu:~/import_test$ node main.js |
参考:
- 详谈 import 路径
- export报错SyntaxError: Unexpected token export
- JavaScript笔记(CommonJS规范,以及exports、module.exports和export、export default区别)
编写注意点:
如果是在函数体内写的语句是不需要分号分隔的,如
1 | setup(){ |
与之对应的是在对象体内,如
1 | var app = { |
里面会出现键值对和必须要逗号分隔开
axios请求
- 从浏览器中创建 XMLHttpRequest
- 从 node.js 发出 http 请求
- 支持promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
- 直接每次从axios库中调用axios函数
1 | <template> |
结合 vue使用vue-axios
npm install --save axios vue-axios
vue-axios是按照vue插件的方式去写的。那么结合vue-axios,就可以去使用vue.use方法了
1 | // 首先在主入口文件main.js中引用 |
之后就可以使用了,在组件文件中的methods里去使用了
1 |
|
axios 改写为 Vue 的原型属性
首先在主入口文件main.js中引用,之后挂在vue的原型链上
1 | import axios from 'axios' |
在组件中使用
1 | this.$axios.get('api/getNewsList').then((response)=>{ |
创建axios实例,并封装成工具类
1 | // utils/requests.js |
组件中直接import
1 | <script> |
axios进阶用法: 结合vuex再封装一层
1 | import axios from 'axios' |
父子组件间通信
props, 父向子传递数据
1 | // 父组件 |
provide和inject, 祖向孙传递数据
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
官方文档
- 父组件通过 provide 选项来提供数据,
1 | // optional api写法 |
▲.处理响应性:
Q: 默认情况下,provide/inject 绑定并不是响应式的
A: 我们可以通过传递一个 ref property 或 reactive 对象给 provide 来改变这种行为。在我们的例子中,如果我们想对祖先组件中的更改做出响应,我们需要为 provide 的 todoLength 分配一个组合式 API computed property:
1 | app.component('todo-list', { |
- 子组件通过 inject 选项来开始使用这些数据。
完整案例:
1 | // 父组件 |
optional写法:
1 | // 父组件, 要拿this必须写成方法, 不能写provide: {a: "45"}了 |
emits,子向父传递事件
- emits参数有俩种形式对象和数组,对象里面可以配置带校验emit事件,为null的时候代表不校验,校验的时候,会把emit事件的参数传到校验函数的参数里面
- 当校验函数不通过的时候,控制台会输出一个警告,但是emit事件会继续执行
- 记录一个坑:比如你emit事件的名称正好和原生事件的名字重复了,那么这个事件会执行俩次,那么配置了emits这个选项的话,就能很好的解决这个问题
- 子组件Emiter.vue
使用
- emits: 列表声明从父组件继承来的事件
- $emit: 抛出事件, 通知父组件处理
- 在子组件中,通过$emit()来触发事件
- 在父组件中,通过v-on来监听子组件事件
1 | // 子组件 |
父组件Emit.vue
1 | <template> |
例子:
注意: 使用compositionAPI也是能有数据成员的比如props, name, emits
解决yarn全局安装模块后但仍提示无法找到命令的问题
如果使用yarn global add xxxx
安装了xxxx插件之后,但是在cmd窗口中仍然提示类似命令无法找到(Command not found)的错误(如@vue/cli
后输入vue <create>
没有提示,),一般是由于yarn的环境没有配置好。可以通过以下的方法,将yarn的环境配置环境中。
首先,先查看一下yarn的bin目录,
- windows输入
yarn global bin
=>C:\Users\leon\AppData\Local\Yarn\bin
然后将该路径加入到path中,对于windows中直接将该目录加入到path中。 - 在Linux中
which yarn
后,将结果填入到~/.bashrc
中,如下格式:export PATH=$PATH:/usr/bin/yarn
导入scss
- 安装scss/sass,
node-sass sass-loader sass
属于重要依赖,所以需-D而不是-S;
1 | npm install node-sass sass-loader sass -D |
-
根路径下建立文件vue.config.js
下面这个(不加也没事)
1
2
3
4
5
6
7
8
9const path = require('path');
module.exports = {
pluginOptions: {
'style-resources-loader': {
preProcessor: 'scss',
patterns: []
}
}
}还有人提供的全局配置,实测可以
1
2
3
4
5
6
7
8
9
10
11module.exports = {
css: {
loaderOptions: {
sass: {
prependData: `@import "./assets/css/test.scss";`
// 等价于:prependData: `@import "@/assets/style.scss";`
// 因为 配置中对import中的@进行了设置'@': resolve('src'),即@ 等价于 /src 这个目录
}
}
},
}如果不需要全局配置,可以直接在某个组件下的style里加
@import "../assets/style.scss";
-
调用test
1
2
3
4
5
6<style lang="scss" scoped>
$bg: red;
.container {
background-color: $bg;
}
</style>
from :
- vue 3.x - 安装scss/sass
- Syntax Error: TypeError: this.getOptions is not a function——sass-loader版本太高
- vue3 vue.config.js 配置
使用Vue3特性的工程:
vue.config.js模板配置文件
1 | // vue.config.js 配置说明 |
前端技术栈
- eslint-plugin-vue eslint-plugin-vue
- axios 强大的前端请求库
- fues.js fues.js Fuzzy Search 前端模糊搜索
- echart echart 数据可视化
- antv antv 蚂蚁数据可视化
- xlsx xlsx SheetJS
- jszip jszip 优秀的前端压缩库
- mockjs mockjs 模拟和交互数据
- wangeditor wangeditor 富文本编辑器
- fullcalendar fullcalendar 丰富的日历插件
文章:
Author: Mrli
Link: https://nymrli.top/2021/11/28/Vue3初体验/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.