草庐IT

Vue3 和 Vue2 的异同及开发中具体区别

会飞的一棵树 2023-03-28 原文

总体来说

性能提升

  1. 重写了虚拟DOM的实现(跳过静态节点,只处理动态节点)
  2. update性能提高1.3~2倍
  3. 服务端渲染速度提高了2~3倍

树摇(Tree shaking)

可以将无用模块“剪辑”掉,仅打包需要的
原理:

  1. ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块
  2. 静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码

参考:介绍一下 tree shaking 及其工作原理

碎片化节点(Fragment)

在 vue2.x 中,每个组件只能有一个根,所以,写每个组件模板时都要套一个父元素。
在 vue3 中,为了更方便的书写组件模板,新增了一个类似 dom 的标签元素 ,也就是 vue3 中组件可以拥有多个根了。
这样做的好处在于:减少标签层级, 减小内存占用。

传送门 (Teleport)

可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。

Suspense

用于协调对组件树中嵌套的异步依赖的处理。
如果在渲染时遇到异步依赖项 (异步组件和具有 async setup() 的组件),它将等到所有异步依赖项解析完成时再显示默认插槽。
https://cn.vuejs.org/api/built-in-components.html#suspense

更好的TypeScript支持

Composition API

组合式API,替换原有的 Options API
根据逻辑相关性组织代码,提高可读性和可维护性
更好的重用逻辑代码(避免mixins混入时命名冲突的问题)
兼容了VUE2中的写法,原有Options API依然可以延用

响应式原理

不再基于 Object.defineProperty而是基于ES6中的Proxy

开发时差异

0. setup

组合式API以setup函数作为组件的入口

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 TypeScript 声明 props 和自定义事件。
  • 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
  • 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。
// setup 函数的第一个参数是组件的 props 
// 没有vue2.x中的this指向vue对象了
export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}

1. 生命周期调用

<script setup>
// 在vue中解构出方法
import { onMounted, onUpdated } from 'vue'

onMounted(() => {
  ...
})

onUpdated(() => {
  ...
})
</script>

beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured

2. 响应式(数据)API

ref
接受一个参数值并返回一个响应式且可改变的 ref 对象

  1. ref 对象拥有一个指向内部值的单一属性 .value
  2. 当ref在模板中使用的时候,它会自动解套,无需在模板内额外书写 .value
import { ref } from "vue";
export default {
  setup() {
    let Num = ref(0),
        isShow = ref(false);

    return {
      Num,
      isShow
    };
  }
};

reactive

  1. 接收一个普通对象然后返回该普通对象的响应式代理等同于 2.x 的 Vue.observable()
  2. 响应式转换是“深层的”:会影响对象内部所有嵌套的属性

使用toRefs(state)可将其创建的传为Refs类型,方便在模板中直接取值,而不用.value

import { ref, reactive } from "vue";
export default {
  props: { title: String },
  setup() {
    let state = reactive({
      Num: 0,
      arr: [1, 2]
    });
    let change = () => {
      state.arr[0] = state.arr[0] + 1;
      state.name = "flytree";
    };

    return {
      state,
      change
    };
  }
};

相关的一些方法:
unref / toRef / toRefs / isRef / isProxy / isReactive / isReadonly

3.计算属性computed

传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象

const count = ref(1);
const plusOne = computed(() => count.value + 1);
console.log(plusOne.value); //2
plusOne.value++; //错误!

或者传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态

const count = ref(1);
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1;
  }
});
plusOne.value = 1;
console.log(count.value); //0

4.侦听器watch

watchEffect
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数

export default {
  props: {
    title: String,
  },
  setup(props) {
    watchEffect(() => {
      console.log(`title is: ` + props.title);
    });
  }
};

watch

  1. watch API 完全等效于 2.x this.$watch
  2. watch 需要侦听特定的数据源,并在回调函数中执行副作用
  3. 默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调侦听单个数据源
点击查看代码
// 侦听器的数据源可以是一个拥有返回值的 getter 函数,也可以是 ref
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
);

const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
});
import { ref, reactive, toRefs, computed, watch } from "vue";
export default {
  setup() {
    ....

    let ratio = ref("--");
    watch(state, (state, preState) => {
      let total = state.supNum + state.oppNum;
      ratio.value =
        total === 0 ? "--" : ((state.supNum / total) * 100).toFixed(2) + "%";
    });

    return {
      ...,
      ratio
    };
  }
};

