Vue3学习笔记:ref函数、reactive函数等常用Composition API、生命周期,Fragment、teleport等新的组件用法记录

发布于:2022-12-28 ⋅ 阅读:(294) ⋅ 点赞:(0)

一、快速上手Vue3.0

1、使用vue-cli

2、使用vite

开发环境中,无需打包

轻量快速热重载

npm init vite-app vue3_test_vite

二、常用Composition API(组合式API)

1、Setup

组件中所用到的:数据、方法等等,均要配置在setup中。

在setup中不能访问到vue2配置

<template>
  <h1>我是App组件</h1>
  <h2>姓名:{{name}}</h2>
  <h2>年龄:{{age}}</h2>
  <button @click="sayHello">说话(VUE3)</button>
  <button @click="sayWelcome">说话(VUE2)</button>
</template>
<script>
export default {
  name: 'App',
  data(){
    return {
      sex:'男'
    }
  },
  methods: {
    sayWelcome(){
      alert('欢迎欢迎')
    }
  },
  //不考虑响应式的问题
  setup(){
    let name = '张三'
    let age = 18
    function sayHello(){
      alert(`我叫${name},我${age}岁了,你好啊`)
    }
    return {
      name,
      age,
      sayHello
    }
  }
}
</script>

2、ref函数

作用:定义一个响应式的数据

语法: const xxx = ref(initValue)

  • 创建一个引用对象
  • JS中操作数据: xxx.value
  • 模板中读取数据不需要.value

接收的基本类型数据:响应式依然是靠 Object.defineProperty() 的get 与set完成的

对象类型的数据(reactive)

<template>
  <h1>我是App组件</h1>
  <h2>姓名:{{name}}</h2>
  <h2>年龄:{{age}}</h2>
  <h3>工作薪水:{{job.salary}}</h3>
  <button @click="changeInfo">修改人的信息</button>
</template>

<script>
import {ref} from 'vue'
export default {
  name: 'App',
  //不考虑响应式的问题
  setup(){
    let name = ref('张三')
    let age = ref(18)
    let job = ref({
      type:'前端工程师',
      salary:'30k'
    })

    function changeInfo(){
      name.value = '李四',
      age.value = 48
      console.log(job.value);
    }
    return {
      name,
      age,
      job,
      changeInfo
    }
  }
}
</script>

3、reactive函数

作用:定义一个对象类型的响应式数据

语法: const 代理对象 = reactive(源对象)接收一个对象或数组,返回一个代理对象(proxy的实例对象)

内部基于ES6的proxy实现,通过代理对象操作源对象内部数据进行操作


<script>
import {ref,reactive} from 'vue'
export default {
  name: 'App',
  setup(){
      let person = reactive({
          name:'张三',
          age:18,
          job:{
               type:'前端工程师',
      		  salary:'30k',
              a:{
                  b:{
                      c:666
                  }
              }
          }
      })

    function changeInfo(){
     	person.name = '...'
        person.age = 48
        person.job.type = '....'
        person.a.b.c = ***
        person.hobby[0] = '学习'
    }
    return {
      name,
      age,
      job,
      changeInfo
    }
  }
}
</script>

4、Vue3响应式

实现vue2的响应式原理:

  let person = {
            name:'张三',
            age:18
        }
// 模拟Vue2中实现响应式
        let p = {}
        Object.defineProperty(p,'name',{
            get(){
                //有人读取name时调用
                return person.name
            },
            set(value){
                //有人修改name时调用
                console.log('有人修改了name,我去更新页面');
                person.name = value
            }
        })
        Object.defineProperty(p,'age',{
            get(){
                //有人读取name时调用
                return person.age
            },
            set(value){
                //有人修改name时调用
                console.log('有人修改了age,我去更新页面');
                person.age = value
            }
        })

实现原理:

通过Proxy(代理):拦截对象中任意属性变化。包括增删读写

通过Reflect(反射):对被代理对象的属性进行操作

  let person = {
            name:'张三',
            age:18
        }
const p = new Proxy(person,{
        get(target,propName){
            console.log(`有人读取了p身上的${propName}属性`);
            return Reflect.get(target,propName)
        },
        set(target,propName,value){
            console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`);
            Reflect.set(target,propName,value)
        },
        deleteProperty(target,propName){
            console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`);
            return Reflect.deleteProperty(target,propName)
        }
    })

