Vue3-初识Vue3、创建Vue3工程、vue3组合式API(setup、ref函数、reactive函数)、响应式原理、计算属性、监视属性

Vue3(1)

目录

一、Vue3简介

2020年9月18日,Vue.js发布了3.0版本,代号:One Piece(海贼王)

耗时两年多,2600+次提交、30+个RFC、600+次PR、99位贡献者

Vue3带来了什么:

1、性能上的提升

  • 打包大小减少42%
  • 初次渲染块55%、更新渲染块133%
  • 内存减少54%

2、源码的升级

  • 使用Proxy代替defineProperty实现响应式
  • 重写虚拟DOM的实现和Tree-Sgaking

3、拥抱TypeScript

  • Vue3可以更好的支持TypeScript

4、新特性

(1)Composition API(组合API)

  • setup配置
  • ref与reactive
  • watch与watchEffect
  • provide与inject

(2)新的内置组件

  • Fragment
  • Teleport
  • Suspense

(3)其他改变

  • 新的生命周期钩子
  • data选项应始终被声明为一个函数
  • 移除keyCode支持作为v-on的修饰符

二、创建Vue3.0工程

1、使用vue-cli创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

js 复制代码
// 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
// 安装或者升级你的@vue/cli
npm install -g @vue/cli
// 创建
vue create vue_test
// 启动
cd vue_test
npm run serve

2、使用vite创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

  • 什么是vite?------新一代的前端构建工具

  • 优势如下:

    1.开发环境中,无需打包操作,可快速冷启动

    2.轻量快速的热重载(HMR)

    3.真正的按需编译,不再等待整个应用编译完成

js 复制代码
// 创建工程
npm init vite-app <project-name>
// 进入工程目录
cd <project-name>
// 安装依赖
npm install
// 运行
npm run dev

三、常用的Composition API(组合式API)

1、拉开序幕的setup

之前我们在vue2中都是多个配置项组合在一起(选项式API:data、method那些),在vue3中,换了一种方式,那就是Composition API(组合式API),而setup则是组合式API表演的舞台,组件中所有的数据和方法都写在setup函数中。

这个setup函数其实有两种返回值:

  1. 返回一个对象

  2. 返回一个渲染函数

用的最多的就是返回一个对象,把用到的所有属性、方法都返回出去,方便在模板中使用(当然,现在有了setup语法糖,后面再说)

html 复制代码
<template>
  <h1>个人信息</h1>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <button @click="sayHello">打招呼</button>
</template>

<script>
import { h } from "vue";
export default {
  name: "App",
  // 此处只是测试一下setup,暂时不考虑响应式的问题
  setup() {
    // 数据
    let name = "potato";
    let age = 18;

    // 方法
    function sayHello() {
      alert(`我叫${name},我${age}岁了`);
    }
      
    // 返回一个对象
    return {
      name,
      age,
      sayHello,
    };

    // 返回一个渲染函数
    // return () => {return h('h1','哈哈哈哈')};
  },
};
</script>

注意:不要把setup和vue2中的选项式API混用,因为setup中访问不到vue2中的属性方法(但2可访问3),如果混用的话,有重名setup优先。

还有,setup前不要加async,因为返回值会变成promise,而不是我们return的那些属性和方法

2、ref函数

还记得vue2的ref属性吗,vue2的ref类似于id。复习Vue2的ref属性

而Vue3的ref是一个函数。

作用:定义响应式的数据。

例:点击按钮修改信息

html 复制代码
<template>
  <h1>个人信息</h1>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <button @click="changeInfo">修改信息</button>
</template>

<script>
import { ref } from "vue";
export default {
  name: "App",
  setup() {
    // 数据
    let name = ref("potato");
    let age = ref(18);

    // 方法
    function changeInfo() {
      // 直接这样写的话是不行的,因为ref把数据包装成了对象,对其操作时要加上'.value'
      // name= "李四";
      // age= 13;
      // console.log(name, age);
      name.value = "李四";
      age.value = 13;
    }

    // 返回一个对象
    return {
      name,
      age,
      changeInfo,
    };
  },
};
</script>

