setup执行的时机

  1. 在beforeCreate之前执行(一次),此时组件对象还没创建;
  2. this是undefined,不能通过this来访问data/computed/methods/props;
  3. 其实所有的composition API相关回调函数中也都不可以;

setup的返回值

  1. 一般都返回一个对象:为模板提供数据,也就是模板中可以直接使用此对象中的所有属性/方法
  2. 返回对象中的属性会与data函数返回对象合并成为组件对象的属性
  3. 返回对象中的方法会与methods中的方法合并成功组件对象的方法
  4. 如果有重名,setup优先
  5. 注意:一般不要混合使用:methods中可以访问setup提供的属性和方法,但在setup方法中不能访问data和methods;setup不能是async函数:因为返回值不再是return的对象,而不是promise,模板看不到return对象中的属性数据

setup参数

  1. setup(props,context)/setup(props,{attrs,slots,emit})
  2. props:包含props配置声明且传入了所有属性的对象
  3. attrs:包含没有在props配置中声明的属性的对象,相当于this.$attrs
  4. slots:包含所有传入的插槽内容的对象,相当于this.$slots
  5. emit:用来分发自定义事件的函数,相当于this.$emit

演示代码

  1. 父组件
	<template>
<h2>App</h2>
<p>msg: {{msg}}</p>
<button @click="fn('--')">更新</button>
<child :msg="msg" msg2="cba" @fn="fn"/>
</template>
<script lang="ts">
import {
reactive,
ref,
} from 'vue'
import child from './child.vue' export default {
components: {
child
}, setup () {
const msg = ref('abc')
function fn (content: string) {
msg.value += content
}
return {
msg,
fn
}
}
}
</script>
  1. 子组件
<template>
<div>
<h3>{{n}}</h3>
<h3>{{m}}</h3>
<h3>msg: {{msg}}</h3>
<h3>msg2: {{$attrs.msg2}}</h3>
<slot name="xxx"></slot>
<button @click="update">更新</button>
</div>
</template>
<script lang="ts">
import {
ref,
defineComponent
} from 'vue' export default defineComponent({
name: 'child',
props: ['msg'],
emits: ['fn'], // 可选的, 声明了更利于程序员阅读, 且可以对分发的事件数据进行校验
data () {
console.log('data', this)
return {
// n: 1
}
}, beforeCreate () {
console.log('beforeCreate', this)
}, methods: {
// update () {
// this.n++
// this.m++
// }
}, // setup (props, context) {
setup (props, {attrs, emit, slots}) {
console.log('setup', this)
console.log(props.msg, attrs.msg2, slots, emit) const m = ref(2)
const n = ref(3) function update () {
// console.log('--', this)
// this.n += 2
// this.m += 2
m.value += 2
n.value += 2 // 分发自定义事件
emit('fn', '++')
} return {
m,
n,
update,
}
},
})
</script>

reactive与ref细节

  1. 是vue3的composition API中最重要的响应式API
  2. ref用来处理基本类型的数据,reactive用来处理对象(递归深度响应式)
  3. 如果用ref对象/数组,内部会自动将对象/数组转换为reactive的代理对象
  4. ref内部:通过给value属性添加getter/setter来实现对数据的劫持
  5. reactive内部:通过使用Proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据
  6. ref的数据操作:在js中要.value,在模板中不需要(内部解析模板时会自动添加.value)
<template>
<h2>App</h2>
<p>m1: {{m1}}</p>
<p>m2: {{m2}}</p>
<p>m3: {{m3}}</p>
<button @click="update">更新</button>
</template>
<script lang="ts">
import {
reactive,
ref
} from 'vue' export default { setup () {
const m1 = ref('abc')
const m2 = reactive({x: 1, y: {z: 'abc'}}) // 使用ref处理对象 ==> 对象会被自动reactive为proxy对象
const m3 = ref({a1: 2, a2: {a3: 'abc'}})
console.log(m1, m2, m3)
console.log(m3.value.a2) // 也是一个proxy对象 function update() {
m1.value += '--'
m2.x += 1
m2.y.z += '++' m3.value = {a1: 3, a2: {a3: 'abc---'}}
m3.value.a2.a3 += '==' // reactive对对象进行了深度数据劫持
console.log(m3.value.a2)
} return {
m1,
m2,
m3,
update
}
}
}
</script>

计算属性和监视属性