5. 获取DOM元素refs

模板的Refs
当使用组合式 API 时,reactive refs 和 template refs 的概念已经是统一的

点击查看代码
<template>
  <div ref="root"></div>
</template>

<script>
  import { ref, onMounted } from 'vue';
  export default {
    setup() {
      const root = ref(null);
      onMounted(() => {
        console.log(root.value);
      });
      return {
        root
      };
    }
  }
</script>

6. 路由使用, VUEX ,VUE挂载

先要解构出方法,再去创建对应路由,store或vue对象,其它一样

Vue Router 4.x


import { createRouter, createWebHashHistory } from 'vue-router';
import routes from './routes';

const router = createRouter({
    history: createWebHashHistory(),
    routes
});

router.beforeEach(async (to, from, next) => { ... })
export default router;

Vue Router 3.x

import VueRouter from 'vue-router'
import routes from './routes';

const router = new VueRouter({
  routes
})
export default router;

VUEX 4.x

import { createStore, createLogger } from 'vuex';
export default createStore({
  state: {},
  mutations: {},
  actions: {},
  plugins: [createLogger()]
})

VUEX 3.x

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {},
  state: {},
  mutations: {},
  actions: {},
})
export default store

实例创建

VUE 3.x

import { createApp } from 'vue';
const app = createApp(App);
app.use(store);
app.use(router);
app.mount('#app');

VUE 2.x

import Vue from 'vue'

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

7. 自定义指令处理技巧

export default function directive(app) {
    app.directive('xxx', {
        // 指令首次绑定到元素且在安装父组件之前...「等同于bind」
        beforeMount(el, binding, vnode, prevVnode) {
            // binding:数据对象
            //   + arg:传给指令的参数   v-xxx:n -> arg:"n"
            //   + modifiers:修饰符对象 v-xxx.stop -> modifiers:{stop:true}
            //   + value:指令绑定的值   v-xxx="1+1" -> value:2
            //   + oldValue:之前绑定的值
        },
        // 安装绑定元素的父组件时...「等同于inserted」
        mounted() {},
        // 在包含组件的VNode更新之前...
        beforeUpdate() {},
        // 在包含组件的VNode及其子VNode更新后...「等同于componentUpdated」
        updated() {},
        // 在卸载绑定元素的父组件之前...
        beforeUnmount() {},
        // 指令与元素解除绑定且父组件已卸载时...「等同于unbind」
        unmounted() {}
    });
};
// main.js
import {
    createApp
} from 'vue';
import App from './App.vue';
import directive from './directive';
const app = createApp(App);
directive(app);
app.mount('#app');

8.emit

在子组件向父组件传值,或者发送事件时,我们通常使用emit,进行操作。
because composition functions are used inside the setup hook which doesn't have access to the other options like methods and emits:

export default defineComponent({
    name: "layout",
    emits: ['showsidebar'],
    setup(props, { emit }) {
        const showSidebar = ref(true);
        const { breakpoints } = useBreakpoint();
        watch(breakpoints, (val) => {
            showSidebar.value = !(val.is === "xs" || val.is === "sm");
            emit('showsidebar',showSidebar.value);
        });
        return {
            showSidebar,
        };
    },
    data() {
        // ...
    },
});
<script setup>
 import { defineEmits,watch,ref } from 'vue'
    
    const emit = defineEmits(['showsidebar'])
    const showSidebar = ref(true);
    const { breakpoints } = useBreakpoint();
    watch(breakpoints, (val) => {
            showSidebar.value = !(val.is === "xs" || val.is === "sm");
            emit('showsidebar',showSidebar.value);
        });
</script>

<script setup>
const emit = defineEmits({
  // No validation
  inFocus: null,

  // Validate submit event
  submit: ({ email, password }) => {
    if (email && password) return true
    else return false
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>

// Typing with TS:
<script setup lang="ts">
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

有关Vue3 和 Vue2 的异同及开发中具体区别的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

    请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

  3. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  4. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  5. ruby-on-rails - `a ||= b` 和 `a = b if a.nil 之间的区别? - 2

    我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行

  6. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  7. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  8. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  9. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  10. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

随机推荐