const xxx = ref(initValue)创建了一个包含响应式数据的引用对象(RefImp对象

在js中操作数据:count.value

在模板中读取数据:<div> {``{count}} </div>(这里会自动读取value属性)

其实ref可以接受的数据不只是基本数据类型,复杂数据类型也可以,只不过比较麻烦,操作时都要.value

html 复制代码
<template>
  <h1>个人信息</h1>
  <h3>职业:{{ job.type }}</h3>
  <h3>工作地点:{{ job.address }}</h3>
  <button @click="changeInfo">修改信息</button>
</template>

<script>
import { ref } from "vue";
export default {
  name: "App",
  setup() {
    // 数据
    let job = ref({
      type: "前端工程师",
      address: "广州",
    });

    // 方法
    function changeInfo() {;
      job.value.type = "后端工程师";
      job.value.address = "深圳";
    }

    // 返回一个对象
    return {
      job,
      changeInfo,
    };
  },
};
</script>

基本数据类型和对象数据类型实现响应式的方式不同,前者依然是Object.defineProperty()的get与set,后者则在内部求助了reactive函数

3、reactive函数

作用:定义一个对象类型的响应式数据(基本类型不要用reactive,要用ref)

js 复制代码
<script>
import { ref, reactive } from "vue";
export default {
  name: "App",
  setup() {
    // 数据
    let job = reactive({
      type: "前端工程师",
      address: "广州",
    });
    // 数组也可以
    let hobby = reactive(["吃饭", "睡觉", "打豆豆"]);

    // 方法
    function changeInfo() {
      job.type = "后端工程师";
      job.address = "深圳";
      hobby[2] = "学习";
    }

    // 返回一个对象
    return {
      job,
      changeInfo,
      hobby,
    };
  },
};
</script>

const 代理对象 = reactive(源对象)接收一个对象或数组,返回一个代理对象(Proxy对象)

  • reactive定义的响应式数据时"深层次的"
  • 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作

这里要注意,只有改变job中的某个属性,才可以实现响应式,如果直接改job,会报警告告诉你job是只读(read-only)的。如果要改job,需要Object.assign(job, newObj);或者job= { ...job, ...newProperties };

4、Vue3中响应式原理

(1)Vue2中响应式原理

在了解Vue3响应式原理之前,咱先来复习下vue2响应式。

我们知道vue2中是通过Object.defineProperty()对对象类型数据进行劫持并添加getter和setter来监视数据的,对于数组是重写了更新数组的方法。 复习vue2监视数据的原理

vue2监视数据存在两个问题:

  1. 直接新增属性、删除属性, 界面不会更新。
  2. 直接通过下标修改数组, 界面不会自动更新。

这两个问题可以通过$set$deletesplice(数组变更方法)解决

(2)Vue3中的Proxy

在vue3中,解决了vue2中的两个问题。

html 复制代码
<button @click="addSex">添加一个sex属性</button>
<button @click="deleteName">删除一个name属性</button>
js 复制代码
    function addSex() {
      // 如果是vue2,要这样写this.$set(this.person, 'sex', '女');
      person.sex = "女";
    }
    function deleteName() {
      // 如果是vue2,要这样写this.$delete(this.person, 'name');
      delete person.name;
    }

下面是模拟实现vue3中响应式的原理:

js 复制代码
<script>
  let person = {
    name: "potato",
    age: 18,
  };
  // 模拟Vue3实现响应式
  const p = new Proxy(person, {
    // 有人在读取p的某个属性时调用
    get(target, propName) {
      console.log(`有人读取了p身上的${propName}属性`);
      return target[propName];
    },
    // 有人在修改p的某个属性、或给p追加某个属性时调用
    set(target, propName, value) {
      console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`);
      target[propName] = value;
    },
    // 有人在删除p的某个属性时调用
    deleteProperty(target, propName) {
      console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`);
      return delete target[propName];
    },
  });
</script>

再严谨一些,用Reflect(反射)来进行增删改查,这也是框架的做法,其实本质上没什么太大的区别,只是Reflect可以更好捕捉错误,避免一些报错吧,不然还要用try-catch去捕获。这里不用太纠结,其实就是为了让框架更加的友好(少一些报错)

  • Proxy(代理)+Reflect(反射)
