vue3.0环境搭建及特性

一、环境搭建

1. Node官网安装Node,推荐下载长期支持版。

检查是否安装成功Node,在cmd输入 node -v。若出现版本号就是安装成功。

查看node版本号.png

安装完node,会附带npm命令。输入npm -v 就能查看npm的版本。
查看npm版本号

若需要cnpm命令,安装淘宝镜像 npm install -g cnpm --registry=https://registry.npm.taobao.org
安装完成后用cnpm -v查看安装的版本号。
查看cnpm版本号

windows若出现问题——'cnpm' 不是内部或外部命令,也不是可运行的程序或批处理文件。可以根据cmd上显示的cnpm安装的地址,找到cnpm文件夹拷贝到npm文件夹所在的目录。
或 npm config set prefix "npm所在的位置"


cnpm与npm在同一个位置
2. vue环境
1.安装vue和vue-cli脚手架
//最新稳定版(安装和更新的代码相同)
npm install vue@next
npm install -g @vue/cli
//或一起安装
npm install -g vue @vue/cli

保证vue/cli版本在4.5.0以上,才能更好的支持3.0。
用vue -V查看安装的版本号检验是都成功安装。


vue/cli版本号
2. 创建vue项目
npm create  

如果想构建一个webpack的vue项目

npm init webpack  

也可以使用 Vite 快速构建 Vue 项目(Vite是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动)

npm init vite  -- --template vue

安装事项:这里会询问使用哪个模板进行安装,我们选择自定义安装的方法Manually select features


安装方法选择

根据需要选择功能


选择功能

选择vue版本及其他
选择vue版本

image.png

安装完成,系统默认npm run serve运行


安装完成

二、特性

Vue3.0与Vue2.x比,
1、性能方面有很大的提升——打包大小减少41%、初次渲染快55%,更新快133%、内存使用减少54%(得益于虚拟DOM的重写和Tree-shaking的优化)。
2、使用Composition API。Vue2.x使用组件选项来组织逻辑,随着复杂度上升易读性比较差,Vue3.0使用Composition API按照逻辑分类,代码维护和易读性较好。虽然Vue2.x给出Mixin来解决这个问题,但是Mixin会存在命名冲突的问题,不清楚暴露出来变量的最用及重用逻辑到其他Component经常会遇到的问题。
3、对TypeScript的支持更好

Tree-shaking

Vue3最重要的变化之一就是引入了Tree-Shaking,Tree-Shaking带来的bundle体积更小是显而易见的。在2.x版本中,很多函数都挂载在全局Vue对象上,比如nextTick、mixin、set等函数,因此虽然我们可能用不到,但打包时只要引入了vue这些全局函数仍然会打包进bundle中。
而在Vue3中,所有的API都通过ES6模块化的方式引入,这样就能让webpack或rollup等打包工具在打包时对没有用到API进行剔除,最小化bundle体积;
简而言之,Tree-Shaking这个打包工具在打包的时候不会把没用到的API打包进去。

//dog.vue
export function dogName(){
  return 'dogName'
}
export function dogAge(){
  return 'dogAge'
}
//index.vue  没使用dogAge所以dogAge不会被打包进去
import { dogName,dogAge} from './dog'
dogName()

全局API的修改:main.js中能直接发现这样的变化:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

createApp(App).use(store).use(router).mount('#app')

创建app实例方式从原来的new Vue()变为通过createApp函数进行创建;不过一些核心的功能比如virtualDOM更新算法和响应式系统无论如何都是会被打包的;这样带来的变化就是以前在全局配置的组件(Vue.component)、指令(Vue.directive)、混入(Vue.mixin)和插件(Vue.use)等变为直接挂载在实例上的方法;我们通过创建的实例来调用,带来的好处就是一个应用可以有多个Vue实例,不同实例之间的配置也不会相互影响,且可以完美的支持tree-shaking:

const app = createApp(App)
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

Vue 2.x 中的这些全局 API 受此更改的影响:


受影响的API
setup

先说说 选项式API和组合式 API。

