Vue3.0学习笔记 开发者工具安装-响应式原理-生命周期-组合式API

Vue3.0 学习笔记

创建Vue3.0工程

使用vue-cli创建

需要@vue/cli版本在4.5.0以上

# 查看版本
vue --version
# 安装或升级@vue/cli
npm install -g @vue/cli
# 创建vue项目
vue create vue-test
# 启动
cd vue_test
npm run serve

使用vite创建

vite --> 新一代前端构建工具

优势

  1. 开发环境中,无需打包操作,可以快速的冷启动
  2. 轻量快速的热加载(HMR)
  3. 真正的按需编译,不再等待整个应用编译完成

在这里插入图片描述

# 安装vite
npm install -g create-vite-app

# 创建工程
npm init vite-app <project-name> # 我忘记安装vite了,这里可以根据提示信息安装
Need to install the following packages:
create-vite-app
Ok to proceed? (y)
Done. Now run:
cd vue_test
npm install (or `yarn`)
npm run dev (or `yarn dev`)

# 进入工程目录
cd <project-name>

# 安装依赖
# cnpm install; 会快很多
npm install

# 运行
npm run dev
Dev server running at:
> Network:  http://192.168.20.1:3000/
> Network:  http://192.168.10.1:3000/
> Network:  http://192.168.43.237:3000/
> Local:    http://localhost:3000/

先使用vue-cli,之后在学vite相关知识点

分析工程结构

main.js

/*
引入了createApp的工厂函数,不使用new创建可以直接调用
import Vue from 'vue'; vue2
*/
import { createApp } from 'vue'
import App from './App.vue'

/*
new Vue({
	render: h => h(App)
}).$mount('#app')
*/
//createApp(App).mount('#app')可以拆成两个
//创建应用实例对象app,类似vm,但app更轻
const app = createApp(App)
//挂载
app.mount('#app')

vue3组件中的模板结构可以没有根标签

  <template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

vue3的开发者工具

下载链接
在这里插入图片描述

常用 Composition组合式 API

组合式 API,使用前先引用。基本都是函数,参数也是函数。
根据需要引用的不同的封装好了的函数

vue2 : Option API 配置式的API
vue3: Composition API 组合式API

vue2的不足:当修改或新增需求,需要分别在data,methods,computed里修改
在这里插入图片描述
vue3:可以更加优雅的组织代码和函数,让相关功能的代码更加有序的组织在一起。在这里插入图片描述

拉开序幕的setup

是什么?
Vue3.0中一个新的配置项,值为一个函数
组件中使用的数据、方法、生命周期钩子等,都要配在setup中

setup函数的两种返回值
1.若返回一个对象,则对象中的属性、方法,在模板中均可直接使用 ★
2.若返回一个渲染函数,则可以自定义渲染内容(了解)

补充:setup不能是async函数,因为返回值不是return对象了,而是promise,模板看不见promise对象中的属性

//返回对象(常用)
<template>
 {{name}}
 {{say()}}
</template>

<script>
export default {
  name: 'App',
  setup() {//这里定义的数据不是响应式数据
    //数据
    let name="ranan";
    //方法
    function say() {
      console.log(`hello${name}`);
    }
    return {name,say}
  }
}
</script>

//返回一个渲染函数
<template></template>

<script>
import {h}  from 'vue' //createElement函数
export default {
  name: 'App',
  setup() {   
    //渲染函数要把h函数调用的返回值返回
    //return ()=>{return h('h1','ranan')}
    return ()=> h('h1','ranan')
  }
}
</script>

vue2和vue3配置混用–最好不要混用!

  • vue2.x的配置(data、methos、computed…)中可以访问到setup中的属性
  • setup中不能访问到Vue2.x配置,setup调用时,data已经被挂载在vm/vc上了
  • 如果重名,setup优先
setup的两个注意点
  • setup执行的时间
    • 在beforeCreate之前执行一次,this是undefined
  • setup可以接受两个参数
    • props 该组件的props对象的值,把props接受的值整理成一个Proxy对象。
    • context 上下文对象
      • attrs 相当于vue2vc组件上的$attrs,规则也是一样的
      • slots 相当于vue2vc组件上的$slots,规则也是一样的
      • emit 用于触发自定义事件,相当于$emit,配合emits属性(类似props)表示接受自定义事件

ref函数

ref函数:给数据加工使其具有响应式,返回值是一个RefImpl引用对象(引用实现的实例对象),也叫做Ref对象

ref响应式的原理是使用数据劫持(第一层): defineProperty中的getter,setter
在这里插入图片描述