5、reactive和ref的对比

  • 从定义数据角度对比:

    ​ ref定义基本数据

    ​ reactive定义对象或数组类型

    从原理角度对比:

    ​ ref通过 Object.defineProperty()的get与set来实现响应式。

    ​ reactive通过使用 Proxy来实现响应式(数据劫持)并通过Reflect操作源对象内部数据。

    从使用角度对比:

    ​ ref定义的数据:操作数据需要 .value,读取数据模板中直接读取不需要 .value

    ​ reactive定义的数据:操作读取均不需要 .value

6、Setup的注意点

setup执行时机:在beforecreate之前执行一次,this是undefined

Setup的参数:props值为对象,包含组件外传递过来且组件内声明接收了的属性

context:上下文对象

​ attrs:值为对象,包含组件外部传递过来但没有props配置中声明的属性,相当于 this.$attrs

​ slots:收到的插槽内容,相当于 this.$slots

​ emit:分发自定义事件的函数,相当于 this.$emit

7、计算属性与监视

1、computed写法

import {computed} from 'vue'
setup(){
    ...
    //简写
    //let fullName = computed(()=>{
    //    return person.firstName + '-' + person.lastName
    //})
    //计算属性完整
    let fullName = computed({
        get(){
            return person.firstName + '-' + person.lastName
        },
        set(value){
            const nameArr = value.split('-')
            person.firstName = nameArr[0]
            person.lastName = nameArr[1]
        }
    })
}

2、watch写法

监视reactive所定义的一个响应式数据的全部属性,无法正确地获取oldValue,deep失效

监视的是reactive定义的对象中的某个对象属性,deep配置有效

import {ref,reactive,watch} from 'vue'
setup(){
    let sum = ref(0)
    let msg = ref('你好啊')
    let person = reactive({
        name:'张三',
        age:18,
        job:{
            j1:{
                salary:20
            }
        }
    })
    //情况一:监视ref所定义的一个响应式数据
    watch(sum,(newValue,oldValue)=>{
        console.log('sum变了',newValue,oldValue)
    },{immediate:true})
    //情况二:监视ref所定义的多个响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
        console.log('sum或msg变了',newValue,oldValue)
    },{immediate:true})
	}
	//情况三:监视reactive所定义的一个响应式数据的全部属性
	//此处无法正确地获取oldValue
	//强制开启深度监视
	watch(person,(newValue,oldValue)=>{
        console.log('person变化了',newValue,oldValue)
    },{deep:false})//deep配置无效
	//情况四:监视reactive所定义的一个响应式数据的某个属性
	watch(()=>person.name,(newValue,oldValue)=>{
        console.log('person的name变化了',newValue,oldValue)
    })
	//情况五:监视reactive所定义的一个响应式数据中的某些属性
	watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
        console.log('person的name或age变化了',newValue,oldValue)
    })
	//特殊情况
	watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true})//此处监视的是reactive定义的对象中的某个对象属性,所以deep配置有效
	return{
        sum,
        msg,
        person
    }

3、watchEffect函数

watch的套路是:既要指明监视属性,也指明监视回调

watchEffect:不用指明监视哪个属性,监视回调中用到哪个就监听哪个

import {ref,reactive,watch,watchEffect} from 'vue'
setup(){
    let sum = ref(0)
    let msg = ref('你好啊')
    let person = reactive({
        name:'张三',
        age:18,
        job:{
            j1:{
                salary:20
            }
        }
            })
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.job.j1.salary
        console.log('watchEffect所指定的回调执行了')
    })
    	return{
        sum,
        msg,
        person
    }
   

8、生命周期

image-20220902121922938

Vue3.0中可以继续使用Vue2的生命周期钩子

beforeDestory改为 beforeUnmount

destroyed 改为 unmounted

Composition API形式的生命周期钩子,与Vue2中钩子对应关系如下:

beforeCreate===> setup()

created===>setup()

beforeMount ===> onBeforeMount

mounted===> onMounted

beforeUpdate===> onBeforeUpdate

updated===> onUpdated

beforeUnmount===> onBeforeUnmount

unmounted ===> onUnmounted

通过组合式API的形式去使用生命周期钩子

<template>
    <h2>求和为:{{sum}}</h2>
    <button @click="sum++">点我+1</button>
</template>