选项式API

2.x版本采用的是Options API(选项API)。在 props 里面设置接收参数,在 data 里面设置变量,在 computed 里面设置计算属性,在 watch 里面设置监听属性,在 methods 里面设置事件方法

使用 (data、computed、methods、watch) 组件选项来组织逻辑通常都很有效。然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的。

组合式 API

而Composition API做的就是把同一功能的代码放到一起维护,这样我们需要维护一个功能点的时候,不用去关心其他的逻辑,只关注当前的功能。
实际使用组合式 API 的地方,在 Vue 组件中,我们将此位置称为 setup。也就是将(生命周期、computed、watch、provide/inject)写在 setup 里。
注意:
因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以在生命周期钩子中编写的任何代码都应该直接在 setup 函数中编写。
没有this。因为setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。
setup 选项是一个接收 propscontext 的函数,此外,我们将 setup 返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。

export default {
  name: "Button",
  setup() {
    const state = reactive({
      count: 1,
    });
    const num = ref(2);
    function add() {
      state.count++;
      num.value += 10;
    }
    const double = computed(() => state.count * 2);
    return {
      state,
      double,
      num,
      add,
    };
  },
};
生命周期

在Vue2.x中有8个生命周期函数:
beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed

在vue3中,新增了一个setup生命周期函数,setup执行的时机是在beforeCreate生命函数之前执行,因此在这个函数中是不能通过this来获取实例的;同时为了命名的统一,将beforeDestroy改名为beforeUnmount,destroyed改名为unmounted。增加了onRenderTracked,onRenderTriggered两个钩子函数作追踪调试功能。
同时,我们可以通过在生命周期函数前加on来访问组件的生命周期。


生命周期钩子

vue2->vue3.png

三、新增功能

响应式API

要为 JavaScript 对象创建响应式状态,可以使用 reactive 方法:

import { reactive } from 'vue'

// 响应式状态
const state = reactive({
  count: 0
})

reactive 相当于 Vue 2.x 中的 Vue.observable() API

reactive函数只接收object和array等复杂数据类型。

对于一些基本数据类型,比如字符串和数值等,我们想要让它变成响应式,我们当然也可以通过reactive函数创建对象的方式,但是Vue3提供了另一个函数ref:

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

ref 会返回一个可变的响应式对象,该对象作为一个响应式的引用维护着它内部的值,这就是 ref 名称的来源。该对象只包含一个名为 value 的 property。

当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动浅层次解包内部值。只有访问嵌套的 ref 时需要在模板中添加 .value




注意:reactive主要负责复杂数据结构,而ref主要处理基本数据结构;但是很多童鞋就会误解ref只能处理基本数据,ref本身也是能处理对象和数组的。
区别:在语法层面,两个有差异。ref定义的响应式数据需要用[data].value的方式进行更改数据;reactive定义的数据需要[data].[prpoerty]的方式更改数据。
但是在应用的层面,还是有差异的,通常来说:单个的普通类型的数据,我们使用ref来定义响应式。表单场景中,描述一个表单的key:value这种对象的场景,使用reactive;在一些场景下,某一个模块的一组数据,通常也使用reactive的方式,定义数据。

访问响应式对象

vue2.0中,通过vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。因为Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

vue3.0,当 ref 作为响应式对象的 property 被访问或更改时,为使其行为类似于普通 property,它会自动解包内部值:

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

如果将新的 ref 赋值给现有 ref 的 property,将会替换旧的 ref:

const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1

当我们处理一些大型响应式对象的property时,我们很希望使用ES6的解构来获取我们想要的值:

let book = reactive({
  name: 'Learn Vue',
  year: 2020,
  title: 'Chapter one'
})
let {
  name,
} = book

name = 'new Learn'
console.log(book.name);// Learn Vue

遗憾的是,使用的 property 的响应性丢失。对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:

let book = reactive({
  name: 'Learn Vue',
  year: 2020,
  title: 'Chapter one'
})
let {
  name,
} = toRefs(book)