RefImpl:reference引用的implement实现

数据的获取使用属性名.value获取,但是模板中直接使用属性名,模板中会自动.value

<template>
{{a}}
</template>
import {ref} from ’vue‘;
<script>
export default {
  name: 'App',
  setup() {//这里定义的数据不是响应式数据
    //数据
    let name=ref("ranan");
    function changeName(){
		name.value = "xxx"
	}
  }
}
</script>

ref函数处理对象

job本身还是RefImpl引用对象,job.value进行加工返回Proxy代理对象A,此代理对象代理了源对象,通过对代理对象的操作间接操作源对象

将对象里的属性直接抛出时,响应式会失效,在模板中修改该属性是不会触发重新渲染。
原因是:暴露出去的数据是普通对象

setup() {
   let job = ref({
   //!!!!!!!!!!!!!!!!!!!重要
   //ref本身是通过getter,setter有响应式的,如果抛出job,替换job是有响应式
   //job.value是通过proxy进行响应式的
       type:'学生',
       salary:'0'
	})
	console.log(job.value);
	/*
	Proxy{
    "type": "学生",
    "salary": "0"
	}
	*/
	return {
		//这里相当于抛出普通值0
		job.value.salary; //模板中修改该属性,不会引起响应式
	}
}
总结

ref函数的作用:定义一个响应式的数据
语法:const xxx = ref (initValue)

  • 创建一个包含响应式数据的引用对象(reference对象)
  • JS中操作数据: xxx.value
  • 模板中读取数据:不需要.value,直接{{属性名}}
  • 基本类型的数据:响应依靠Object.defineProperty()的get与set函数
  • 对象类型的数据:内部求助了Vue3.0中的一个新函数reactive函数

reactive函数

作用:定义一个对象类型的响应式数据
语法:const 代理对象 = reactive(源对象)接受一个对象(或数组),返回一个Proxy的实例对象
说明: reactive定义的响应式数据是深层次的,内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作

import {reactive} from 'vue';
export default {
  name: 'App',
  setup() {   
     let job = reactive({
       type:'学生',
       salary:'0'
    	})  
    	console.log(job);
    	/*
		Proxy {type: '学生', salary: '0'}
		*/	
  }
}
reactive与ref的对比

reactiveref
定义数据对象类型数据基本类型数据
如果定义对象,.value会借住reactive将对象转为代理对象
原理Proxy实现响应式(数据劫持)
通过Reflect操作源对象内部的属性
通过Object.defineProperty()的get和set来实现响应式(数据劫持)
使用不需要使用.value操作数据需要.value,模板中读取不需要.value

Vue3中的响应式原理

Vue2的响应式

对象类型:通过Object.defineProperty()对属性的读取、修改进行拦击-数据劫持
数组类型:通过重写更新数组的一系列方法来实现拦截的

存在的问题

  1. 新增属性、删除属性、界面不会更新
    解决:通过vm.$set(obj,key.val)/vm.$delete(obj,key)新增属性/删除属性
  2. 直接通过下标修改数组,界面不会自动更新
    解决:vm.$set(arr,index.value),调用数组的splice方法
Vue3的响应式

实现原理

  • 通过Proxy代理:拦截对象中任意属性的改变
  • 通过Reflect反射:对被代理对象的属性进行操作