js 复制代码
<script>
  let person = {
    name: "potato",
    age: 18,
  };
  // 模拟Vue3实现响应式
  const p = new Proxy(person, {
    // 有人在读取p的某个属性时调用
    get(target, propName) {
      console.log(`有人读取了p身上的${propName}属性`);
      return Reflect.get(target, propName);
    },
    // 有人在修改p的某个属性、或给p追加某个属性时调用
    set(target, propName, value) {
      console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`);
      Reflate.set(target, propName, value);
    },
    // 有人在删除p的某个属性时调用
    deleteProperty(target, propName) {
      console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`);
      return Reflate.deleteProperty(target, propName);
    },
  });
</script>
(3)Vue3响应式原理总结⭐

对于refreactive是不一样的

ref简单类型是通过:Object.defineProperty()的get与set ,当然啊,ref定义的复杂类型是通过:reactive的Proxy

reactive是通过Proxy来实现响应式 的(上文提到了),并通过Reflect来操作源数据

不管怎么样,总结来说,vue3中新增的就是对于复杂数据类型通过Proxy实现响应式,也就是两个点:

  1. 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性的增删、属性值的读写等。
  2. 通过Reflect(反射): 对源对象的属性进行上述操作。
js 复制代码
new Proxy(person, {
    // 拦截读取属性值
    get(target, propName) {
      return Reflect.get(target, propName);
    },
    // 拦截设置属性值或添加属性值
    set(target, propName, value) {
      Reflate.set(target, propName, value);
    },
    // 拦截删除属性
    deleteProperty(target, propName) {
      return Reflate.deleteProperty(target, propName);
    },
});

5、reactive和ref的对比

(1)定义数据类别不同

  • ref用于定义基本类型数据
  • reactive用于定义对象或数组类型数据
  • 备注:ref也可以定义对象或数组类型数据 ,它内部会自动通过reactive转为代理对象

(2)原理不同

  • ref是通过:Object.defineProperty()getset,当然啊,复杂类型是通过reactive的Proxy
  • reactive是通过Proxy来实现响应式的(上文提到了),并通过Reflect来操作源数据

(3)使用方式不同

  • ref定义的数据,操作时需要.value模板读取不需要.value
  • reactive定义的数据,操作和读取都不需要.value

6、setup的两个注意点

(1)执行时机

setup函数的执行时机是beforeCreate之前,也就是所有生命周期的最前面 ,此时this是undefined,也就是说在setup中是绝对不能通过this访问组件实例的

