VUE3新特性
1. Composition API
-
setup
使用
setup
时,它接受两个参数:- props: 组件传入的属性
- context:提供三个属性
setup 中接受的
props
是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。const { xxx } = toRefs(props) // 可用toRefs解决
setup
中不能访问 Vue2 中最常用的this
对象,所以context
中就提供了this
中最常用的三个属性:attrs
、slot
和emit
,分别对应 Vue2.x 中的$attr
属性、slot
插槽 和$emit
发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。语法糖 script setup
直接给script标签添加setup属性,不要像旧的语法那样在底部return一堆属性出去
组件import后直接在template使用,不需要注册
<template> <div> <span>{{ num }}</span> <panel></panel> </div> </template> <script setup> import { ref } from 'vue' import Panel from '@/components/Panel.vue' const num = ref(0) </srcipt>
script setup 相关文档
-
reactive
<template> <div class="about"> <div> <span>count 点击次数: </span> <span>{{ count }}</span> <button @click="addCount">点击增加</button> </div> </div> </template> <script> import { reactive, toRefs } from 'vue' export default { setup () { const state = reactive({ count: 0 }) const addCount = function () { state.count++ } return { // 这样展开后state property会失去响应式,因为是取值返回,不是引用 // ...state, ...toRefs(state), addCount, } }, } </script>
-
ref
ref
函数将一个普通对象转化为响应式包装对象,将一个ref
值暴露给渲染上下文,在渲染过程中,Vue 会直接使用其内部的值,在模板中可以把{{ num.value }}
直接写为{{ num }}
,但是在js中还是需要通过num.value
取值和赋值<template> <div class="about"> <div> <span>num 点击次数: </span> <span>{{ num }}</span> <button @click="addNum">点击增加</button> </div> </div> </template> <script> import { ref } from 'vue' export default { setup () { const num = ref(0) const addNum = function () { num.value++ } return { num, addNum } } } </script>
-
toRefs
toRefs
将reactive
对象转换为普通对象,其中结果对象上的每个属性都是指向原始对象中相应属性的ref
引用对象,这在组合函数返回响应式状态时非常有用,这样保证了开发者使用对象解构或拓展运算符不会丢失原有响应式对象的响应 -
readonly
对于不允许写的对象,不管是普通
object
对象、reactive
对象、ref
对象,都可以通过readonly
方法返回一个只读对象直接修改
readonly
对象,控制台会打印告警信息,不会报错const state = reactive({ count: 0 }) const readonlyState = readonly(state) // 监听只读属性,state.count修改后依然会触发readonlyState.count更新 watch(() => readonlyState.count, (newVal, oldVal) => { console.log('readonly state is changed!') setTimeout(() => { // 修改只读属性会打印告警信息,但是不会报错 readonlyState.count = 666 }, 1000) })
-
生命周期
setup 执行时机是在 beforeCreate 之前执行
相对应的生命周期,3版本的执行时机总比2版本的早
-
Computed
接受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。
const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2
或者,它也可以使用具有
get
和set
函数的对象来创建可写的 ref 对象。const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0
-
watch
watch(source, callback, [options])
参数说明:
source: 可以支持 string,Object,Function,Array; 用于指定要侦听的响应式变量
callback: 执行的回调函数
options:支持 deep、immediate 和 flush 选项。
侦听 reactive 定义的数据
const state = reactive({ nickname: "lilei", age: 20 });
// 修改age值时会触发 watch的回调
watch(
() => state.age,
(curAge, preAge) => {
console.log("新值:", curAge, "老值:", preAge);
}
);
侦听 ref 定义的数据
const year = ref(0);
watch(year, (newVal, oldVal) => {
console.log("新值:", newVal, "老值:", oldVal);
});
侦听多个数据
watch(
[() => state.age, year],
([curAge, newVal], [preAge, oldVal]) => {
console.log("新值:", curAge, "老值:", preAge);
console.log("新值:", newVal, "老值:", oldVal);
}
);
侦听复杂的嵌套对象
const state = reactive({
room: {
id: 100,
attrs: {
size: "140平方米",
type: "三室两厅",
},
},
});
watch(
() => state.room,
(newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
},
{ deep: true }
);
stop 停止监听
我们在组件中创建的watch
监听,会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()
函数的返回值,操作如下:
const stopWatchRoom = watch(() => state.room, (newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
}, {deep:true});
setTimeout(()=>{
// 停止监听
stopWatchRoom()
}, 3000)
2. Fragment
-
可以直接写多个节点,根节点不是必要的,无需创建了,减少了节点数。
-
Fragment节点是虚拟的,不会DOM树中呈现。
<template>
<div></div>
<div></div>
</template>
3. Teleport
例子:在子组件Header
中使用到Dialog
组件,此时Dialog
就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index
和样式都变得困难。 Dialog
从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data
或者props
)的值。
即希望继续在组件内部使用Dialog
, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。
若希望 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html
文件中定义一个供挂载的元素:
<body>
<div id="app"></div>
<div id="dialog"></div>
</body>
定义一个Dialog
组件Dialog.vue
, to
属性, 与上面的id
选择器一致:
<template>
<teleport to="#dialog">
<div class="dialog">
...
</div>
</teleport>
</template>
在一个子组件Header.vue
中使用Dialog
组件。header
组件
<div class="header">
...
<navbar />
<Dialog v-if="dialogVisible"></Dialog>
</div>
...
Dom 渲染效果如下:
使用 teleport
组件,通过 to
属性,指定该组件渲染的位置与 <div id="app"></div>
同级,也就是在 body
下,但是 Dialog
的状态 dialogVisible
又是完全由内部 Vue 组件控制.
4. Suspense
在正确渲染组件之前进行一些异步请求是很常见的事。组件通常会在本地处理这种逻辑,绝大多数情况下这是非常完美的做法。
该 <suspense>
组件提供了另一个方案,允许等待整个组件树处理完毕而不是单个组件。
<template>
<suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</suspense>
</template>
这个 <suspense>
组件有两个插槽。它们都只接收一个子节点。default
插槽里的节点会尽可能展示出来。如果不能,则展示 fallback
插槽里的节点。
重要的是,异步组件不需要作为 <suspense>
的最近子节点。它可以在组件树任意深度的位置且不需要出现在和 <suspense>
自身相同的模板中。只有所有的后代组件都准备就绪,该内容才会被认为解析完毕。
5. vue-router
vue2.x使用路由选项redirect设置路由自动调整,vue3.x中移除了这个选项,将在子路由中添加一个空路径路由来匹配跳转
// vue2.x router
[
{
path: '/',
component: Layout,
name: 'WebHome',
redirect: '/dashboard', // 这里写跳转
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('../views/dashboard/index.vue')
}
]
}
]
// vue3.x router
[
{
path: '/',
component: Layout,
name: 'WebHome',
children: [
{ path: '', redirect: 'dashboard' }, // 这里写跳转
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('../views/dashboard/index.vue')
}
]
}
]
import { useRouter, useRoute } from 'vue-router'
export default {
setup () {
const router = useRouter()
const route = useRoute()
return {
}
}
}
6. Vuex
import { useStore } from 'vuex'
export default {
setup () {
const store = userStore()
const userId = computed(() => store.state.app.userId)
const getUserInfo = () => store.dispatch('xxxx')
const setUserInfo = () => store.commit('xxx')
return {
userId
}
}
}
7. getCurrentInstance
获取当前的组件实例
import { getCurrentInstance } from 'vue'
export default {
setup () {
// const { ctx } = getCurrentInstance() ctx 只在开发环境生效,生产环境无效
const { proxy: ctx } = getCurrentInstance()
console.log('ccccc', ctx)
return {
}
}
}
了解更多可查看官方文档
使用总结
-
在2版本时候,当代码行数很多时,data,计算属性,watch都分布在不同区域,要来回切换,开发体验不好,新版本可以按业务逻辑放到一块
-
组合式Api开发起来更加灵活,逻辑复用较好
-
业务逻辑集中写在setup中,可能会导致代码臃肿较难维护
组件抽离、要统一开发规范
-
利用 ES6 模块系统import/export,按需编译,体积比Vue2.x更小 (Tree shaking)
import { computed, watch } from "vue";
问题:
watch监听reactive的数据为什么需要用函数返回?
1.官网,data源要是返回值的getter函数或ref
watch源码
export function watch<T = any>(
source: WatchSource<T> | WatchSource<T>[], /* getter方法 */
cb: WatchCallback<T>, /* hander回调函数 */
options?: WatchOptions /* watchOptions */
): StopHandle {
return doWatch(source, cb, options)
}
watch接受三个参数,分别是getter方法,回调函数,和options配置项。
doWatch核心方法
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): StopHandle {
/* 此时的 instance 是当前正在初始化操作的 instance */
const instance = currentInstance
let getter: () => any
if (isArray(source)) { /* 判断source 为数组 ,此时是watch情况 */
getter = () =>
source.map(
s =>
isRef(s)
? s.value
: callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
)
/* 判断ref情况 ,此时watch api情况*/
} else if (isRef(source)) {
getter = () => source.value
/* 正常watch情况,处理getter () => state.count */
} else if (cb) {
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
} else {
/* watchEffect 情况 */
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return callWithErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onInvalidate]
)
}
}
/* 处理深度监听逻辑 */
if (cb && deep) {
const baseGetter = getter
/* 将当前 */
getter = () => traverse(baseGetter())
}
let cleanup: () => void
/* 清除当前watchEffect */
const onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
cleanup = runner.options.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}
let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE
const applyCb = cb
? () => {
if (instance && instance.isUnmounted) {
return
}
const newValue = runner()
if (deep || hasChanged(newValue, oldValue)) {
if (cleanup) {
cleanup()
}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
onInvalidate
])
oldValue = newValue
}
}
: void 0
/* TODO: scheduler事件调度*/
let scheduler: (job: () => any) => void
if (flush === 'sync') { /* 同步执行 */
scheduler = invoke
} else if (flush === 'pre') { /* 在组件更新之前执行 */
scheduler = job => {
if (!instance || instance.isMounted) {
queueJob(job)
} else {
job()
}
}
} else { /* 正常情况 */
scheduler = job => queuePostRenderEffect(job, instance && instance.suspense)
}
const runner = effect(getter, {
lazy: true, /* 此时 lazy 为true ,当前watchEffect不会立即执行 */
computed: true,
onTrack,
onTrigger,
scheduler: applyCb ? () => scheduler(applyCb) : scheduler
})
recordInstanceBoundEffect(runner)
/* 执行watcherEffect函数 */
if (applyCb) {
if (immediate) {
applyCb()
} else {
oldValue = runner()
}
} else {
runner()
}
/* 返回函数 ,用终止当前的watchEffect */
return () => {
stop(runner)
if (instance) {
remove(instance.effects!, runner)
}
}
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!