<template>
<h2>App</h2>
fistName: <input v-model="user.firstName"/><br>
lastName: <input v-model="user.lastName"/><br>
fullName1: <input v-model="fullName1"/><br>
fullName2: <input v-model="fullName2"><br>
fullName3: <input v-model="fullName3"><br> </template>
<script lang="ts">
/*
计算属性与监视
1. computed函数:
与computed配置功能一致
只有getter
有getter和setter
2. watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
3. watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
*/ import {
reactive,
ref,
computed,
watch,
watchEffect
} from 'vue' export default { setup () {
const user = reactive({
firstName: 'A',
lastName: 'B'
}) // 只有getter的计算属性
const fullName1 = computed(() => {
console.log('fullName1')
return user.firstName + '-' + user.lastName
}) // 有getter与setter的计算属性
const fullName2 = computed({
get () {
console.log('fullName2 get')
return user.firstName + '-' + user.lastName
}, set (value: string) {
console.log('fullName2 set')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
}
}) const fullName3 = ref('') /*
watchEffect: 监视所有回调中使用的数据
*/
/*
watchEffect(() => {
console.log('watchEffect')
fullName3.value = user.firstName + '-' + user.lastName
})
*/ /*
使用watch的2个特性:
深度监视
初始化立即执行
*/
watch(user, () => {
fullName3.value = user.firstName + '-' + user.lastName
}, {
immediate: true, // 是否初始化立即执行一次, 默认是false
deep: true, // 是否是深度监视, 默认是false
}) /*
watch一个数据
默认在数据发生改变时执行回调
*/
watch(fullName3, (value) => {
console.log('watch')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
}) /*
watch多个数据:
使用数组来指定
如果是ref对象, 直接指定
如果是reactive对象中的属性, 必须通过函数来指定
*/
watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
console.log('监视多个数据', values)
}) return {
user,
fullName1,
fullName2,
fullName3
}
}
}
</script>

生命周期

与2.x版本生命周期相对应的组合API

。beforeCreate=>使用setup()

。create=>使用setup()

。beforeMount=>onBeforeMount

。mounted=>onMounted

。beforeUpdate=>onBeforeUpdate

。updated=>onUpdated

。beforeDestroy=>onBeforeUnmount

。destroyed=>onUnmounted

。errorCaptured=>onErrorCaptured

toRefs

  1. 把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref
  2. 应用:当从合成函数返回响应式对象时,toRefs非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
  3. 问题:reactive对象取出的所有属性值都是非响应式的
  4. 解决:利用toRefs可以将一个响应式reactive对象的所有原始属性转换为响应式的ref属性
<template>
<h2>App</h2>
<h3>foo: {{foo}}</h3>
<h3>bar: {{bar}}</h3>
<h3>foo2: {{foo2}}</h3>
<h3>bar2: {{bar2}}</h3>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue'
/*
toRefs:
将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,
这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
*/
export default { setup () { const state = reactive({
foo: 'a',
bar: 'b',
}) const stateAsRefs = toRefs(state) setTimeout(() => {
state.foo += '++'
state.bar += '++'
}, 2000); const {foo2, bar2} = useReatureX() return {
// ...state,
...stateAsRefs,
foo2,
bar2
}
},
} function useReatureX() {
const state = reactive({
foo2: 'a',
bar2: 'b',
}) setTimeout(() => {
state.foo2 += '++'
state.bar2 += '++'
}, 2000); return toRefs(state)
} </script>

ref获取元素

  1. 利用ref函数获取组件中的标签元素
  2. 功能需求:让输入框自动获取焦点
<script lang="ts">
import { onMounted, ref } from 'vue'
/*
ref获取元素: 利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
*/
export default {
setup() {
const inputRef = ref<HTMLElement|null>(null) onMounted(() => {
inputRef.value && inputRef.value.focus()
}) return {
inputRef
}
},
}
</script>

自定义hook函数

与vue2中mixins作用差不多,使用方法是直接在页面中引用即可

import { ref,onMounted } from "vue";
export default function() {
const x = ref(0);
const y = ref(1);
const clickHandler = (event:MouseEvent) => {
x.value = event.pageX;
y.value = event.pageY;
} onMounted(() => {
window.addEventListener('click',clickHandler);
}) return {
x,
y
}
}

toRefs的使用

可以将响应式的对象转换成一个个ref变量

 const person = reactive({
name: 'guozhiqiang',
age: '20',
sex: '男',
tag: '帅哥'
}) const a = toRefs(person);//返回一个ref变量组成的数组
a.tag.value = '大帅哥';
console.log(a.tag.value);//大帅哥 console.log(person.tag.value)//大帅哥

ref获取标签元素

const InputText = ref<HTMLElement | null>();//获取ref为InputText的元素

shallowReactive与shallowRef