// 注意这里解构出来的name是ref对象
// 需要通过value来取值赋值
name.value = 'new Learn'
console.log(book.name);// new Learn

使用 readonly 防止更改响应式对象

let book = reactive({
  name: 'Learn Vue',
  year: 2020,
  title: 'Chapter one'
})
const copy = readonly(book);
//Set operation on key "name" failed: target is readonly.
copy.name = "new copy";
响应式计算和侦听
计算值

有时我们需要的值依赖于其他值的状态。在vue2.x中我们使用computed函数来进行计算属性,在vue3中将computed功能进行了抽离,它接受一个getter函数,并为getter返回的值创建了一个不可变的响应式ref对象:

const num = ref(0);
const double = computed(() => num.value * 2);
num.value++;

console.log(double.value);// 2
double.value = 4// Warning: computed value is readonly

或者,它可以使用一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

const num = ref(0);
const double = computed({
  get: () => num.value * 2,
  set: (val) => (num.value = val / 2),
});

num.value++;
console.log(double.value);// 2

double.value = 8
console.log(num.value);// 4
侦听

和computed相对应的就是watch,computed是多对一的关系,而watch则是一对多的关系;vue3也提供了两个函数来侦听数据源的变化:watch和watchEffect。

watch,它的用法和组件的watch选项用法完全相同,它需要监听某个数据源,然后执行具体的回调函数:

import { reactive, ref, watch } from "vue";

const state = reactive({
  count: 0,
});

//侦听时返回值得getter函数
watch(
  () => state.count,
  (count, prevCount) => {
    console.log(count, prevCount);  // 1 0
  }
);
state.count++;

const count = ref(0);
//直接侦听ref
watch(count, (count, prevCount) => {
  console.log(count, prevCount, "watch");// 2 0
});
count.value = 2;

如果我们来侦听一个深度嵌套的对象属性变化时,需要设置deep:true。

一般侦听都会在组件销毁时自动停止,但是有时候我们想在组件销毁前手动的方式进行停止,可以调用watch返回的stop函数进行停止。

const count = ref(0);

const stop = watch(count, (count, prevCount) => { 
  console.log(count, prevCount);// 打印1 0,2 1 后不执行
});

setInterval(()=>{
   count.value++;
}, 1000);
// 停止watch
setTimeout(()=>{
   stop();
}, 2500);

还有一个函数watchEffect也可以用来进行侦听,但是都已经有watch了,这个watchEffect和watch有什么区别呢?他们的用法主要有以下几点不同:
1、watchEffect不需要手动传入依赖
2、每次初始化时watchEffect都会执行一次回调函数来自动获取依赖
3、watchEffect无法获取到原值,只能得到变化后的值

const count = ref(0);
const state = reactive({
  year: 2021,
});

watchEffect(() => {
  console.log(count.value);
  console.log(state.year);//0 2021,1,2022...
});
watch([() => state.year, count], (newVal, oldVal) => {
  //[2022, 1]  [2021, 0]
  //[2023, 2]  [2022, 1]...
  console.log(newVal, oldVal);
});
setInterval(() => {
  count.value++;
  state.year++;
}, 1000);
Fragment

所谓的Fragment,就是片段;在vue2.x中,要求每个模板必须有一个根节点,所以我们代码要这样写:


或者在Vue2.x中还可以引入vue-fragments库,用一个虚拟的fragment代替div;在React中,解决方法是通过的一个React.Fragment标签创建一个虚拟元素;在Vue3中我们可以直接不需要根节点:


Teleport

可以将插槽中的元素或者组件传送到页面的其他位置。就是在一些嵌套比较深的组件来转移模态框的位置。虽然在逻辑上模态框是属于该组件的,但是在样式和DOM结构上,嵌套层级后较深后不利于进行维护(z-index等问题);因此我们需要将其进行剥离出来:





  
    
    
    
    
    
  
  
    
    
//teleport组件将加载在这里
Suspense

