vue3.0的基本使用

87 阅读7分钟

setup

一、概念

setup是所有Composition API(组合api)“表演的舞台”,组件中所用的到数据、方法等等,均要配置在setup中,但在setup函数中定义的变量和方法最后都需要return出去,不然无法在模版中使用

二、注意点

1、由于在执行 setup函数的时候,还没有执行 Created 生命周期方法,所以在 setup 函数中,无法使用 data 和 methods 的变量和方法

2、由于我们不能在 setup函数中使用 data 和 methods,所以 Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined

3、setup函数只能是同步的不能是异步的

 

三、参数

1、props

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

setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}

不能用es6的方法对props直接进行解构,会消除props的响应性

如果需要对props进行解构,可在setup函数中通过toRefs函数来完成此操作

import { toRefs } from 'vue'

setup(props) {
  const { title } = toRefs(props)

  console.log(title.value)
}

如果title是可选的prop,及传入的参数可能没有title,toRef函数不会为title创建一个热敷,此时可以通过toRef来实现

import { toRef } from 'vue'
setup(props) {
  const title = toRef(props, 'title')
  console.log(title.value)
}

 

2、context

上下文对象

export default {
  setup(props, context) {
    console.log(context.attrs)
    console.log(context.slots)
    console.log(context.emit)
    console.log(context.expose)
  }
}

1)attrs 非响应式对象

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

2)slots 非响应式对象

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

3)emit 触发事件

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

4)expose

暴露公共函数

 

context是一个普通的javascripts对象,因此不具备响应式,可以直接进行解构

export default {
  setup(props, { attrs, slots, emit, expose }) {
    ...
  }
}

 

四、返回参数

setup函数的两种返回值:

(1)若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。

(2)若返回一个渲染函数,则可以自定义渲染内容

import {h} from 'vue'
setup() {
	// 数据
	let name = 'xxx'
	let age = 18
	// 方法
	function sayHello() {
		alert(`${name}${age}岁`)
	}
	// 返回一个对象(常用)
	 return {
		name,
		age,
		sayHello
	}
	// 返回一个渲染函数
	return () => {h('h1', 'xxxxxx')} 
}

 

五、语法糖

1、使用方法

setup的语法糖即在script1中直接写setup

使用setup语法糖后,不用写setup函数;组件只需要引入不需要注册;属性和方法也不需要再返回,可以直接在template模板中使用

	<script lang="ts" setup>
		import {ref} from 'vue';
		//此时注册的变量或方法可以直接在template中使用而不需要导出
		const numb = ref(0);
		let func = ()=>{
			numb.value++;
		}
	</script>

 

2、setup语法糖中新增的api

1)defineProps

子组件接收父组件中传来的props

父组件代码

<template>
		<my-component @click="func" :num="num"></my-component>
	</template>
	<script lang="ts" setup>
		import {ref} from 'vue';
		import myComponent from '@/components/myComponent.vue';
		const num = ref(0);
		let func = ()=>{
			num.value++;
		}
	</script>

子组件代码

	<template>
		<div>{{num}}</div>
	</template>
	<script lang="ts" setup>
		import {defineProps} from 'vue';
		defineProps({
			num:{
				type:Number,
				default:NaN
			}
		})
	</script>

 

 

 

2)defineEmits

子组件调用父组件中的方法

父组件代码

	<template>
		<my-component @addNumb="func" :numb="numb"></my-component>
	</template>
	<script lang="ts" setup>
		import {ref} from 'vue';
		import myComponent from '@/components/myComponent.vue';
		const numb = ref(0);
		let func = ()=>{
			numb.value++;
		}
	</script>

子组件代码

	<template>
		<div>{{numb}}</div>
		<button @click="onClickButton">数值加1</button>
	</template>
	<script lang="ts" setup>
		import {defineProps,defineEmits} from 'vue';
		defineProps({
			numb:{
				type:Number,
				default:NaN
			}
		})
		const emit = defineEmits(['addNumb']);
		const onClickButton = ()=>{
			//emit(父组件中的自定义方法,参数一,参数二,...)
			emit("addNumb");
		}
	</script>

3)defineExpose

子组件暴露属性,可以在父组件中拿到

子组件代码

	<template>
		<div>子组件中的值{{numb}}</div>
		<button @click="onClickButton">数值加1</button>
	</template>
	<script lang="ts" setup>
		import {ref,defineExpose} from 'vue';
		let numb = ref(0);
		function onClickButton(){
			numb.value++;	
		}
		//暴露出子组件中的属性
		defineExpose({
			numb 
		})
	</script>