注意这里的beforeCreate是写在setup外面的 ,如果在setup里面,是没有beforeCreate和created这两个钩子的,因为setup的执行时机就相当于这两个钩子(setup会先于所有的钩子执行,把想在这两个钩子里写的代码写到steup中最前面就行了

(2)setup的参数

setup接收两个参数:(props,context)

  1. props是一个对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
  2. context是一个对象,包含三个属性,分别是attrs、slots、emit
  • 第一个attrs相当于this.$attrs,值为对象,包含组件外部传递过来,但没有在props配置中声明的属性
  • 第二个slots相当于this.$slots,包含收到的插槽的内容。
  • 第三个emit相当于this.$emit,用来触发组件的自定义事件。
js 复制代码
props: ['name', 'age']
setup(props, context) {
  console.log(props) // Proxy{name:'potato',age:18}:组件外部传递过来,且组件内部声明接收了的属性。
  console.log(context.attrs)//相当于this.$attrs
  console.log(context.slots)相当于this.$slots
  console.log(context.emit)//相当于this.$emit
}

四、计算函数和监视函数

1、计算函数(computed)

和vue2中功能是一样的,只不过变成了一个函数,而且要手动引入。默认写法是传一个回调

如果我们只使用简写的话,当我们去修改计算属性值的时候,控制台会有警告,告诉我们这个属性值是只读的。

这是因为计算属性默认是只读的,当它所依赖的值改变的时候,它自己会变。如果要想改计算属性,需要用下面的完整写法,传一个对象,里面有gettersettersetter中参数是修改后的新值

html 复制代码
<template>
  <h1>个人信息</h1>
  姓:<input type="text" v-model="person.firstName" />
  <br />
  名:<input type="text" v-model="person.lastName" />
  <br />
  全名:<input type="text" v-model="person.fullName" />
  <br />
  <span>全名:{{ person.fullName }}</span>
</template>

<script>
import { computed, reactive } from "vue";
export default {
  setup() {
    let person = reactive({
      firstName: "张",
      lastName: "三",
    });

    // 计算属性-简写(没有考虑计算属性被修改的情况)
    // person.fullName = computed(function () {
    //   return person.firstName + "-" + person.lastName;
    // });

    // 计算属性-完整(包括读和写)
    person.fullName = computed({
      get() {
        return person.firstName + "-" + person.lastName;
      },
      set(value) {
        const nameArr = value.split("-");
        person.firstName = nameArr[0];
        person.lastName = nameArr[1];
      },
    });

    return {
      person,
    };
  },
};
</script>

2、监视函数(watch)

其实watch在vue3中和vue2中功能是一样的,就是写法不一样。

vue2的监听函数写法点此复习vue2监听函数

先准备一些数据吧:

js 复制代码
import { ref, reactive } from 'vue'
const sum = ref(0)
const msg = ref('hello')
const person = reactive({
	name: 'potato'
	age: 18
	job: {
		type: 'code'
		salary: 10
	}
})
(1)第一个参数怎么写
  • 情况一:vue3监视ref所定义的一个响应式数据
js 复制代码
watch(sum, (newVal, oldVal) => {
	console.log("sum的值变了", newVal, oldVal);
});
  • 情况二:vue3监视ref所定义的多个响应式数据
js 复制代码
watch([sum, msg], (newVal, oldVal) => {
	console.log("sum或msg变化了", newVal, oldVal);//new和old也是监听值构成的数组
});
  • 情况三:监视reactive定义的响应式数据
    这里有两个坑,第一个是reactive定义的数据,监视时回调中无法获得oldValue!oldValue和newValue一样
    第二个坑是,监视reactive定义的数据,默认开启的deep:true,且deep不能改成false
js 复制代码
watch(person, (newVal, oldVal) => {
	console.log("person变化了");
},{immediate:true,deep:false});
  • 情况四:监视reactive定义的响应式数据中的某个属性
    这里要注意,第一个参数必须写成箭头函数 ,如果直接写person.job,那么就相当于写了个死的值,这样是监视不到的。还有就是如果job是一个对象 ,那么默认deep是false 的,如果要深度监视需要手动开启deep:true(deep配置有效)
js 复制代码
watch(()=>person.job,(newValue,oldValue)=>{
	console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true}) 
  • 情况五:监视reactive定义的响应式数据中的某些属性
    如果这种情况的话和上面类似,不同的是第一个参数写成数组,且每个元素是箭头函数,返回的new和old值也是相对应的值构成的数组。
js 复制代码
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
	console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
(2)第一个参数到底写不写.value

如果我们把person换成是一个ref定义的呢?那么监视的时候写不写.value

js 复制代码
import { ref, reactive } from 'vue'
const sum = ref(0)
const msg = ref('hello')
const person = ref({
	name: 'dantin'
	age: 18
	job: {
		type: 'code'
		salary: 10
	}
})

答案是:要写的,因为ref对于复杂数据类型,内部是借助reactiveProxy实现响应式的,所以这么写的话就相当于是写了一个reactive定义的响应式数据,在监视时也就具有了对应的坑(见上文情况三)

js 复制代码
watch(person.value,(newValue,oldValue)=>{
	console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不奏效

除此之外还有一种办法,那就是深度监视person(它是一个RefImpl对象),这样就能监视到其中的value及更深层的变化。这是因为如果直接监视person不读取.value,那么监视的是RefImpl对象,只有其中value的地址变的时候才能监视到,value里面的东西变化是监视不到的,所以要开deep

js 复制代码
watch(person,(newValue,oldValue)=>{
	console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:true}) 

那为什么简单数据类型不需要.value呢?其实和上面的情况四是一样的,如果简单数据类型直接.value,那么监视的就是一个写死的值。不.value的话,监视的是一个响应式的RefImpl对象,当里面value变化的时候是可以监视到的

js 复制代码
watch(sum,(newValue,oldValue)=>{
	console.log('sum变化了',newValue,oldValue)
})

如果非要.value,请使用箭头函数动态读取,每次sum变化都会执行回调读取最新的值

js 复制代码
watch(() => sum.value,(newValue,oldValue)=>{
	console.log('sum变化了',newValue,oldValue)
})

3、watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调
  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

watchEffect有点像computed,但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

js 复制代码
//watchEffect的回调一上来会先执行一次
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(() => {
  const x1 = sum.value;
  const x2 = person.job.salary;
  console.log("watchEffect所指定的回调执行了");
});
相关推荐
Martin -Tang13 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发14 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html