共计 19654 个字符,预计需要花费 50 分钟才能阅读完成。
Vue3
[TOC]
学习
vue3 重写的动机
- 应用新的 js 原生个性
-
解决设计和体系架构的缺点
在 vue3 中新增了 es6 的 proxy,proxy 打消了 vue2 的所有限度(比方新对象属性的减少、数组元素的间接批改不会触发响应式机制,这是很多老手所谓的 bug),而且有着更好的性能。
Proxy 就是真正意义给一个对象包上一层代理从而去实现数据侦听劫持的操作,所以总的来说这个复杂度是间接少了一个数量级的。只有你对这个代理后的对象拜访或批改外面的货色都能被这层代理所感知到,进而去动静地决定返回什么货色给你,并且也不再须要把选项的这些货色再反复挂到组件实例的 this 下面,因为你拜访的时候是有这个信息晓得你拜访的货色是属于 props 还是 data 还是其余的,vue 只须要依据这个信息去对应的数据结构外面拿进去就能够了,单就这一点而言你就能够感觉到组件的内存占用曾经少了一半。
vue3 的改良及特点
1. 性能的晋升:打包大小缩小 41%,首次渲染快 55%,更新快 133%,内存应用缩小 54%。
2. 新推出的 Composition API 使组件更易保护,缩小无用数据绑定页面更晦涩。
4. 更好 TypeScript 反对,能够在创立命令里间接配置,页面集成畅通无阻。
5.Teleport(瞬移组件)、Suspense(解决异步加载组件问题)和全局 API 的批改和优化。
6.Vue3 兼容大部分 Vue2 的个性,用 Vue2 代码开发 Vue3 都能够。
Vue3.x 中的新增点
1. 多根节点组件
在 vue3 中,组件正式反对多根节点组件,即片段!
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
2.setup
要开始应用 Composition API,咱们首先须要一个能够理论应用它的中央。在 Vue 组件中,咱们将此地位称为 setup。
setup 执行时尚未创立组件实例,所以不能应用 this,此时 this 为 undefined。除了 props,无法访问组件中申明的任何 data、computed、methods。
注:setup 是为了优化性能让程序按需引入全局对立
参数 (props, context)
// props 父组件传过来的 props props 是具备反馈性的(传入新的 props 时会自动更新)
// context {attrs, emit, slots}
setup(props, context) {console.log(context)
/**
* attrs: Proxy
* emit: (event, ...args) => instance.emit(event, ...args)
* slots: Proxy
*/
}
setup 生命周期钩子
钩子函数 | setup 钩子 |
---|---|
beforeCreate | 没有 |
created | 没有 |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
因为 setup 是围绕 beforeCreate 和 created 生命周期挂钩运行的,因而您无需显式定义它们。换句话说,应该在这些钩子中编写的任何代码都应间接在 setup 函数中编写。
用法一:
<template>
<div class="home">
<div> 名字:{{name}}</div>
<ul>
<li v-for="item in list" :key="item" @click="show(item)">{{item}}</li>
</ul>
</div>
</template>
<script lang="ts">
import {defineComponent, ref} from "vue";
// 注:defineComponent 在 TypeScript 下,给予了组件正确的参数类型推断
export default defineComponent({
name: "Home",
components: {},
props:['msg'],
setup(props,context) {
// 注:setup 函数是处于生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 也就说在 setup 函数中是无奈应用 data 和 methods 中的数据和办法,而 methods 等能够应用 setup 中 return 进来的数据。/*
一. 函数的第一个参数是 props 用于接管 props.msg
这个 props 是一个响应式的 Proxy 对象,不能够解构,解构后会失去响应,如果要用解构的形式,要用 toRefs
let {msg} = toRefs(props) // 然而解析成 ref 了要用 msg.value, 所以间接用 props.msg 更简略
二.context 对象在 setup()中裸露三个属性 attrs、slots 和 emit 因为在 setup 函数中还没有创立 Vue 实例,是无奈应用 vm.$attrs、vm.$slots 和 vm.$emit 的,所以这三个属性充当了这样的作用,应用办法雷同。留神:context.attrs 和 vm.$attrts 蕴含的是在实例 vm.props 中没有被申明辨认的 attribute(class 和 style 除外)。所以 setup()中参数 props 中裸露的变量,就不会在 context.attrs 中裸露。context.slots 和 vm.$slots 只能拜访具名插槽,没有命名的插槽或者 v -slot:default 的是没有裸露的。context 的 attrs 和 slots 是有状态的,当组件更新时也会实时更新,所以也不要解构。但与 props 不同的是,它们不是响应式的,在 setup()中的应用应放弃只读的状态,如果要扭转能够在 onUpdated 的周期函数中进行。context.emit 和 vm.$emit 能够触发实例上的监听事件。*/
const list = ref(["深圳", "北京", "上海"]);
const name = ref("");
// 注:用 ref 是为了转换成援用类型,让全局援用保持一致,而之前原始类型是不行的,所以要 name.value 的方示赋值
const show = (index: string) => {name.value = index;};
// 注:不 return 进来的数据,模板是无奈应用的。return {
list,
name,
show
};
},
});
</script>
用法二: reactive() 优化
<template>
<div class="home">
<div> 名字:{{data.name}}</div>
<ul>
<li v-for="item in data.list" :key="item" @click="data.show(item)">{{item}}</li>
</ul>
</div>
</template>
<script lang="ts">
import {defineComponent, reactive} from "vue";
export default defineComponent({
name: "Home",
components: {},
setup() {
const data = reactive({list: ["深圳", "北京", "上海"],
name: "",
show: (index: string) => {data.name = index;},
});
return {data};
},
});
</script>
用法三: toRefs() 优化
<template>
<div class="home">
<div> 名字:{{name}}</div>
<ul>
<li v-for="item in list" :key="item" @click="show(item)">{{item}}</li>
</ul>
</div>
</template>
<script lang="ts">
import {defineComponent,reactive,toRefs} from "vue";
export default defineComponent({
name: "Home",
components: {},
setup() {
const data = reactive({list: ["深圳", "北京", "上海"],
name: "",
show: (index: string) => {data.name = index;},
});
const refData = toRefs(data);
// 不能间接解析 ...data 必须用 toRefs()
return {...refData};
},
});
</script>
jsx
setup() {const root = ref(null)
return () => <div ref={root} />
}
在 setup 获取示例 this 的办法
setup 外面 this
指向window
,composition 的文档中也没有提到怎么获取组件实例。
咱们能够通过 getCurrentInstance()
这个接口获取组件实例:
setup() {// getCurrentInstance()能够获取组件实例
const instance = getCurrentInstance()
console.log(instance);
onMounted(()=>{
// 组件实例的上下文才是咱们相熟的 this
console.log(instance.ctx.foo); // 'foo'
console.log(instance.ctx.bar()); // '我是 bar 办法'
})
return {}},
然而很快咱们又蒙圈了,这个组件实例和咱们以前相熟的 this
不一样,间接拜访 this.foo
还是找不到数据。
vue3 中组件实例构造如下,各个选项中的 this
实际上是 ctx
或proxy
当然坑还是有的,你仔细观察这个 ctx
,发现它不是一个 Proxy 对象,也就是这位兄台只有值却没有响应性,所以如果要利用响应个性,还得用proxy
这个属性返回上下文对象,如果只是想要数据,上图中不是有个 data
也是 Proxy 类型的嘛。
setup() {const { proxy, data} = getCurrentInstance()
// 想要利用响应能力,就要应用 proxy 这个上下文
watchEffect(() => {console.log(proxy.foo) // foo 变动会有输入
console.log(data.foo) // foo 变动会有输入
})
},
最初大家还要留神,setup()执行的工夫点是很早的,甚至早于 created,因而 foo 和 bar 的拜访如果没有特意放到 onMounted 外面还真没有。
setup() {const instance = getCurrentInstance()
console.log(instance.ctx.foo); // undefined
console.log(instance.ctx.bar()); // undefined
},
罕用解决篇:
setup 函数太长了怎么办?
setup(){let { val, todos, addTodo} = useTodo()
let {count,double,add} = useCounter()
let {x, double:doubleX} = useMouse()
return {
val, todos, addTodo,
count,double,add,
x,doubleX
}
}
return 的上下文太长了,咱们能够应用 vue3 的 setup script
性能,把 setup 这个配置也优化掉,一个性能 export 一次
<script setup>
import useCounter from './counter'
import useMouse from './mouse'
import useTodo from './todos'
let {val, todos, addTodo} = useTodo()
export {val, todos, addTodo}
let {count,double,add} = useCounter()
export {count,double,add}
let {x, double:doubleX} = useMouse()
export {x,doubleX}
</script>
我的属性怎么就不响应了
上面是我第一次写的代码
setup({foo, bar}) {watchEffect(() => {console.log(foo, bar) // foo,bar 发生变化,也不会有输入
})
}
props 是一个 Proxy 对象,间接解构就失去了响应能力,所以看待 props 要温顺,不能动不动就劈开了
setup(props) {watchEffect(() => {console.log(props.foo, props.bar) // foo,bar 发生变化,会有输入
})
}
真想劈开也行,看你喜爱什么姿态了
setup(props) {const { foo, bar} = toRefs(props)
watchEffect(() => {console.log(foo.value, bar.value) // foo,bar 发生变化,会有输入
})
}
怎么派发自定义事件?
忽然之间没有 this 了之后,如同忽然生存不能自理了,再也不能用 this.$emit()
派发事件了。
其实通过组件实例是能够派发事件的,比方:
setup() {getCurrentInstance().emit('ooxx')
}
// 然而这样比拟麻烦,所以咱们要用到 setup 函数的第二个参数:setup(props, ctx) {ctx.emit('ooxx')
}
// 当然,还能把 emit 间接解构进去应用更不便:setup(props, { emit}) {emit('ooxx')
}
vue3 获取以后页面路由
1.
import {getCurrentInstance} from 'vue'
setup() {const { ctx} = getCurrentInstance()
console.log(ctx.$router.currentRoute.value)
}
2.
import {useRoute} from 'vue-router'
import {toRaw} from 'vue'
setup () {const route = useRoute()
console.log(toRaw(route))
}
父组件调用子组件办法
// 父组件
<Listmodify :isShow="isShow" ref="child"></Listmodify>
setup(props, context) {const child = ref();
const handleEdit = () => {child.value.str();
};
return {handleEdit, child};
}
// 子组件
setup(props, context) {const str = () => {console.log("我被父组件调用了");
};
return {str};
}
3.Teleport 传送
(解决款式等抵触问题不挂载到 app 下)
Teleport 提供了一种办法,是咱们能够管制要在 Dom 中哪个父对象下出现 HTML,而不用求助于全局状态或将其拆分为两个局部(有点像哆啦 A 梦的任意门,集体了解)
Teleport 的应用
示例一:
1、先搭建一个 vue3 的我的项目
2、开始应用 示例:
<div id="app"></div>
<div id="teleport-target"></div>
<div id="modal-container"></div>
<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
}
}
}
3、与 Vue components 一起应用 —— 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>
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
示例二:
- index.html 页面新加插入点(会挂载到 #headTitie DOM 下)
<div id="headTitie"></div>
<div id="app"></div>
- 在 components 目录下新建 headTitle.vue
<template>
<teleport to="#headTitie">
<div class="head">
<h1>{{title}}</h1>
</div>
</teleport>
</template>
<script lang="ts">
import {defineComponent, ref} from "vue";
export default defineComponent({
name: "headTitie",
setup() {const title = ref("Vue3 新个性示例");
return {title,};
},
});
</script>
- 在 App.vue 加
<template>
<headTitle />
<router-view />
</template>
<script lang="ts">
import headTitle from "./components/headTitle.vue";
export default {
name: "App",
components: {headTitle,},
};
</script>
4.watch 监听
从 vue 中引入 watch 能够参考 第 4 点 Reactivity(反馈性)
- 监听 ref 申明的 反馈性数据
- 监听 reactive 申明的反馈性数据
- 监听 props 外面的数据
<script>
// 肯定应用之前引入
import {ref, watch} from 'vue'
setup(props) {
// 监听 ref 数据
let count = ref(0)
watch(count, (val, old) => {// 新数据、老数据})
// 监听 reactive 数据 对象
let person = reactive({age: 18})
watch(() => person.age, (val, old) => {// 新数据、老数据})
// 监听 reactive 数据 数组
let arr = reactive([1, 2, 3])
watch(arr, (val, old) => {// 新数据、老数据})
// 监听 props 的数据 退出 props 传了一个 testkey
watch(() => props.testkey, () => {})
// 要留神 return
return {
count,
person,
arr
}
}
</script>
<script lang="ts">
import {defineComponent, ref, reactive, toRefs, watch} from "vue";
export default defineComponent({
name: "Home",
components: {},
setup() {const text = ref("测试单个值");
const data = reactive({list: ["深圳", "北京", "上海"],
name: "",
show: (index: string) => {data.name = index;},
});
//watch(text, 单个用法,watch([text,()=>data.name], 多个用法,注:()=>data.name 为了兼容 vue2
watch([text,()=>data.name], (newValue, oldValue) => {console.log(`new--->${newValue}`);
console.log(`old--->${oldValue}`);
});
const refData = toRefs(data);
return {...refData,};
},
});
</script>
watchEffect 和 watch 有啥不同?
这俩办法很类似,都是察看响应式数据,变动执行副作用函数,但有如下不同:
- watch 须要明确指定监督指标
watch(() => state.counter, (val, prevVal) => {})
//watchEffect 不须要
watchEffect(() => {console.log(state.counter)
})
- watch 能够获取变动前后的值
- watch 是懒执行的,它等效于 vue2 中的
this.$watch()
,watchEffect 为了收集依赖,要立刻执行一次
生命周期钩子能不能写多个?
当然能够写多个,最初它们会按注册程序顺次执行:
setup() {onMounted(() => {})
onMounted(() => {})
onMounted(() => {})
}
甚至还能拆分出多个雷同生命周期钩子到独立函数中
function fun1() {
// 这里能够用 onMounted 执行代码
onMounted(() => {})
}
function fun2() {
// 这里还能够用 onMounted 执行代码
onMounted(() => {})
}
setup() {fun1()
fun2()
onMounted(() => {})
}
5. computed 计算属性
从 vue 中引入 computed
<script>
import {ref, computed} from 'vue'
setup(props) {let count = ref(0)
// 以后组件的计算属性
let countCom = computed(() => {return count.value * 2})
//props 传进来的计算属性
let countCom2 = computed(() => {return props.propsKey * 2})
}
</script>
扩大——
- 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
6. setup 生命周期钩子应用
- setup 的 onMounted 执行 在 整个组件的 mounted 之前
- setup 的 onMounted 执行 在 setup 同步组件执行之后
- setup 的 onMounted 外面以及 setup 不能应用 this
<script>
import {onMounted} from 'vue'
mounted () {console.log(4)
},
setup () {onMounted(() => {console.log(1)
})
console.log(2)
setTimeout(() => {console.log(3)
}, 0)
// 打印程序
// 2 1 4 3
onMounted(() => {console.log(this)
})
// 打印
//undefined
}
</script>
7.provide 和 inject
<!– 一句话介绍:provide 能够向所有子孙组件提供数据以及提供批改数据的办法,子孙组件用 inject 应用数据。–>
从 vue 中引入父组件 provide
<script>
import {provide} from 'vue'
export default {setup () {
provide('person', {
name: 'Bob',
age: 18
})
provide('city', '杭州')
}
}
</script>
子孙组件 inject
<script>
import {inject} from 'vue'
export default {setup () {
// 父辈组件 provide 的数据
let person = inject('person')
let city = inject('city')
// 父辈组件 没有提供的值,给个默认值 不给默认为为 undefined 而且会有 Vue warn 揭示
let noProvide = inject('noProvide', 'noProvide hah')
}
}
</script>
provide 带有反馈性的数据 父组件值批改,子孙组件值会对应的批改
<script>
import {provide} from 'vue'
export default {setup () {let city = ref('滨江')
provide('city', city)
}
}
</script>
provide 一个 函数
<script>
import {provide} from 'vue'
export default {setup () {let provideFun = () => {console.log('provide fun')
}
provide('provideFun', provideFun)
}
}
</script>
// 父组件
import {ref,provide} from 'vue';
// provide()承受两参数(name: string,data: 传递的数据)
setup(){let init = ref(1);
function change(){init.value += 1}
provide('init',init)
provide('update',change) // 数据响应式的要害,批改数据的办法也要一应传递上来,否则会不响应
return {change,init}
}
// 子组件
import {inject,readonly} from 'vue';
// inject()接管两个参数(name: 要承受 provide 的 name,data: 可不填,默认的数据)
setup(){const oneInit = inject('init') // 接管上方传递下来的数据
const updateUser = inject('updatea1',readonly(oneInit))
// 接管办法,readonly 只读,免得外在起因批改了数据
return {
oneInit,
updateUser
}
}
8.Reactivity(反馈性)
Vue 最独特的性能之一是不引人注目的反馈零碎。
当您将纯 JavaScript 对象作为 data 选项传递给应用程序或组件实例时,Vue 将遍历其所有属性,并应用带有 getter 和 setter 的处理程序将它们转换为 Proxies
- 跟踪更改它的函数:在代理的 getter 中进行此操作 effect
- 触发函数,以便它能够更新最终值:在代理中的 setter 中进行操作 trigger
申明反馈性数据
个别应用 ref、reactive、readonly 来申明数据
- ref 申明根本类型
- reactive 申明援用类型
- readonly 申明只读援用类型
import {ref, reactive, readonly} from 'vue'
export default {setup () {
// 个别用 ref 申明 根本 类型
// 用 reactive 申明 援用 类型
let count = ref(0) // 在 script 应用 count.value,在 template 应用 {{count}}
let state = reactive({name: 'Bob'}) // 在 script 应用 state.name,在 template 应用 {{state.name}}
// 用 ref 申明 援用类型
let obj1 = ref({count: 1})
// 用 reactive 申明 根本 类型 正告⚠️ 能够失常应用,然而没有反馈性
let num1 = reactive(10) //value cannot be made reactive: 10
// readonly
// readonly 和 reactive 一样,然而申明的是只读数据 申明根本类型和 reactive 一样正告揭示
let person = readonly({age: 18})
setTimeout(() => {
// 定时器 批改 正告 ⚠️
person.age = 10 // Set operation on key "age" failed: target is readonly
});
// 肯定要 return 才能够在 template 和 script 其余中央应用
return {
count,
state,
obj1
}
}
}
count 打印出的数据
RefImpl {_rawValue: 0, _shallow: false, __v_isRef: true, _value: 0}
__v_isRef: true
_rawValue: 0
_shallow: false
_value: 0
value: 0
state 打印出的数据
Proxy {name: "Bob"}
[[Handler]]: Object
deleteProperty: ƒ deleteProperty(target, key)
get: ƒ (target, key, receiver)
has: ƒ has(target, key)
ownKeys: ƒ ownKeys(target)
set: ƒ (target, key, value, receiver)
[[Target]]: Object
name: "Bob"
[[IsRevoked]]: false
obj1 打印出的数据
RefImpl {_rawValue: {…}, _shallow: false, __v_isRef: true, _value: Proxy}
__v_isRef: true
_rawValue: {count: 1}
_shallow: false
_value: Proxy {count: 1}
value: Proxy
[[Handler]]: Object
[[Target]]: Object
count: 1
[[IsRevoked]]: false
num1 打印的数据
10
template 中应用
<!-- 应用 ref 定义的根本类型间接应用 -->
<!-- 应用 reactive 定义的援用类型间接. 对应的属性 -->
<div class="vue3_pro">
count: {{count}}
state: {{state.name}}
</div>
9. Vue3 生命周期函数用法
须要引入 (注:vue2 生命周期函数不影响)
<script lang="ts">
import { defineComponent,ref,reactive,toRefs,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from "vue";
export default defineComponent({
name: "Home",
components: {},
setup() {//setup() 开始创立组件之前,在 beforeCreate 和 created 之前执行。const data = reactive({list: ["深圳", "北京", "上海"]
});
onBeforeMount(()=>{// 组件挂载到节点上之前执行的函数。})
onMounted(()=>{// 组件挂载实现后执行的函数。})
onBeforeUpdate(()=>{// 组件更新之前执行的函数})
onUpdated(()=>{// 组件更新实现之后执行的函数。})
onBeforeUnmount(()=>{// 组件卸载之前执行的函数。})
onUnmounted(()=>{// 组件卸载实现后执行的函数。})
onActivated(()=>{// 被蕴含在 <keep-alive> 中的组件,会多出两个生命周期钩子函数。被激活时执行。})
onDeactivated(()=>{// 比方从 A 组件,切换到 B 组件,A 组件隐没时执行。})
onErrorCaptured(()=>{// 当捕捉一个来自子孙组件的异样时激活钩子函数。})
//< 调试用生命函数
onRenderTracked((event)=>{
// 跟踪所有状态触发
console.log(event);
});
onRenderTriggered((event) => {
// 跟踪以后状态触发
console.log(event);
//key 那边变量产生了变动
//newValue 更新后变量的值
//oldValue 更新前变量的值
//target 目前页面中的响应变量和函数
});
// 调试用生命函数 />
const refData = toRefs(data);
return {...refData};
},
mounted(){console.log("vue2 生命周期");
}
});
</script>
10.Vue3 模块化重用性能 (优化 mixins)
1. 新建 useTime.ts 文件
import {ref} from "vue";
const time = ref("00:00:00");
const getTime = () => {const now = new Date();
const h= now.getHours() < 10 ? "0" + now.getHours() : now.getHours();
const m = now.getMinutes() < 10 ? "0" + now.getMinutes() : now.getMinutes();
const s= now.getSeconds() < 10 ? "0" + now.getSeconds() : now.getSeconds();
time.value = h + ":" + m + ":" + s;
setTimeout(getTime, 1000);
};
export {time, getTime}
2. 引入
<template>
<div class="home">
<div> 工夫:{{time}} <button @click="startTime"> 开始 </button></div>
</div>
</template>
<script lang="ts">
import {defineComponent, ref} from "vue";
import {time, getTime} from './useTime';
export default defineComponent({
name: "Home",
components: {},
setup() {const startTime = () => {getTime();
};
return {
startTime,
time
};
},
});
</script>
11.Suspense 异步申请组件
- 新建 Demo.vue
<template>
<div class="Demo">
<div> 名字:{{name}}</div>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
export default defineComponent({
name: "Demo",
components: {},
setup() {return new Promise((resolve, reject) => {setTimeout(() => {return resolve({ name: "我是 Suspense 异步申请组件"});
}, 2100);
});
},
});
</script>
- 应用引入 home.vue
<template>
<div class="home">
<Suspense>
<template #default>
<Demo />
</template>
<template #fallback>
<p> 加载中...</p>
</template>
</Suspense>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
import Demo from "./Demo.vue";
export default defineComponent({
name: "Home",
components: {Demo}
});
</script>
Composition-Api:
12.ref()
ref()
函数能够依据给定的 值来创立一个响应式的数据对象,返回值是一个 对象,且只蕴含一个 .value
属性。
- 用 ref 创立响应式对象
// 引入 ref
import {ref} from '@vue/composition-api'
setup() {
// 创立响应式对象
const count = ref(0);
return {count}
}
- 应用响应式对象
<p> 以后的 count 的值为:{{count}}</p>
<button @click="count++"> 点击减少 count</button>
在 JS 中批改这个值要额定加上value
:
- ref 的注意事项
-
在
setup()
函数内,由ref()
创立的响应式数据返回的是对象,所以须要用.value
来拜访;而在
setup()
函数内部则不须要.value
,间接拜访即可。 - 能够在
reactive
对象中拜访ref()
函数创立的响应式数据。 - 新的
ref()
会笼罩旧的ref()
。
ref 数据响应式监听,和 react-hook 如同差不多。ref 函数传入一个值作为参数,返回一个基于该值的响应式 Ref 对象,该对象中的值一旦被扭转和拜访,都会被跟踪到,就像咱们改写后的示例代码一样,通过批改 count.value 的值,能够触发模板的从新渲染,显示最新的值
13.reactive()
reactive 是用来定义更加简单的数据类型,然而定义后外面的变量取出来就不在是响应式 Ref 对象数据了
reactive()
函数接管一个一般的对象,返回出一个响应式对象。
在 Vue2.x 的版本中,咱们只须要在
data()
中定义一个数据就能将它变为响应式数据,在 Vue3.0 中,须要用reactive
函数或者ref
来创立响应式数据。
- 用 reactive 创立响应式对象
// 在组件库中引入 reactive
import {reactive} from '@vue/ composition-api'
setup() {
// 创立响应式对象
const state = reactive({count:0});
// 将响应式对象 return 进来,裸露给模板应用
return state;
}
- 应用响应式对象
<p> 以后的 count 的值为:{{count}}</p>
<button @click="count++"> 点击减少 count< button>
composition-api 引入了独立的数据响应式办法reactive
,它能够将传入的对象做响应式解决:
const state = reactive({foo: 'foo',})
watchEffect(() => {console.log(state.foo)
})
state.foo = 'foooooo' // 输入 'foooooo'
这个形式相似于咱们设置的 data
选项,可能解决咱们大部分需要。然而也有以下问题:
- 当咱们间接导出这个 state 时,咱们在模板中就要带上 state 这个前缀
setup() {const state = reactive({})
return {state}
}
<div>{{state.foo}}</div>
// 为了解决这个问题又要引入 toRefs
setup() {const state = reactive({})
return {...toRefs(state) }
}
<div>{{foo}}</div>
ref 和 reactive 之间的抉择
如果是单值,倡议 ref,哪怕是个单值的对象也能够
一个业务关注点有多个值,倡议 reactive
14. readonly()
传入一个响应式对象、一般对象或 ref,返回一个 只读 的对象代理。这个代理是深层次的,对象外部的数据也是只读的。
const state = reactive({count: 0})
const copy = readonly(state)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// state 上的批改会触发 copy 上的侦听
state.count++
// 这里只读属性不能被批改
copy.count++ // warning!
15. watchEffect()
watchEffect()
会立刻执行传入的函数,并 响应式侦听其依赖,并在其依赖变更时从新运行该函数。
- 根本用法
const count = ref(0)
// 首次间接执行,打印出 0
watchEffect(() => console.log(count.value))
setTimeout(() => {
// 被侦听的数据发生变化,触发函数打印出 1
count.value++
}, 1000)
- 进行侦听
watchEffect()
应用时返回一个函数,当执行这个返回的函数时,就进行侦听。
const stop = watchEffect(() => {/* ... */})
// 进行侦听
stop()
Composition-Api 依赖工具
1. isRef()
isRef()
顾名思义,是用来判断某个值是否为 ref()
创立进去的响应式的值。
当你须要开展某个可能为 ref()
创立的响应式的值的时候,会用到它:
import {isRef} from '@vue/composition-api'
const unwrapper = isRef(foo) ? foo.value : foo
2.toRefs()
toRefs()
能够将 reactive()
创立进去的响应式对象转换成 内容为 ref 响应式的值的一般对象
在搞清楚
toRefs()
的用法之前,咱们须要先理解一下用reactive()
和ref()
创立进去的响应式对象的区别:
- 用
reactive()
创立的响应式对象,整个对象是响应式的,而对象里的每一项都是一般的值。当你把它用开展运算符开展后,整个对象的一般值都不是响应式的;- 而用
ref()
创立的响应式的值,自身就是响应式的,并不依赖于其余对象。
所以当你须要开展 reactive()
创立的响应式对象,又不想让他们失去响应式特点的时候,就须要用 toRefs()
将它进行转换:
mport {toRefs} from '@vue/composition-api'
setup() {
// 定义响应式数据对象
const state = reactive({count: 0})
// 定义简略的函数,使 count 每次 +1
const add = () => {state.count++}
// 将 setup 函数的内容 return 进来,供外界应用
return {
// 将 state 开展导出,同时将其属性都转化为 ref 模式的响应式数据
...toRefs(state),
add
}
}
<template>
<div>
<p> 以后的 count 值为:{{count}}</p>
<button @click="add"> 点击 +1</button>
</div>
</template>
vue3 与 vue2 的变动
这都是本人试出来的坑
router-link
改良如下:
- 删除
tag
prop – 应用作用域插槽代替 - 删除
event
prop – 应用作用域插槽代替 - 减少
scoped-slot
API - 进行主动将
click
事件调配给外部锚点 - 增加
custom
prop 以齐全反对自定义的router-link
渲染
在 vue2-router 中,想要将 <roter-link>
渲染成某种标签,例如 <button>
,须要这么做:
<router-link to="/" tag="button"> 按钮 </router-link>
!-- 渲染后果 -->
<button> 按钮 </button>
当前可能须要这样做:
<router-link
to="/foo"
v-slot="{href, route, navigate, isActive, isExactActive}"
>
<li
:class="[isActive &&'router-link-active', isExactActive &&'router-link-exact-active']"
>
<a :href="href" @click="navigate">{{route.fullPath}}</a>
</li>
</router-link>
插槽 prop 的对象蕴含上面几个属性:
1、href:解析后的 URL。将会作为一个 a 元素的 href attribute。
2、route:解析后的规范化的地址。
3、navigate:触发导航的函数。会在必要时主动阻止事件,和 router-link 同理。
4、isActive:如果须要利用激活的 class 则为 true。容许利用一个任意的 class。
5、isExactActive:如果须要利用准确激活的 class 则为 true。容许利用一个任意的 class。
API 变更
对于 Composition API 的阐明:
1、useRouter
2、useRoute
3、onBeforeRouteLeave
4、onBeforeRouteUpdate
5、useLink