公司部门Vue技术分享
Vue2与Vue3
| 序号 | 条目 | Vue2 | Vue3 |
|---|---|---|---|
| 1 | 创建项目的方法 | webpack | webpack/vite |
| 2 | 实例化 | new Vue | createApp *import {createApp} from 'vue' |
| 3 | template模板 | 只支持单一根节点 | 可以使用多个根节点 |
| 4 | 生命周期 | 新版本setup中的都是以onXxx()函数注册使用,其中beforeCreate、created 这两个函数可以由setup()代替。 onRenderTracked、onRenderTriggered调试 | |
| 5 | 双向绑定 | 1.无法检测到新的属性添加/删除 2.无法监听数组的变化 3. 需要深度遍历,浪费内存 | 1. 使用 ES6的Proxy 作为其观察者机制,取代之前使用的Object.defineProperty。Proxy默认可以支持数组 2.允许框架拦截对象上的操作 3.多层对象嵌套,使用懒代理 |
| 6 | 虚拟DOM | 单个组件部分变化需要遍历该组件的整个vdom树 | 重写了虚拟 DOM 的实现更多编译时的优化以减少运行时的开销 |
| 7 | diff算法优化 | 虚拟dom是进行全量的对比 | Vue3新增了静态标记(PatchFlag)只对比带有patch flag的节点 |
| 8 | hoistStatic 静态提升 | 无论元素是否参与更新, 每次都会重新创建, 然后再渲染 | 不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可 |
| 9 | 事件监听缓存 | 默认情况下onClick会被视为动态绑定, 所以每次都会去追踪它的变化 | 直接缓存起来复用即可(需要开启事件监听缓存) |
| 10 | Composition API(类似React Hooks) | ||
*2新增createApp方法
// vue2.0
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './App.scss'
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
// vue3.0
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './App.scss'
const app = createApp(App)
app.use(router).use(store).mount('#app');
*3template模板
<template>
<!-- vue3.0组件的根节点可以有多个,或者使用<Fragment> 空标签 -->
<div class="login"></div>
<div class="main"></div>
<div></div>
</template>
*4 生命周期 ⽣命周期钩⼦可以通过 onXXX 形式导⼊并在setup内部注册 这些⽣命周期钩⼦注册函数只能在 setup() 使⽤。
import { onMounted, onUpdated, onUnmounted } from "@vue/composition-api";//引入钩子
export default {
setup(props, ctx) {
// `props` 属性被定义之后,实际上等价于 `Vue2.0` 版本的 `beforeCreate` 和 `Created` 这两个生命周期
const loadMore = () => {};
onMounted(() => {
loadMore();
});
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
return {
loadMore
};
}
};
// 可以多次注册,按顺序执⾏
setup() {
onMounted(() => {
console.log('mounted1')
})
onMounted(() => {
console.log('mounted2')
})
}
setup() {
onMounted(() => {
console.log('mounted1')
})
onMounted(() => {
console.log('mounted2')
})
}
//可以⽤在其他可复⽤的逻辑中
function useCounter() {
const counter = ref(0)
let timer
onMounted(() => {
timer = setInterval(() => counter.value++, 1000)
})
onUnmounted(() => {
clearInterval(timer)
})
return counter
}
setup() {
const counter = useCounter()
return { counter }
}
*6 虚拟DOM *7diff算法优化 vue-next-template-explorer.netlify.app/
<div>
<div>divdivdivdiv</div>
<p :class="name" :style="dd">我是一个标题</p>
<p>{{msg}}</p> <!-- 双向绑定 -->
</div>
// 虚拟DOM
import { createElementVNode as _createElementVNode, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, toDisplayString as _toDisplayString, createCommentVNode as _createCommentVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("div", null, "divdivdivdiv"),
_createElementVNode("p", {
class: _normalizeClass(_ctx.name),
style: _normalizeStyle(_ctx.dd)
}, "我是一个标题", 6 /* CLASS, STYLE */),
_createElementVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
_createCommentVNode(" 双向绑定 ")
]))
}
可以通过flag的信息得知当前节点要对比的具体内容
export const enum PatchFlags {
TEXT = 1,// 动态文本节点
CLASS = 1 << 1, // 2 // 动态 class
STYLE = 1 << 2, // 4 // 动态 style
PROPS = 1 << 3, // 8 // 动态属性,但不包含类名和样式
FULL_PROPS = 1 << 4, // 16 // 具有动态 key 属性,当 key 改变时,需要进行完整的 diff 比较。
HYDRATE_EVENTS = 1 << 5, // 32 // 带有监听事件的节点
STABLE_FRAGMENT = 1 << 6, // 64 // 一个不会改变子节点顺序的 fragment
KEYED_FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或部分子字节有 key
UNKEYED_FRAGMENT = 1 << 8, // 256 // 子节点没有 key 的 fragment
NEED_PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比较
DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot
HOISTED = -1, // 静态节点
// 指示在 diff 过程应该要退出优化模式
BAIL = -2
}
*8 hoistStatic 静态提升 *9事件监听缓存 @click="onClick"
<div>
<p>我是一个标题</p>
<p>{{msg}}</p>
</div>
// 静态提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "我是一个标题"), // 每次都会创建一个虚拟节点
_createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
]))
}
// 静态提升之后
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "我是一个标题", -1 /* HOISTED */) // 定义了一个全局变量,只会创建一次
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
]))
}
*10 Composition API
10.1 setup 函数是⼀个新的组件选项。作为在组件内使⽤ Composition API 的⼊⼝点。
如果 setup 返回⼀个对象,则对象的属性将被合并到组件的渲染函数上下⽂:
<template>
<div>{{ state.foo }}</div>
</template>
<script>
import { reactive } fr om 'vue'
export default {
setup() {
const state = reactive({ foo: 'bar' })
// 暴露给模板
return {
state
}
},
}
</script>
setup 也可以返回⼀个函数,该函数会作为组件渲染函数:
import { h, reactive } from 'vue'
export default {
setup() {
const state = reactive({ foo: 'bar' })
// 返回⼀个函数作为渲染函数
return () => h('div', state.foo)
},
}
setup 参数: setup(props, {attrs, slots, emit})
const Comp = {
template: `<div>comp <slot /></div>`,
props: {
dong: {
type: String,
default: ''
},
},
setup(props, ctx) {
console.log(props, ctx);
}
}
<comp dong="dong" tua="tua">default slot content</comp>
// props是响应式的,但是不能解构,否则将失去响应能⼒
// ok
setup(props) {
watchEffect(() => {
console.log(props.dong);
})
}
// no ok
setup({dong}) {
watchEffect(() => {
console.log(dong);
})
}
this 在 setup() 中不可⽤也没有必要使⽤
在 setup() 内部,this 不是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这使得 setup() 在和其它选项式 API 一起使用时可能会导致混淆。
setTimeout(() => state.foo = 'barrrrrr', 1000)
setup() 中获取组件实例
const instance = getCurrentInstance()
console.log(instance);
10.2 生命周期钩子
| 选项式 API | Hook inside setup |
|---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
10.3 Provide / Inject
import { provide, inject } from 'vue'
// 祖代
const Ancestor = {
setup() {
provide('colorTheme', 'dark')
},
}
// 子代
const Descendent = {
setup() {
const theme = inject('colorTheme')
return {
theme,
}
},
}
注⼊值的响应性
如果注⼊⼀个响应式对象,则它的状态变化也可以被侦听。
// 提供者响应式数据
const themeRef = ref('dark')
provide('colorTheme', themeRef)
// 使⽤者响应式数据
const theme = inject('colorTheme')
watchEffect(() => {
console.log(`theme set to: ${theme.value}`)
})
10.4 模板引用
当使⽤组合式 API 时,reactive refs 和 template refs 的概念已经是统⼀的。为了获得对模板内元素或组
件实例的引⽤,我们可以像往常⼀样在 setup() 中声明⼀个 ref 并返回它:
<template>
<div ref="root"></div>
</template> <script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// 挂载后, dom会被赋值给root这个ref对象
console.log(root.value) // <div/>
})
return {
root,
}
},
}
</script>
JSX 中的用法
export default {
setup() {
const root = ref(null)
return () =>
h('div', {
ref: root
})
// with JSX
return () => <div ref={root} />
}
}
Vue中使用TS
已存在的项目可以使用插件的方式引入
vue add @vue/typescript //自动配置tsconfig.json 使用vue/cli构建的项目
新建项目时可以选择支持TS
新建项目时可以选择支持TS,已存在的项目可以使用插件的方式引入
npm init vite hello-vue3 -- --template vue // 使用vite构建项目
Select a framework: » - Use arrow-keys. Return to submit.
vanilla
> vue
react
preact
lit
svelte
Select a variant: » - Use arrow-keys. Return to submit.
vue
> vue-ts
Webpack 配置TS
// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
// 这样就可以对 `this` 上的数据属性进行更严格的推断
"strict": true,
"jsx": "preserve",
"moduleResolution": "node"
}
}
/ webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
},
exclude: /node_modules/,
},
{
test: /\.vue$/,
loader: 'vue-loader',
}
...
在vue中使用tsx
经典的三段式
<template>
<div class="hello">
<h1 @click="msgClick">{{ msg }}</h1>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
组合式写法(composition)
<template>
<div>
You clicked: {{ count }} times
<button @click="handleClick">
Click me
</button>
</div>
</template>
<script lang="javascript">
// 数据,以及处理逻辑抽离到另外一个文件
import { useCounter } from 'path/to/counter-hook'
export default {
setup() {
const { count, increment } = useCounter({ initialValue: 0, delay: 1000 })
return { count, increment }
}
}
另一个counter-hook
import { ref } from '@vue/composition-api'
export const useCounter = ({intialValue = 0, delay = 1000}) => {
const count = ref(intialValue)
const increment = () => { count.value++ }
return {
count,
increment: throttle(increment, delay)
}
}
function throttle(cb, duration) {
let shouldCall = true
return function(...args) => {
if (!shouldCall) return
const context = this
cb.apply(context, args)
shouldCall = false
setTimeout(() => {
shouldCall = true
}, duration)
}
}
组合式写法进阶版(tsx)
// 注意,这里必须引入`h`,后续tsx语法依赖
import { defineComponent, h } from '@vue/composition-api'
// 数据,以及处理逻辑抽离到另外一个文件
import { useCounter } from 'path/to/counter'
export default defineComponent({
setup() {
return () => (
<div>
You clicked: { count } times
<button onClick={increment}>
Click me
</button>
</div>
)
}
})