浅拷贝与深拷贝的区别,shallowReactive只有对象第一层的数据是响应式的,可以引起视图更新

shallowRef其实就是特殊的shallowReactive,也是只监听第一层数据

这两个适用于层数较深的对象但只需要对象第一层需要响应式的需求,可以减少浏览器开销

const person = shallowReactive({
name: 'guozhiqiang',
age: '20',
sex: '男',
tag: '帅哥',
job: {
name: 'web 开发工程师'
}
}) person.name = 'shuaige';//数据更新时页面会变化
person.job.name = 'aa';//数据更新时页面不会变化 //shallowRef生成非递归响应数据,只监听第一层数据的变化
let state= shallowRef({
gf: {
f: {
s: {
d: 4
},
c: 3
},
b: 2
},
a: 1
})

readOnly 和 shallowReadonly

用于设置数据只读,readonly和shallowReadonly的区别就是shallowReadonly只有第一层数据只读,其他数据可改变

const person = readonly({
name: 'guozhiqiang',
age: '20',
sex: '男',
job: {
name: 'web 开发工程师'
}
}) person.name = 'a'//错误,目标代理对象为只读 const person2 = shallowreadonly({
name: 'guozhiqiang',
age: '20',
sex: '男',
job: {
name: 'web 开发工程师'
}
}) person.name = 'a'//错误,只有第一层数据为只读
person.job.name = 'a'//可以

toRaw 和 markRaw

  • toRaw:

    作用:将一个由reactive生成的响应式对象转为普通对象。

使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。

  • markRaw:

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

应用场景:

有些值不应被设置为响应式的,例如复杂的第三方类库等。

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

 let data = {
name: "张三",
age: 18,
likeFood: {
fruits: {
apple: "苹果",
},
},
}
// 将普通对象变为响应对象
let person = reactive(data);
// 将响应对象变为普通对象
let p = toRaw(person)
//标记p,永远不能成为响应对象
markRaw(p)

toRef的使用

ref:是复制,修改响应式数据不会影响以前的数据,数据发生变化,界面就会自动更新。

toRef:是引用,修改响应式数据会影响以前的数据,数据发生变化,界面就会不会更新。

应用场景

如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新UI,那么就可以使用toRef

setup() {
const state = reactive({
age:5,
money:100
})
//把响应式数据state对象中的某个属性age变成了ref对象了
const age = toRef(state,'age')
//把响应式对象中的某个属性使用了ref进行包装,变成了一个ref对象
const money = ref(state.money)
}

customRef的使用

自定义hook防抖的函数,value传入的数据,将来数据的类型不确定,所以,用泛型delay防抖的间隔事件,默认是200毫秒