<script>
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
    name:'DemoOne',

    setup(){
        console.log('---setup---');
        let sum = ref(0)
        //通过组合式API的形式去使用生命周期钩子
        onBeforeMount(()=>{
            //函数体
            console.log('--onBeforeMount--');
        })
        onMounted(()=>{
            //函数体
            console.log('--onMounted--');
        })
        onBeforeUpdate(()=>{
            //函数体
            console.log('--onBeforeUpdate--');
        })
        onUpdated(()=>{
            //函数体
            console.log('--onUpdated--');
        })
        onBeforeUnmount(()=>{
            //函数体
            console.log('--onBeforeUnmount--');
        })
        onUnmounted(()=>{
            //函数体
            console.log('--onUnmounted--');
        })
        return {sum}
    },
    //#region 
    // beforeCreate(){
    //     console.log('---beforeCreate---');
    // },
    // created(){
    //     console.log('---created---');
    // },
    // beforeMount(){
    //     console.log('---beforeMount---');
    // },
    // mounted(){
    //     console.log('---mounted---');
    // },
    // beforeUpdate(){
    //     console.log('---beforeUpdate---');
    // },
    // updated(){
    //     console.log('---updated---');
    // },
    // beforeUnmount() {
    //     console.log('---beforeUnmount---');
    // },
    // unmounted(){
    //     console.log('---unmounted---');
    // }
    //#endregion
}
</script>

9、自定义hook函数

  • 本质是一个函数,把setup函数中使用的Composition API进行了封装
  • 类似于mixin
  • 复用代码,让setup中的逻辑更清楚

hooks/usePoint.js

import {onMounted,reactive,onBeforeUnmount} from 'vue'
export default function(){
    //实现“打点”的数据
    let point = reactive({
        x:0,
        y:0
    })
    //实现打点的方法
    function savePoint(event){
        point.x = event.pageX
        point.y = event.pageY
        console.log(event.pageX,event.pageY);
    }
    //实现打点的钩子
    onMounted(()=>{
        //函数体
        window.addEventListener('click',savePoint)
    })
    onBeforeUnmount(()=>{
        window.removeEventListener('click',savePoint)
    })
    return point
}

components/DemoOne.vue

<template>
    <h2>求和为:{{sum}}</h2>
    <button @click="sum++">点我+1</button>
    <hr>
    <h2>鼠标坐标为x{{point.x}},y:{{point.y}}</h2>
</template>
<script>
import {ref} from 'vue'
import usePoint from '../hooks/usePoint'
export default {
    name:'DemoOne',
    setup(){
        console.log('---setup---');
        let sum = ref(0)
        let point =  usePoint()
        return {sum,point}
    },
}
</script>

10、toRef

作用:创建一个Ref对象,其value值指向另一个对象中的某个属性

语法 : const name = toRef(person,'name')

应用:要将响应式对象中的某个属性提供给外部使用时

扩展: toRefs(person)批量创建ref对象

<template>
    <h1>{{person}}</h1>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h2>薪资:{{ job.j1.salary }}</h2>
  <button @click="name += '~'">修改姓名</button>
  <button @click="age++">增长年龄</button>
  <button @click="job.j1.salary++">涨薪</button>
</template>

<script>
import { reactive,toRefs } from "vue";
export default {
  name: "DemoOne",
  setup() {
    let person = reactive({
      name: "张三",
      age: 18,
      job: {
        j1: {
          salary: 20,
        },
      },
    });
    // return {
    //     person,
    //     name:toRef(person,'name'),
    //     age:toRef(person,'age'),
    //     salary:toRef(person.job.j1,'salary')
    // }
    return{
        ...toRefs(person)
    }
  },
};
</script>

三、其他Composition API

1、shallowReactive与shallowRef

shallowRecative只考虑对象类型第一层(最外层)的响应式

shallowRef不去处理对象类型的响应式,只处理基本数据类型响应式

使用:

如果一个对象数据结构比较深,但只用外层属性===>shallowRecative

如果一个对象数据后续不会修改对象中的属性,如要修改则直接替换为新的对象===>shallowRef

2、readonly与shallowReadonly

不希望数据被修改时

readonly:使一个响应式数据变为只读

shallowReadonly:使一个响应式数据外层只读

person = readonly(person)
person = shallowReadonly(person)

3、toRaw与markRaw

toRaw

  • 作用:将一个由 reactive生成的响应式对象转为普通对象
  • 使用:读取响应式对象对应的普通对象,对普通对象的所有操作不会引起页面变化
const p = toRaw(person)

markRaw

作用:标记一个对象,使其永远不会再成为响应式对象

使用:

有些值不应该被设为响应式,例如第三方类库axios等

