vue2与vue3技术点对比

869 阅读5分钟

公司部门Vue技术分享

Vue2与Vue3

序号条目Vue2Vue3
1创建项目的方法webpackwebpack/vite
2实例化new VuecreateApp *import {createApp} from 'vue'
3template模板只支持单一根节点可以使用多个根节点
4生命周期新版本setup中的都是以onXxx()函数注册使用,其中beforeCreate、created 这两个函数可以由setup()代替。
onRenderTracked、onRenderTriggered调试
5双向绑定1.无法检测到新的属性添加/删除
2.无法监听数组的变化
3. 需要深度遍历,浪费内存
1. 使用 ES6的Proxy 作为其观察者机制,取代之前使用的Object.defineProperty。Proxy默认可以支持数组
2.允许框架拦截对象上的操作
3.多层对象嵌套,使用懒代理
6虚拟DOM单个组件部分变化需要遍历该组件的整个vdom树重写了虚拟 DOM 的实现更多编译时的优化以减少运行时的开销
7diff算法优化虚拟dom是进行全量的对比Vue3新增了静态标记(PatchFlag)只对比带有patch flag的节点
8hoistStatic 静态提升无论元素是否参与更新, 每次都会重新创建, 然后再渲染不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
9事件监听缓存默认情况下onClick会被视为动态绑定, 所以每次都会去追踪它的变化直接缓存起来复用即可(需要开启事件监听缓存)
10Composition 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

compositionAPI.gif

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 生命周期钩子

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

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 refstemplate 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() {![click-demo.gif](https://upload-images.jianshu.io/upload_images/11213662-443cdec75b5608c3.gif?imageMogr2/auto-orient/strip

    const { count, increment } = useCounter({ initialValue: 0, delay: 1000 })
    return () => (
      <div>
        You clicked: { count } times
        <button onClick={increment}>
          Click me
        </button>
      </div>
    )
  }
})