function userDebounceRef<T>(value: T,delay = 200){
let timeOutId:number
return customRef((track,trigger)=>{
return {
get() {
//告诉Vue追踪数据
track()
return value
}
set(newValue:T) {
//清除定时器
cleatTimeout(timeOutId)
//开启定时器
timeOutId = setTimeout(()=>{
value = newVlaue
//告诉Vue更新界面
trigger()
},delay)
}
}
})
export default defineComponent({
name:'App',
setup() {
const keyword = userDebounceRef('abs',500)
}
return {
keyword,
}
})

Provide 和 inject实现祖孙级组件通信

在setup中使用

   provide('color','red');

   const w = inject('color');

响应式数据的判断

//isRef: 检查一个值是否为一个ref对象
console.log(isRef({}))
//isReactive: 检查一个值是否为一个reactive对象
console.log(isReactive({}))
//isReadonly: 检查一个值是否为一个readonly对象
console.log(isReadonly({}))
//isProxy: 检查一个值是否为reactive和readonly代理的对象
console.log(isProxy({}))

手写组合api

shallowReactive 和 Reactive

const reactiveHandler = {
get(target: any,prop: any):any {
if(prop === 'is_reactive') return true;
const result = Reflect.get(target,prop);
console.log('拦截了读取数据',prop,target);
return result;
},
set(target: any,prop: any,val:any):any {
const result = Reflect.set(target,prop,val);
console.log('拦截了读取数据',prop,target);
return result;
},
deleteProperty(target: any, prop: any):any {
const result = Reflect.deleteProperty(target,prop);
console.log('拦截了删除属性',prop,target);
return result;
}
}
function shallowReactive<T>(target:T):T {
if(target && typeof target === 'object') {
console.log(1);
return new Proxy(target,reactiveHandler);
} else {
return target;
}
} function reactive<T>(target: T):T {
if(target && typeof target === 'object') {
if(target instanceof Array) {
target.forEach((item,index) => {
target[index] = reactive(item);
})
} else {
Object.keys(target).forEach(key => {
target[key] = reactive(target[key]);
})
}
return new Proxy(target,reactiveHandler);
} else {
return target;
} } export {
shallowReactive,
reactive
}

shallowReadonly 和 readonly

const readonlyHandler = {
get(target: any,prop: any):any {
if(prop ==== 'is_readonly') return true;
const result = Reflect.get(target,prop);
console.log('拦截了读取数据',prop,target);
return result;
},
set(target: any,prop: any,val:any):any {
console.log('只读不能设置属性',prop,target);
return true;
},
deleteProperty(target: any, prop: any):any {
console.log('只读不能删除属性',prop,target);
return true;
}
}
function shallowReadonly<T>(target:T):T {
if(target && typeof target === 'object') {
return new Proxy(target,readonlyHandler);
} else {
return target;
}
} function readonly<T>(target: T):T {
if(target && typeof target === 'object') {
if(target instanceof Array) {
target.forEach((item,index) => {
target[index] = readonly(item);
})
} else {
Object.keys(target).forEach(key => {
target[key] = readonly(target[key]);
})
}
return new Proxy(target,readonlyHandler);
} else {
return target;
} } export {
shallowReadonly,
readonly
}

shallowRef 和 ref

function shallowRef(target) {
return {
is_shallowRef: true,
_value: target,
get value(){
console.log('劫持到了读取');
return this._value
},
set value(val) {
console.log('劫持到了设置')
this._value = val;
}
}
} function ref(target) {
target = reactive(target);
return {
isRef: true,
_value: target,
get value(){
console.log('劫持到了读取');
return this._value
},
set value(val) {
console.log('劫持到了设置')
this._value = val;
}
}
} export {
shallowRef,
ref
}

isRef 和 isReactive 和 isReadonly

function isRef(target) {
return target&&target.isRef;
} function isReactive(target) {
return target&&target.isReactive;
} function isReadonly(target) {
return target&&target.isReadonly;
} export {
isReactive,
isRef,
isReadonly
}

Fragment(片断)

  • 在vue2中,组件必须有一个根标签
  • 在vue3中, 组件可以没有根标签,内部会将多个标签包含在一个Fragment标签中
  • 减少标签层级,减小内存占用
<Fragment>
<div>111</div>
<div>222</div>
</Fragment>

Teleport(瞬移)

  • Teleport提供了一种干净的方法,让组件的html在父组件界面外的特定标签插入显示

    index.html
  <div id="app"></div>
<div id="teleport-target"></div>
<script type="module" src="/src/main.js"></script>

src/components/HelloWorld.vue 中,添加如下,留意 to 属性跟上面的 id 选择器一致

  <button @click="showToast" class="btn">打开 toast</button>
<!-- to 属性就是目标位置 -->
<teleport to="#teleport-target">
<div v-if="visible" class="toast-wrap">
<div class="toast-msg">我是一个 Toast 文案</div>
</div>
</teleport>
import { ref } from 'vue';
export default {
setup() {
// toast 的封装
const visible = ref(false);
let timer;
const showToast = () => {
visible.value = true;
clearTimeout(timer);
timer = setTimeout(() => {
visible.value = false;
}, 2000);
}
return {
visible,
showToast
}
}
}

效果如下

  • 可以看到,我们使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与 <div id="app"></div> 同级,也就是在 body 下,但是 teleport 的状态 visible 又是完全由内部 Vue 组件控制

  • 与 Vue components 一起使用 —— modal

  • 如果 包含 Vue 组件,则它仍将是 父组件的逻辑子组件

  • 接下来我们以一个 modal 组件为例

  <div id="app"></div>
<div id="teleport-target"></div>
+ <div id="modal-container"></div>
<script type="module" src="/src/main.js"></script>
<teleport to="#modal-container">
<!-- use the modal component, pass in the prop -->
<modal :show="showModal" @close="showModal = false">
<template #header>
<h3>custom header</h3>
</template>
</modal>
</teleport>
  • JS 核心代码如下:
import { ref } from 'vue';
import Modal from './Modal.vue';
export default {
components: {
Modal
},
setup() {
// modal 的封装
const showModal = ref(false);
return {
showModal
}
}
}
  • 在这种情况下,即使在不同的地方渲染 Modal,它仍将是当前组件(调用 Modal 的组件)的子级,并将从中接收 show prop

  • 这也意味着来自父组件的注入按预期工作,并且子组件将嵌套在 Vue Devtools 中的父组件之下,而不是放在实际内容移动到的位置

  • 看实际效果以及在 Vue Devtool 中

Vue3 setup详解的更多相关文章

  1. vue3.0API详解

    Vue 3.0 于 2020-09-18 发布了,使用了 Typescript 进行了大规模的重构,带来了 Composition API RFC 版本,类似 React Hook 一样的写 Vue, ...

  2. # Vue3 setup 函数

    Vue3 setup 函数 vue2 和 vue3 开发的区别 首先,目前来说 vue3 发布已经有一段时间了,但是呢,由于还处于优化完善阶段,对于 vue3 开发项目的需求不是很高,主要还是以 vu ...

  3. NSURLSession详解

    导语 现在NSURLConnection在开发中会使用的越来越少,iOS9已经将NSURLConnection废弃,现在最低版本一般适配iOS7,所以也可以使用. NSURLConnection相对于 ...

  4. [转]keil使用详解

    第一节 系统概述 Keil C51是美国Keil Software公司出品的51系列兼容单片机C语言软件开发系统,与汇编相比,C语言在功能上.结构性.可读性.可维护性上有明显的优势,因而易学易用.用过 ...

  5. pip安装使用详解(转)

    pip类似RedHat里面的yum,安装Python包非常方便.本节详细介绍pip的安装.以及使用方法. 1.pip下载安装 1.1 pip下载   1 # wget "https://py ...

  6. JavaScript事件详解-jQuery的事件实现(三)

    正文 本文所涉及到的jQuery版本是3.1.1,可以在压缩包中找到event模块.该篇算是阅读笔记,jQuery代码太长.... Dean Edward的addEvent.js 相对于zepto的e ...

  7. 【iOS自定义键盘及键盘切换】详解

    [iOS自定义键盘]详解 实现效果展示: 一.实现的协议方法代码 #import <UIKit/UIKit.h> //创建自定义键盘协议 @protocol XFG_KeyBoardDel ...

  8. 李洪强iOS经典面试题156 - Runtime详解(面试必备)

    李洪强iOS经典面试题156 - Runtime详解(面试必备)   一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C ...

  9. 李洪强iOS经典面试题155 - const,static,extern详解(面试必备)

    李洪强iOS经典面试题155 - const,static,extern详解(面试必备) 一.const与宏的区别(面试题): const简介:之前常用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽 ...

随机推荐

  1. JavaScript 的诞生历史

    看到一篇介绍JS诞生历史的文章,很有意思,文章里描述了很多的历史细节 https://webdevelopmenthistory.com/1995-the-birth-of-javascript/

  2. C++ TCHAR* 与char* 互转

    C++ TCHAR* 与char* 互转 在MSDN中有这么一段: Note: The ANSI code pages can be different on different computers, ...

  3. Java 线程池中 submit() 和 execute()方法有什么区别?

    两个方法都可以向线程池提交任务,execute()方法的返回类型是 void,它定义在 Executor 接口中. 而 submit()方法可以返回持有计算结果的 Future 对象,它定义在 Exe ...

  4. 阐述 ArrayList、Vector、LinkedList 的存储性能和特性?

    ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的 数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉 及数组元素移动等内存操作,所以索引数 ...

  5. 为什么 wait(), notify()和 notifyAll ()必须在同步方法或 者同步块中被调用?

    当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接 着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify() 方法.同样的,当一个线程需要调用对 ...

  6. memcached 是原子的吗?

    所有的被发送到 memcached 的单个命令是完全原子的.如果您针对同一份数据 同时发送了一个 set 命令和一个 get 命令,它们不会影响对方.它们将被串行化. 先后执行.即使在多线程模式,所有 ...

  7. Netty学习摘记 —— 深入了解Netty核心组件

    本文参考 本篇文章是对<Netty In Action>一书第三章"Netty的组件和设计"的学习摘记,主要内容为Channel.EventLoop.ChannelFu ...

  8. torch.optim.SGD参数详解

    随机梯度下降法 $\theta_{t} \leftarrow \theta_{t-1}-\alpha g_{t}$ Code: optimzer = torch.optim.SGD(model.par ...

  9. 重定向(redirect)与转发(forward)的区别

    重定向(redirect)与转发(forward)的区别 1.重定向时地址栏会发生改变,转发时地址栏不会发生改变 当浏览器请求资源时,服务器直接访问目标地址的URL,将URL的响应内容读取,之后再将读 ...

  10. Numpy的数学统计函数

    Numpy的数学统计函数 本节内容: 1.Numpy有哪些数学统计函数: 函数名 说明 np.sum 所有元素的和 np.prod 所有元素的乘积 np.cumsum 元素的累积加和 np.cumpr ...