渲染不可变数据的大列表时,可跳过响应式转换提高性能

let car = {name:'奔驰',price:40}
person.car = markRaw(car)

4、customRef

创建一个自定义Ref,并对其依赖项跟踪和更新触发进行显式控制

实现防抖效果:

<template>
  <input type="text" v-model="keyWord">
  <h3>{{keyWord}}</h3>
</template>

<script>
import { customRef } from "vue";
export default {
  name: "App",
  setup(){
    //自定义ref
    function myRef(value,delay){
      let timer
      return customRef((track,trigger)=>{
        return {
          get(){
            console.log(`有人从myRef读取数据了,${value}`);
            track() //通知vue追踪数据的变化
            return value
          },
          set(newValue){
            console.log(`有人把myRef数据改为了:${newValue}`);
            clearTimeout(timer)
            timer = setTimeout(()=>{
              value = newValue
            trigger() //通知vue重新解析模板
            },delay)
          }
        }
      })
    }
    // let keyWord = ref('hello') //使用Vue的ref
    let keyWord = myRef('hello',500) //使用程序员自定义的ref
    return {keyWord}
  }
}
</script>

5、provide与inject

作用:实现祖孙组件间通信

使用:父组件有一个 provide来提供数据,后代组件有一个 inject 选项来使用数据

父组件App.vue

<template>
  <div class="app">
    <h3>App组件(祖),{{name}}--{{price}}</h3>
    <Child />
  </div>
</template>
<script>
import { reactive,toRefs,provide } from 'vue'
import Child from './components/Child.vue'
export default {
  name:'App',
  components:{Child},
  setup(){
    let car = reactive({
      name:'奔驰',
      price:40
    });
    provide('car',car)//给自己后代组件传递数据
    return{
      ...toRefs(car)
    }
  }
  }
</script>
<style scoped>
.app{
  background-color: aqua;
  padding: 10px;
}
</style>

后代组件Son.vue

<template>
  <div class="son">
    <h3>Son组件(孙){{car.name}} -- {{car.price}} </h3>
  </div>
</template>

<script>
import {inject} from 'vue'
export default {
    name:'SonOne',
    setup(){
      let car =  inject('car')
      return{car}
    }
  }
</script>
<style scoped>
.son{
  background-color: gray;
  padding: 10px;
}
</style>

6、响应式数据的判断

isRef():检查一个值是否为ref对象

isReactive():检查一个对象是否为reactive创建的响应式代理

isReadonly() :检查一个对象是否为readonly创建的只读代理

isProxy() :检查一个对象是否为代理

四、新的组件

1、Fragment

Vue3组件可以没有根标签,会将多个标签包含在一个Fragment虚拟元素中

减少层级嵌套

2、teleport

将组件html结构移动到指定位置

<teleport to="移动位置">
	<div v-if="isShow" class="mask">
        <div class="dialog">
            <h3>
                我是一个弹窗
            </h3>
            <button @click="isShow=false">
                关闭弹窗
            </button>
        </div>
    </div>
</teleport>

3、Suspense

等待异步组件时渲染一些额外内容,增加用户体验

异步引入组件

import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

使用 Suspense包裹组件,配置 defaultfallback

<template>
	<div class="app">
		<Suspense>
			<template v-slot:default>
				<Child/>
			</template>
			<template v-slot:fallback>
				<h3>加载中</h3>
			</template>
		</Suspense>
	</div>
</template>

五、其他变化

略写~~~~

移除 v-on.native修饰符

父组件中绑定事件

<fu v-on:click="clickEvent" v-on:close="closeEvent">
</fu>

子组件中声明自定义事件

<script>
export default{
    emits:['close']
}
</script>
  </h3>
        <button @click="isShow=false">
            关闭弹窗
        </button>
    </div>
</div>
```

3、Suspense

等待异步组件时渲染一些额外内容,增加用户体验

异步引入组件

import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

使用 Suspense包裹组件,配置 defaultfallback

<template>
	<div class="app">
		<Suspense>
			<template v-slot:default>
				<Child/>
			</template>
			<template v-slot:fallback>
				<h3>加载中</h3>
			</template>
		</Suspense>
	</div>
</template>

五、其他变化

略写~~~~

移除 v-on.native修饰符

父组件中绑定事件

<fu v-on:click="clickEvent" v-on:close="closeEvent">
</fu>

子组件中声明自定义事件

<script>
export default{
    emits:['close']
}
</script>
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到