父组件代码

	<template>
		<my-comp ref="myComponent"></my-comp>
		<button @click="onClickButton">获取子组件中暴露的值</button>
	</template>
	<script lang="ts" setup>
		import {ref} from 'vue';
		import myComp from '@/components/myComponent.vue';
		//注册ref,获取组件
		const myComponent = ref();
		function onClickButton(){
			//在组件的value属性中获取暴露的值
			console.log(myComponent.value.numb)  //0
		}
		//注意:在生命周期中使用或事件中使用都可以获取到值,
		//但在setup中立即使用为undefined
		console.log(myComponent.value.numb)  //undefined
		const init = ()=>{
			console.log(myComponent.value.numb)  //undefined
		}
		init()
		onMounted(()=>{
			console.log(myComponent.value.numb)  //0
		})
	</script>

 

 

 

 

 

 

 

ref和reactive

一、ref函数

作用

定义一个响应式的数据,在数据改变的同时能够引发图层的变化

语法

const xxx = ref(initValue)

无初始值设置则为null

(1)创建一个包含响应式数据的引用对象(reference对象)

(2)在js中操作数据:xxx.value

(3)在模板template中读取数据:不需要.value,直接

{{xxx}}

注意:ref既可以接受基本类型也可以接受对象类型,对于基本类型的数据,响应式依然是 依靠object.defineProperty()的get和set完成的,即通过给value属性设置setter和getter实 现数据劫持,创造一种任意值的引用并能够不丢失响应性地随意传递

当数据类型为对象时,更推荐使用reactive来完成

 

二、reactive函数

作用

定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数,否则不具备响应式)

语法

const 代理对象 = reactive(源对象)

接收一个对象(或数组),返回一个代理对象(proxy的实例对象)

reactive定义的响应式把数据是深层次的

内部基于ES6的Proxy实现,通过代理对象操作元对象内部数据进行操作,本质是将每一层数据解析成proxy对象,reactive响应式默认都是递归的,改变某一层的值都会递归的调用一遍,重新渲染dom

 

三、ref和reactive的区别

从定义数据角度对比:

ref用来定义:基本类型数据

reactive用来定义:对象(或数组)类型数据

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

从原理角度对比:

ref通过Object.defineProperty()的get和set来实现响应式(数据劫持)

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

从使用角度对比:

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

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

从响应式对比:

使用ref创建的响应式值,本身就是响应式的,并不依赖于其他对象

而用reactive创建的响应式对象,整个对象是响应式的,但对象里的每一项都是普通的值,当你把它用展开运算符展开后,整个对象的普通值都不是响应式的

如果需要展开reactive创建的响应式对象又不想他们失去响应式的时候,可以利用toRefs来进行转换

 

注意,ref本质也是reactive,ref(obj)等价于reactive({value:obj})

 

生命周期

vue2.0->vue3.0

// 在beforeCreate执行前执行,组件实例创建前,执行
beforeCreate -> 请使用 setup()
created -> 请使用 setup()
// 挂载DOM前执行
beforeMount -> onBeforeMount
// 挂载DOM后执行
mounted -> onMounted
// 更新组件前执行
beforeUpdate -> onBeforeUpdate
// 更新组件后执行
updated -> onUpdated
// 卸载销毁前 执行
beforeDestroy -> onBeforeUnmount
// 卸载销毁后执行
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured

计算属性

computed又被称作计算属性,用于动态的根据某个值或某些值的变化,来产生对应的变化,computed具有缓存性,当依赖的值无变化时,不会引起computed声明值的变化 

一、computed创建只读计算属性

给 computed 传入一个函数,可以得到一个只读的计算属性:

const count = ref(1)
// 创建一个计算属性,使其值比 count 大 1
const bigCount = computed(() => count.value + 1)
 
console.log(bigCount.value) // 输出 2
bigCount.value++ // error 不可写

二、computed创建可读可写的计算属性

const count = ref(1)
 
// 创建一个 computed 计算属性,传入一个对象
const bigCount = computed({
    // 取值函数
    get: () => (count.value + 1),
    // 赋值函数
    set: val => {
      count.value = val - 1
    }
})
 
// 给计算属性赋值的操作,会触发 set 函数
bigCount.value = 9
// 触发 set 函数后,count 的值会被更新
console.log(count.value) // 8
 

 

监听属性

