Vue3 新特性
Vue3 的新特性通过 Vue 的作者尤雨溪的视频而来:Vue.js 作者谈 Vue 3.0 beta 现状
有哪些亮点?
- 性能提升
- Tree-shking 支持,按需打包,更小体积
- Composition api 新语法
- Fragment,Teleport,Suspense 新组件
- 更好的 TypeScript 支持
- 自定义渲染器
性能提升
- 重写 Virtual dom,静态标记显著提升性能
- 编译时性能优化
- 更好的初始化性能
- 和 Vue2 相比,更新快了1.2 ~ 2倍
- 和 Vue2相比,SSR 快了2 ~ 3倍
Tree-shking
- 使更多可选的内容可以被 tree-shaking,比如 v-model 和 <transition>现在就可以被tree-shaking了,没有用到的时候,打包就不会被放入项目中;
- 模板中只包含一个简单的 Hello World 时,框架为项目带来的大小只增加了 13.5kb。如果采用了 Composition API 支持替代其他 options 的 API 的话,会变成 11.75kb 大小,在 Vue3.0 中为了兼容之前版本,默认不会把 options 的 API 去掉,但是会有开关提供把这些 API 去掉的选项;
- 即便是 Vue3.0 的东西都放进去,也只有 22.5kb(包括所有的新功能)。这个大小比 Vue2.x 小,但功能却更多。
Webpack 是有 Tree-shaking 这个功能的,但 Tree Shaking 前提是 ES Module(import…) 来写。Vue 3 在浏览器中,依然会有一个全局的 Vue对象,但是在用 Webpack 的时候,它就没有defualt export,你就不能 import vue,把 Vue 当对象本身去操作,所有的 Api 都要 import 进来。这样使得一些不会被用到的功能不会被引用进来。
文件大小变化很明显,22.5KB ~ 13.5KB,如果使用 Composition 新语法,只有 11.75KB
Composition api
- 能和 Options api 一起使用,Composition api 可以视为新添加的 Api,不影响已有 Api 的使用,甚至可以跟已有 Api 一起使用。
- 灵活的逻辑组合与复用,在 Vue2.x 里面,我们可能会采用 Mixin 来进行逻辑的抽取。Vue3 尽量使用 Composition api 来抽逻辑。如果是逻辑库的作者,提供可复用逻辑的时候,尽量也用 Composition api 去提供。
Vue3 在 setup 把一个对象返回,会把对象变成响应式,然后,Vue 在需要的地方去追踪它所用到的响应式的依赖,当依赖变化的时候重新去渲染。(reactive)
vue 3 里暴露一个新的 Api 叫做 watchEffect,Effect 就是副作用和 React 的 useEffect 类似。
一些原生的数据结构,比如像数字、Boolean 等,这些就需要用一个东西包装(ref)
核心:reactive \ ref \ watchEffect \ 其他是这三者的组合使用。
Fragment
组件不再需要一个唯一的根节点了
Vue 2 模板只有一个单独的根节点。
Vue 3 文字、多个节点、甚至也可以是个v-for,都会自动变成碎片,如果使用渲染函数,也可以直接在渲染函数里面返回一个数组,自动变成一个碎片。
Teleport
对标 React Portal
<teleport>
向 Vue 核心添加组件
该组件需要一个目标元素,该元素是通过需要一个 HTMLElement
或 querySelector
字符串的 prop
提供的。
组件将其子代移动到 DOM
选择器标识的元素
在虚拟 DOM
级别,子级仍然是子代的后代 <teleport>
,因此他们可以从其祖先那里获得注入
Suspense
异步组件
和 React 的 Suspense 类似
<Suspense>
<template #default>
异步的组件
</template>
<template #fallback>
Loading ...(加载状态的组件)
</template>
</Suspense>
更好的 TypeScript 支持
自定义渲染器
Vue3.0 中支持 自定义渲染器 (Renderer):这个 Api 可以用来创建自定义的渲染器, (在以往像 weex 和 mpvue,需要通过 fork 源码的方式进行扩展)
vite
Vue3 提供了一种新工具 vite,它在程序开发阶段,采用了 Koa、Node 开启一个服务和游览器对 import 的支持实现了按需请求加载内容,避免了热更新时长时间的编译过程,这大大提高了开发效率。
当然发布生产的时候,还是通过 Webpack 进行编译打包流程。
About Vue2.x
- 最后一个将为版本 2.7
- 从 3.0 向后移植兼容的改进
- 在 3.0 中删除的功能的弃用警告
- LTS(长期支持的版本,会定时发布系统更新) 18个月
Vue3 架构
核心
详细
Vue3 的三种使用
vue-cli
官方 Cli 工具,记得要升级最新版本
npm install -g @vue/cli
vue create 01-vue3-cli
cd 01-vue3-cli
<!--安装 Vue3 库-->
vue add vue-next
npm run serve
webpack
vue-cli 一开始还没有支持的时候,vue 官网整了一个 webpack 的项目配置,直接 clone 即可
git clone https://github.com/vuejs/vue-next-webpack-preview.git 01-vue3-webpack
cd 01-vue3-webpack
npm install
npm run dev
vite
这是一个 Vue 全新的开发工具,是由 Vue 的作者开发的,目的是以后取代 webpack,原理就是利用游览器现在已经支持 ES6 的 import
语法,碰见 import
会发送一个 http 请求去加载文件,vite 拦截这些请求,做一些预编译,就省去了 webpack 漫长的打包时间,提升开发效率。
npm install -g create-vite-app
create-vite-app 01-vue3-vite
cd 01-vue3-vite
npm install
npm run dev
运行项目后,打开 http://localhost:3000/ 看一下 network 就会发现,所有的文件都是 import
进行了预编译,然后通过游览器发起请求,做到了天生的按需加载,秒开项目。
Vue3 新语法实战
对比旧 Vue2 Option api
<template>
<div>
<h1>{{state.count}} * 2={{double}}</h1>
<button @click="add">累加</button>
</div>
</template>
<script>
export default {
data: () => ({
state: {
count: 1
}
}),
computed: {
double() {
return this.state.count * 2;
}
}
methods: {
add() {
this.state.count++;
}
}
}
</script>
从上面的简单例子可以看到,在 Vue2 的处理方式就是这样的。
下面我们看一下改用 Vue3 的 Composition api 模式
Composition api 新语法体验
<template>
<h1>{{state.count}} * 2={{double}}</h1>
<button @click="add">累加</button>
</template>
<script>
import {reactive,computed} from 'vue'
export default {
setup(){
const state = reactive({
count:1
})
function add(){
state.count++
}
const double = computed(()=>state.count*2)
return {state,add,double}
}
}
</script>
Composition api 的方式写,从上面例子中,虽然看不出来有什么好处,那是因为组件规模还不是很庞大,其实在组件变得越来越庞大之后,优势就会越来越明显了,后续会展示出来。
Composition api 相比于 Option api 模式有什么区别
setup
setup
是新的选项,可以理解是 Composition 的入口,函数内部在 beforeCreate
之前调用,函数返回的内容会作为模板渲染的上下文
reactive
其实和 Vue2 里的 Vue.observerable
是一样的,把一个数据变成响应式,这个能力是完全独立的。
关于 Vue.observerable
的用法看这里:cn.vuejs.org/v2/api/#Vue…
computed
其实就是一个计算属性,和 Vue2 的能力是一样的
功能拆分,全局 import
Vue2 里面的 data
,methods
,computed
都是挂载在 this
之上的,有两个明显的缺点
- 不利于类型推导
- 如果一个项目没有用到
computed
功能,代码也会被打包
Vue3 的按需手动 import
写法更有利于 Tree-shaking 打包
ref
在 Vue3 中 reactive
负责复杂的数据结构,ref
可以吧基本的数据结构包装成响应式的
<template>
<h1>{{state.count}} * 2={{double}}</h1>
<h2>{{num}}</h2>
<button @click="add">累加</button>
</template>
<script>
import {reactive,computed,ref,onMounted} from 'vue'
export default {
setup(){
const state = reactive({
count:1
})
const num = ref(2)
function add(){
state.count++
num.value+=10
}
const double = computed(()=>state.count*2)
onMounted(()=>{
console.log('mouted')
})
return {state,add,double,num}
}
}
</script>
可以看到,当要包装独立基本数据类型的时候,就可以使用上 ref
了
抽离功能块
<script>
import {reactive,computed,ref,onMounted} from 'vue'
export default {
setup(){
const {state,double} = useCounter(1)
const num = ref(2)
function add(){
state.count++
num.value+=10
}
onMounted(()=>{
console.log('mouted')
})
return {state,add,double,num}
}
}
function useCounter(count,n){
const state = reactive({
count
})
const double = computed(()=>state.count*2)
return {state,double}
}
</script>
这里可以看到,这样写的话,就很像 React 的 Hook 写法了,这样的做法在后续项目越来越庞大的时候就能体现出很好的优势出来了。
总结
看了上面的各种写法后,当我们在开发一个组件的时候,需要把数据放到 data
,把计算属性放到 computed
,把方法放到 methods
,这种做法虽然说将不同的功能对应放到响应的位置,但也带来了一个问题,当组件十分庞大的时候,会发现对组件的维护变得十分难受,当修改一个功能时需要在组件上下到处去翻腾,前后修改代码,这也是 大圣老师 所说的上下反复横跳的问题。
可以看看官方的例子:
// 更改为 Vue3 写法后
export default {
setup () {
// Network
const { networkState } = useNetworkState()
// Folder
const { folders, currentFolderData } = useCurrentFolderData(networkState)
const folderNavigation = useFolderNavigation({ networkState, currentFolderData })
const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
const { showHiddenFolders } = useHiddenFolders()
const createFolder = useCreateFolder(folderNavigation.openFolder)
// Current working directory
resetCwdOnLeave()
const { updateOnCwdChanged } = useCwdUtils()
// Utils
const { slicePath } = usePathUtils()
return {
networkState,
folders,
currentFolderData,
folderNavigation,
favoriteFolders,
toggleFavorite,
showHiddenFolders,
createFolder,
updateOnCwdChanged,
slicePath
}
}
}
优势就由此可见了。
新事物
Fragment
这个功能呢就是 Vue 组件可以不用一定要一个根节点了,可以这样写:
<template>
<h1>H1</h1>
<div>Div</div>
</template>
这种写法在 Vue2 中是会报错的
下面再来看一个快速排序算法组件小例子:
<template>
<quick :data="left" v-if="left.length"></quick>
<li>{{flag}}</li>
<quick :data="right" v-if="right.length"></quick>
</template>
<script>
export default {
name:'quick',
props:['data'],
setup(props){
let flag = props.data[0]
let left = []
let right = []
props.data.slice(1).forEach(v=>{
v>flag? right.push(v): left.push(v)
})
return {left, right, flag}
}
}
</script>
<ul>
<FragmentDemo :data="[5,3,1,6,9,4,2,8]" />
</ul>
运行后的结果呈现出来是这样的:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
</ul>
Teleport
这个组件和 React 的 Portal 组件是类似的,用起来比较简单,就是可以渲染 vue 组件的内容,到指定的 dom 节点中。
在做弹窗的时候,比较有用,因为往往,我们的弹窗都需要渲染到最外层的 body 下面,否则嵌套过多,蒙层可能会被父元素的 transform 影响。
常用场景:公共的模态框 Modal
下面来看一下小例子:
<template>
<h1>{{ msg }}</h1>
<div class="confirm-modal">
<button @click="isOpen = true">打开</button>
<!-- 注意这一块代码 -->
<Teleport to="#modal-container">
<div class="modal-warp" v-if="isOpen">
<div class="cover"></div>
<div class="content">
<p>我在外部哦</p>
<button @click="isOpen = false">取消</button>
</div>
</div>
</Teleport>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'HelloWorld',
props: {
msg: {
type: String,
default: 'Teleport 使用案例',
},
},
setup() {
const isOpen = ref(false);
return { isOpen };
},
};
</script>
<style>
.modal-warp {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
}
.cover {
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.2);
}
.content {
position: absolute;
padding: 16px 32px;
background-color: white;
border-radius: 4px;
}
</style>
上面的 <Teleport to="#modal-container">
表示,接下来将会把里面的内容渲染到 id 为 modal-container
的 dom 节点中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<div id="modal-container"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
运行的效果就是这样滴:
Suspense
Suspense 的功能是一个异步组件加载的功能,这个在 React 中也存在一个类似
React
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
Vue3
<Suspense>
<template #default>
异步的组件
</template>
<template #fallback>
加载状态的组件
</template>
</Suspense>
这里也将展示一个小例子:
父组件
<template>
<h1>Supense</h1>
<Suspense>
<template #default>
<AsyncComponent :timeout="3000" />
</template>
<template #fallback>
<h1>加载中</h1>
</template>
</Suspense>
</template>
<script>
import AsyncComponent from './AsyncComponent.vue';
export default {
components: {
AsyncComponent,
},
};
</script>
AsyncComponent 组件
<template>
<h1>一个异步小组件</h1>
</template>
<script>
function sleep(timeout) {
return new Promise((resolve) => setTimeout(resolve, timeout));
}
export default {
name: 'AsyncComponent',
props: {
timeout: {
type: Number,
required: true,
},
},
async setup(props) {
await sleep(props.timeout);
},
};
</script>
这样基本就能体现出来了,运行结果如下:
Vue3 相关问题
1、vue3 中新增 setup() 函数代替了 2.x 版本的什么函数?
答:
- beforeCreate
- created
2、vue3 对 vue2 进行了那些改变?
答:
- 使用了 proxy 代替了 Object.defineProperty
- 新增 Composition Api
- 让 vue 更快的优化
- 更好的 ts 支持,vue3 直接用 ts 重写
3、vue3 有哪些优点
答:
- 更容易维护
- 更多的原生支持
- 更易于开发使用
4、Vue3 更新内容
答:
- Vue3 会兼容之前的写法,Composition API 也是可选的
- 为了继续支持 IE11,Vue3 将发布一个支持旧观察者机制和新 Proxy 版本的构建
- Vue3 不仅会使用 TypeScript,而且许多软件包将被解耦,使所有内容更加模块化
5、Vue3 将使用 ES2015 Proxy 作为其观察者机制?
答:
对
7、关于 Vue 的生命周期
答:
页面首次加载会触发 beforeCreate、created、beforeMount、mounted、beforeUpdate、updated
正确的是:
beforeCreate、created、beforeMount、mounted
8、以下属于 Vue 组件通信方式正确项是?
- props
- context
- provide / inject
- listeners
答:
- props
- provide / inject
- listeners
解析:
context 属于 react 中的一种跨层级传参方式