//通过修改代理对象p来简介操作源对象person
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}属性,我要去更新界面了!`)
		Reflect.set(target,propName,value)
	},
	//有人删除p的某个属性时调用
	deleteProperty(target,propName){
		console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
	   return Reflect.deleteProperty(target,propName)
	}
})

计算属性与监视

computed函数

计算属性不是一个对象,而是一个函数

import {computed} from 'vue'
setup(){
	//计算属性-简写内部是函数形式,只符合被读取的情况
	let fullName = computed(()=>{
		return person
	})
	//计算属性-完整写法
	let fullName = computed({
		get(){},
		set(val){}
	})
}
watch函数

watch的参数
1.监视的数据
2.该数据绑定的回调函数
3.vue2的配置参数

import {ref,reacitve,computed} from 'vue'
setup(){
  let sum = ref(0)
  let msg= ref("hello")
  let person= reactive({
	name:"ranna",
	age:18,
	job:{
		j1:{
			salary:20
		}
	}
  })
  //情况1:监视ref所定义的响应式数据,第三个参数是类似vue2的配置参数
  watch(sum,(newvalue,oldValue)=>{},{immediate:true,deep:true})
  //情况2:监视ref所定义的多个响应式数据
  watch([sum,msg],(newvalue,oldValue)=>{
	//此时newvalue和oldValue都是数组,里面的顺序和第一个参数的顺序一致
  })
  //情况3:监视reacitve,当person里面的属性发生变化时可以检测到,常用
  watch(person,(newValue,oldValue)=>{ 
		//!!!!!!此时无法正确的获取oldValue,oldValue和newValue的值一样了
	})
  //情况4:监视reactive定义的响应式数据中的某个属性
   watch(()=>person.name,(newValue,oldValue)=>{ 
		//此时可以获取到oldValue
	})
	//情况5:监视reactive定义的响应式数据中的某些属性
	//()=> [person.name,person.age]也可以
	watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{ 
		//person.name和person.age是基本数据类型,此时可以获取到oldValue
	})
	//特殊情况:监视reactive定义的响应式数据中的某个属性时,修改job属性里面的salary时需要开启深拷贝,deep有效 
	watch(()=>person.job,(newValue,oldValue)=>{ 
		//person.job是对象,此时获取不到oldValue
	}.{deep:true})
	return {person}
}

**注意这里有坑!!! **
1.监视reacitve定义的响应式数据的全部属性时,当person里面的属性发生变化时可以检测到,此时无法正确的获取oldValue,oldValue和newValue的值一样了,因为对象里面的数据发生了改变,但是对象是一个地址,地址没有发生改变 --> 监视的是基本数据类型就可以获取到oldValue
2.监视reactive定义的响应式数据的全部属性时,强制开启了深度监视deep配置无效 --> 对于对象的第一层,深度监视强制开启,如果对象里面还有对象,那么deep配置生效需要手动配置

watch监视ref对象时value问题

当ref的参数是对象时,ref对象采用的defineProperty,进行了一层的数据劫持,对比是否发生修改是比较的.value地址值,如果修改对象里面的数据是不会被发现修改了的

  let sum = ref(0);
  let person= reactive({
	name:"ranna",
	age:18,
	job:{
		j1:{
			salary:20
		}
	}
  })
  //console.log(sum);//ref对象,ref.value值是基本类型
  console.log(person);//ref对象,ref.value值是Proxy对象
  //console.log(sum.value);//0
  //这里监视时sum变量也就是ref对象,写成sum.value监视的是0这个值是不可以的
  watch(sum,(newvalue,oldValue)=>{}); 
  //person是ref对象,ref对象采用的defineProperty,对比是否发生修改是比较的.value地址值,如果修改对象里面的数据是不会被发现修改了的
  watch(person,(newvalue,oldValue)=>{});  //无效
  watch(person,(newvalue,oldValue)=>{ //有效,defineProperty只监视一层,类似vue2,开启深度监视就可以了
    console.log(newvalue);
  },{deep:true})
  watch(person.value,(newvalue,oldValue)=>{ //有效,这里的person.value = reacitve()函数返回的,也就是监视的是reacitve定义的对象
    console.log(newvalue);
  })
  
watchEffect函数 监视使用了的数据
  • watch的套路是: 既要指明监视的属性,也要指明监视的回调
  • watchEffect的套路是: 不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性。监视的数据发生变化时执行回调函数。

watchEffect函数有点像computed函数

  • computed注重的是根据已有值计算出来新的值,所以必须要写返回值
  • watchEffect注重过程(回调函数的函数体),所以不用写返回值
//一上来就会执行
watchEffect(()=>{
	
})

生命周期

vue2:beforeCreatecreatedbeforeMountmountedbeforeDestroydestroyed
vus3:beforeCreatecreatedbeforeMountmountedbeforeUnmountunmounted
在这里插入图片描述
写在setup函数外面按照vue2的配置项形式写
如果通过配置项的写法,setup()在beforeCreate()之前执行一次

export default{
	name:'Demo',
	setup(){},
	beforeCreate(){},
	created(){},
	//....
}

使用Composition组合式API形式在setup函数里使用生命周期钩子
beforeCreatecreated 钩子没有提供组合API的形式
执行顺序:相同的组合式API会先于配置项

  • beforeCreate -> setup()
  • created -> setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeMount -> onBeforeMount
  • unmounted -> onUnmounted
import {onMounted} from 'vue' //按需引用
export default{
	name:'Demo',
	setup(){
		onBeforeMount(()=>{})
	}
}

自定义hook函数 复用代码

**什么是hook? **
本质是一个函数,把setup函数中使用的Composition API进行了封装

将某个功能的js代码,封装在一个js文件里以函数形式暴露,一般将该js文件放在src/hooks目录下,以usexxx.js命名 --> 类似mixin mixin里面没有周期函数
组件需要使用的时候再引入该js文件

自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂

import {需要的组合api函数如生命周期钩子等} from'vue'
function savaPoint(){
//实现鼠标打点相关的数据
//实现鼠标打点相关的方法
//实现鼠标打点相关的生命周期钩子
return 有用的东西
}
export default savaPoint;

toRef函数 创建一个ref对象

作用: toRef函数创建一个ref对象,该ref对象的value值指向参数对象中的某个属性,返回ref对象的value值改变,参数对象中的该属性也会改变
说明: 本质是引用,与原始数据有关联
语法: const name=toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用

<template>
{{preson.name}}
{{preson.age}}
{{preson.job.j1.salary}}
</template>
<script>
import {reactive} from 'vue'
export default{
	name:'Demo',
	setup(){
  let person= reactive({
	name:"ranan",
	age:18,
	job:{
		j1:{
			salary:20
		}
	}
  })
	return {person}
}
}
</script>

需求:让模板中使用数据的代码更简单点

<template>
{{name}}
{{age}} 
{{salary}}
</template>
<script>
import {reacitve} from 'vue'
export default{
	name:'Demo',
	setup(){
	//,,,,
		return{//这样写的问题是没有响应式,这样相当于是将基本数据类型值进行赋值,修改name是不会引起person.name进行修改的
		name:person.name,//name = “ranan”
		age:person.age, 
		salart:person.job.j1.salary
		}
	}
}
</script>

错误写法:这样写的问题是没有响应式,这样相当于是将基本数据类型值进行赋值,修改name是不会引起person.name进行修改的

解决办法:toRef函数创建一个ref对象,该ref对象的value值指向参数对象中的某个属性,ref的value值改变,参数对象中的该属性也会改变,本质是引用,与原始数据有关联。
语法:const name=toRef(person,'name')

/*
{
    "_object": {
        "name": "ranan",
        "age": 18,
        "job": {
            "j1": {
                "salary": 20
            }
        }
    },
    "_key": "age",
    "__v_isRef": true
    value:(...)//读取的时候读的是person.age
}
*/
//const age= toRef(person,'age');
//return {
//	age:age;
//}
return {
 	age:toRef(person,'age'); //本质是引用
 	age:ref(person.age);//相当于为18做了响应式返回了新ref对象,但是age的改变不会引起person.age的改变
}
toRefs函数 创建多个ref引用对象

说明: toRefs与toRef功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
返回一个和参数一致的对象,只不过属性的值都变成了ref对象的值

 return{
 	...toRefs(person)
 }

原来对象的响应式是通过Proxy实现的,之前获得person.age=值,现在获得person.age = ref对象
但是toRefs对于新增的属性不会生效,Ref使用的是defineProperty,对新增和删除没有反应。 --> 直接返回响应式对象本身即可
在这里插入图片描述

其他Composition API

shallowReactive和shallowRef

shallowReactive浅响应,只有对象的第一层有响应式
shallowRef对于对象类型更为严格,不处理对象类型的响应式

什么时候使用

  • 如果有一个对象数据,结构比较深,但变化时只是外层属性变化 --> shallowReactive
  • 如果有一个数据对象,后续功能不会修改该对象中的属性,而是生成新的对象来替换 --> shallowRef
let x = shallowRef({
	y:0;
})

在这里插入图片描述

readonly与shallowReadonly 响应式数据变成只读

readonly:让一个响应式数据变为只读的(深只读),返回的还是proxy对象只是不能修改
shallowReadonly:让一个响应式数据变为只读的,只考虑对象的第一层
应用场景:不希望数据被修改时,不能修改数据

person = readonly(person); //person不可以修改

toRaw与markRaw 响应式数据 --> 普通数据

响应式数据 --> 普通数据

  • toRaw
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象进行操作,不会影响页面的改变
  • markRow
    • 作用:标记一个对象,使其永远不会成为响应式对象,对这个普通对象进行操作,不会影响页面的改变
    • 应用场景:当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高效率。有些值不应该被设置为响应式的,例如复杂的第三方库等
//当函数被调用时,p.age会+1,但是不会引起页面的改变,因为它是个普通对象
function showRawPerson(){
	const p = toRaw(person);
	p.age++;
}
function addCar(){//person是响应式对象,利用Proxy,在上面添加属性会被监测到,person.car也是响应式的
	let car = {name:'奔驰',price:40};
	person.car = markRaw(car)
}

customRef 自定义Ref

作用:创建一个自定义的ref

需求:有两个输入框,在第一个输入框输入信息后,等1s后显示 --> 防抖的效果,只有最后一次触发延迟delay后执行回调,其余时候触发会重新开始计时

import {ref,customRef} from 'vue'
export default {
	name: 'App',
	setup() {
		//自定义一个ref——名为:myRef
		function myRef(value,delay){
			let timer 
			/*
			cunstomeRef和Ref功能一致生成一个ref对象,但是内部逻辑自定义
			参数是函数,该函数必须返回一个对象
		    */
			return customRef((track,trigger)=>{
				return {
					get(){
						console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
						track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
						//有点类似收集依赖?
						return value
					},
					set(newValue){
						console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
						clearTimeout(timer)
						timer = setTimeout(()=>{
							value = newValue
							trigger() //通知Vue去重新解析模板,数据修改了重新get
							//有点像set通知依赖
						},delay)
					},
				}
			})
	}

		// let keyWord = ref('hello') //使用Vue提供的ref
		let keyWord = myRef('hello',500) //使用程序员自定义的ref
			
		return {keyWord}
	}
}

provide与inject 祖孙组件间通信

作用:实现祖孙组件间通信
套路:父组件有一个provide选项来提供数据,子组件有一个inject选项来使用数据

具体写法

1.祖组件中
provide('给传入的数据起名',真实数据)给自己的后代组件传递数据

setup(){
	....
	let car = reactive({name:'奔驰',price:'40万'});
	provide('car',car);
}

2.孙组件中
inject('接受的数据名') 后代组件接受祖先组件传来的值,返回值为该变量存储的数据,数据是Proxy对象

setup(props,context){
	....
	const car = inject('car');
	return {car};
	....
}

响应式数据的判断

  • isRef():检查一个值是否为ref对象
  • isReactive(): 检查一个对象是否是由reactive创建的响应式代理
  • isReadonly(): 检查一个对象是否是由readonly创建的只读代理
  • isProxy(): 检查一个对象是否由reactive或者readonly方法创建的代理

说明
readonly()返回的还是proxy对象只是不能修改

新的组件

Fragment组件 减少标签层级

  • 在Vue2中:组件必须有一个根标签
  • 在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中,不参与渲染

好处:减少标签层级,减小内存占用

Teleport 传送组件

什么是Teleport

Teleport 能够将我们的组件html结构移动到指定位置的技术

/*将需要传送的结构用Teleport标签包裹*/
<teleport to="body">
<!--to="#ranan" 到id=ranan标签里面-->
</teleport>

Suspense组件

Suspense组件作用:等待异步组件时渲染一些额外内容,让应用又更好的用户体验

compositionAPI defineAsyncComponent()定义异步组件(动态组件/懒加载)
参数为函数,参数的函数需要有返回值

组件懒加载,先出来的组件可以先渲染 --> 性能优化解决首屏加载慢(白屏)的问题

//App组件中
<Child/>
// import Child from './components/Chile.vue' 静态引入 
import {defineAsyncComponent} from 'vue'
//动态引入、异步引入
const Child = defineAsyncComponent(()=>import('./components/Chile.vue'));

存在问题
在这里插入图片描述

Suspend内置组件不用引入直接使用,v-slot属性必须按要求写!

<template>
	/*
	内部使用插槽实现,此时已经准备好了两个插槽
	插槽1 default插槽:Child组件展示的东西
	插槽2 fallback插槽:当Child组件还没有加载好的时候展示的东西
	*/
	<Suspense v-slot:default> 
		<Child/>
	</Suspense>
		<Suspense v-slot:fallback> /*fallback退路*/
			正在加载中....
		</Suspense>
</template>

使用了异步组件和Suspense 组件后setup可以是async函数

其他

全局API的转移

const app = createApp(App)
app.mount('#app')

应用实例对象app,类似vm,但app更轻

在这里插入图片描述

其他变化

  • data选项始终被声明为一个函数
  • 过渡类名的更改

vue3.x写法

.v-enter,
.v-leave-to{}
.v-leave,
.v-enter-to{}

vue3.x写法

.v-enter-from,
.v-leave-to{}
.v-leave-from,
.v-enter-to{}
  • 移除keyCode作为v-on的修饰符,同时不支持Vue.config.keyCodes.huiche=13定义按键别名
  • 移除v-on.native组件绑定原生事件时,会以为是自定义事件
    父组件绑定事件
<my-componet
 	v-on:close="handleComponentEvent"
 	v-on:click="handleNativeClickEvent"
/>

子组件中声明自定义事件

export default{
	emits:['close'] //没有指定click,则认为click是原生事件
}
  • 移除过滤器filiter
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值