对指定声明的属性实行监听

一、监听ref创建的基本数据类型

1、监听一个数据

<script>
import { watch, ref} from 'vue';
export default {
    name:"",
    setup(){
        let url=ref("www.webtools.wang");
        watch(url,(newvalue,oldvalue)=>{
            console.log(newvalue,oldvalue)
        },{immediate:false})
        return {
            url
        }
    }
}
</script>

2、监听多个数据

<script>
import { watch, ref} from 'vue';
export default {
    name:"",
    setup(){
        let url=ref("www.webtools.wang");
        let sum=ref(1);
        watch([sum,url],(newvalue,oldvalue)=>{
            console.log(newvalue,oldvalue)
        },{immediate:false})
        return {
            sum,
            url
        }
    }
}
</script>

 

二、监听ref创建的对象

需要开启深度监听

<script>
import {ref,watch} from 'vue'
export default {
    name:"",
    setup(){
        let person=ref({
            name:"chenxuan",
            age:18
        })
        // 此处监听不到
        // watch(person,(newvalue,oldvalue)=>{
        //     console.log("监听到了变化!")
        // })
 
        // 解决方法1
        // 使用ref创建引用类型的响应数据,在vue内部最终还是通过reactive实现的
        // person.value 相当于在监听reactive创建的响应数据
        // watch(person.value,(newvalue,oldvalue)=>{
        //     console.log("监听到了age变化!")
        // })
 
        // 解决方法2,开启深度监听
        watch(person.value,(newvalue,oldvalue)=>{
            console.log("监听到了age变化!")
        },{deep:true})
        return{
            person
        }
    }
}
</script>

三、监听reactive创建的对象

 watch(person,(newvalue,oldvalue)=>{
 console.log("监听到了")
})

 

四、监听reactive定义的数据的某一个属性

如果监听对象中的某个属性,需要使用箭头函数的方式

监听更少的数据有利于提高性能

watch(()=>person.name,(newvalue,oldvalue)=>{
  console.log('监听到了')
})

 

五、关于配置选项

    const obj = reactive({
      msg: {
        info: 'ooo'
      }
    })
   watch(() => obj.msg,(v1, v2) => {
        console.log(v1, v2)
      },
      {
        // 首次渲染组件就触发一次
        immediate: true,
        // 开启深度监听,对象里面的数据如果发生变化也会被侦听到
        // 如果监听的数据是一个比较长的表达式,那么需要用一个函数的方式
        // 但是写成函数形式之后,里层的数据变化不到,所以需要添加deep选项
        deep: true
      }
    )

 

 

keep-alive

概念

keep-alive是vue中的一个内置组件,通常用它来包裹一个动态组件,keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

作用是将组件缓存在内存当中,防止重复渲染DOM,属于消耗内存获取速度。

它有两个特殊的生命周期钩子activated 和 deactivated,在vue3.0里面生命周期函数前面都要加上on,onActivated,onDeactivated。当组件在使用了keep-alive包裹时,正常的生命周期函数mounted和unmounted将不会执行,取而代之的是为它新增的这个两个特殊钩子函数。

属性

include:只有名称匹配的组件会被缓存;类型可以是数组、字符串或者正则。

exclude:名称匹配的组件不会被缓存;类型可以是数组、字符串或者正则。

max:最多可以缓存多少组件实例。

在vue3.0中使用keep-alive

<router-view v-slot="{ Component, route }" id="pageWrapper">
    <transition :name="transitionName" mode="out-in">
      <keep-alive :include="cacheRouter">
        <component :is="Component" />
      </keep-alive>
    </transition>
 </router-view>

设置被缓存的路由

1、直接写死

把需要缓存的路由name写到一个数组中,但这种方法不够灵活

cacheRouter: ['home', 'order'];

2、通过设置路由原信息

在需要被缓存的组件的meta里面添加keepAlive标记

export const routes: RouteRecordRaw[] = [
  {
    path: '/myLogin',
    name: 'myLogin',
    component: () => import('@/views/login/login.vue'),
    meta: {
      title: '登录',
      index: 1,
      keepAlive: true,
    },
  },
];
let cacheList: any[] = [];
const keepAliveView = (_route: RouteRecordRaw[], _cache: RouteRecordName[]): void => {
  _route.forEach((item) => {
    item.meta?.keepAlive && item.name && _cache.push(item.name);
  });
};
//routes 路由配置数组
keepAliveView(routes, cacheList);


export default cacheList;