Suspense是Vue3推出的一个内置组件,它允许我们的程序在等待异步组件时渲染一些后备的内容,可以让我们创建一个平滑的用户体验;Vue中加载异步组件其实在Vue2.x中已经有了,我们用的vue-router中加载的路由组件其实也是一个异步组件:

export default {
  name: "Home",
  components: {
    AsyncButton: () => import("../components/AsyncButton"),
  },
}

在Vue3中重新定义,异步组件需要通过defineAsyncComponent来进行显示的定义:

// 全局定义异步组件
//src/main.js
import { defineAsyncComponent } from "vue";
const AsyncButton = defineAsyncComponent(() =>
  import("./components/AsyncButton.vue")
);
app.component("AsyncButton", AsyncButton);


// 组件内定义异步组件
// src/views/Home.vue
import { defineAsyncComponent } from "vue";
export default {
  components: {
    AsyncButton: defineAsyncComponent(() =>
      import("../components/AsyncButton")
    ),
  },
};

同时对异步组件的可以进行更精细的管理:

export default {
  components: {
    AsyncButton: defineAsyncComponent({
      delay: 100,
      timeout: 3000,
      loader: () => import("../components/AsyncButton"),
      errorComponent: ErrorComponent,
      onError(error, retry, fail, attempts) {
        if (attempts <= 3) {
          retry();
        } else {
          fail();
        }
      },
    }),
  },
};

这样我们对异步组件加载情况就能掌控,在加载失败也能重新加载或者展示异常的状态。

四、非兼容的功能

非兼容的功能主要是一些和Vue2.x版本改动较大的语法,在Vue3上可能存在兼容问题。

v-model

非兼容:用于自定义组件时,v-model prop 和事件默认名称已更改:
prop:value -> modelValue;
event:input -> update:modelValue;
非兼容:v-bind 的 .sync 修饰符和组件的 model 选项已移除,可用 v-model 作为代替;
新增:现在可以在同一个组件上使用多个 v-model 进行双向绑定;
新增:现在可以自定义 v-model 修饰符。






v-if 与 v-for 的优先级对比

2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用
3.x 版本中 v-if 总是优先于 v-for 生效。

v-bind合并

在 2.x,如果一个元素同时定义了 v-bind="object" 和一个相同的单独的 property,那么这个单独的 property 总是会覆盖 object 中的绑定。


在 3.x,如果一个元素同时定义了 v-bind="object" 和一个相同的单独的 property,那么声明绑定的顺序决定了它们如何合并。换句话说,相对于假设开发者总是希望单独的 property 覆盖 object 中定义的内容,现在开发者对自己所希望的合并行为有了更好的控制。


v-for中ref

在 Vue 2 中,在 v-for 中使用的 ref attribute 会用 ref 数组填充相应的 $refs property。当存在嵌套的 v-for 时,这种行为会变得不明确且效率低下。



在 Vue 3 中,此类用法将不再自动创建 $ref 数组。要从单个绑定获取多个 ref,请将 ref 绑定到一个更灵活的函数上 (这是一个新特性):



Data

非兼容:组件选项 data 的声明不再接收纯 JavaScript object,而是接收一个 function。
非兼容:当合并来自 mixin 或 extend 的多个 data 返回值时,合并操作现在是浅层次的而非深层次的 (只合并根级属性)。

在 2.x 中,开发者可以通过 object 或者是 function 定义 data 选项。但是我们知道在组件中如果data是object的话会出现数据互相影响,因为object是引用数据类型。
在Vue3中,data只接受function类型,通过function返回对象;同时Mixin的合并行为也发生了改变,当mixin和基类中data合并时,会执行浅拷贝合并:

const Mixin = {
  data() {
    return {
      user: {
        name: 'Jack',
        id: 1
      }
    }
  }
}

const CompA = {
  mixins: [Mixin],
  data() {
    return {
      user: {
        id: 2
      }
    }
  }
}
//2.x
{
  "user": {
    "id": 2,
    "name": "Jack"
  }
}
//3.x
{
  "user": {
    "id": 2
  }
}

你可能感兴趣的:(vue3.0环境搭建及特性)