@update 2022/4/22
@author helson
生命周期
vue2.0和vue3.0之间生命周期对比
vue2 | vue3 |
---|---|
beforeCreate | setup |
created | setup |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | beforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
新特性
支持多根节点
子组件
// Card.vue
<template>
<div class="header">{{header}}</div>
<div class="footer">{{footer}}</div>
</template>
<script setup lang="ts">
const props = defineProps({
header: {
type: String,
required: true,
},
footer: {
type: String,
required: true,
},
})
</script>
父组件
<template>
<div class="blink">
<Card :header="header" :footer="footer" />
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
const header = ref('headerInfo');
const footer = ref('footerinfo');
</script>
支持多根节点可以让我们写一些组件的时候,不用在最外层多包括一层
div
,Dom结构更加直观。
setUp
函数
setUp
在创建组件实例时,在初始 props 解析之后立即调用
setup
。在生命周期方面,它是在
beforeCreate
钩子之前调用的。
setup
函数接受两个参数,第一个参数
props
第二个参数
context
props
props
是响应式的,在传入新的 props 时会对其进行更新,并且可以通过使用
watchEffect
或
watch
进行观测和响应,不要对
props
进行解构赋值,解构之后将不具有响应性。
export default {
props: {
name: String
},
setup(props) {
watchEffect(() => {
console.log(`name is: ` + props.name)
})
}
}
context:执行上下文
常用的
emit
slots
attrs
在context可以获取到。
export default defineComponent({
name: "CardList",
components: { Blank },
props: {
list: {
type: Array,
default: () => {
return [];
},
},
},
setup(props, context) {
// 实例化 router
const router = useRouter();
const toView = (item) => {
router.push({ name: "Article", query: { id: item.id } });
};
const like = (index) => {
context.emit("like", index);
};
return {
toView,
like,
};
},
});
setup
语法糖(
setup
在
<script>
内添加
setup
之后,可以不需要再写复杂的结构了,精简代码。
组件
组件的使用不需要引入组件之后,再在
components
内注册。直接引入就可以使用。
变量
变量和函数的声明之后可以直接使用,不需要再进行
return
<script setup lang="ts">
import {ref} from 'vue';
// 导入之后,就可以直接使用
import Header from '@/components/Header.vue';
</script>
Props和Emit
props
和
emit
需要使用
defineProps defineEmit
来定义
<script setup>
import { defineProps, defineEmit, useContext } from 'vue'
const props = defineProps({
foo: String,
})
const emit = defineEmit(['change', 'delete'])
</script>
props增强类型可以使用ts来定义或者直接采用2.0一样方法使用对象方式
2.0方式:
<script setup>
import { defineProps} from 'vue'
const props = defineProps({
foo: {
type: String,
require: true,
},
name: {
type: String,
}
})
</script>
ts方式:
<script setup>
import { defineProps} from 'vue'
interface Props {
foo: string;
name?: string;
}
const props = defineProps<Props>();
</script>
以上两种方式是等价的.
如果想要设置默认参数值使用
withDefaults
import {withDefaults} from "vue"
withDefaults(defineProps<Props>(), {
foo: "demo",
name: "helson"
});
Ref获取组件实例
使用setup语法糖,默认是不会保留属性和参数的。想要暴露组件的一些属性的方法时,需要使用
defineExpose
来定义暴露参数
TS中想要指定
ref
参数的类型可以使用泛型exp:
const myDom = ref<HTMLElement>(null)
// 父组件
<template>
<div>
<header ref="headerRef"></header>
<div class="myDom"></div>
</div>
</template>
<script setup>
import {ref} from 'vue'
const headerRef= ref(null);
// headerRef.value.clear();![请添加图片描述](https://img-blog.csdnimg.cn/ac68c468f5174a3487f21a84bb01795f.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASGVsc29uQA==,size_20,color_FFFFFF,t_70,g_se,x_16)
// const headerRef= ref<HTMLElement>(null)
</script>
// 子组件
<script setup lang="ts">
const clear = () => {};
const name = ref("demo")
defineExpose({name,clear})
</script>
await语法支持
在
script
可以直接
await
语法
<script setup>
const post = await fetch('https://api.fastmock.site/dhskajs12/card/list').then((r) => r.json())
</script>
对象监听
watch
:
watch是惰性执行,也就是只有监听的值发生变化的时候才会执行
watch只能监听响应式数据:ref定义的属性和reactive定义的对象,如果直接监听reactive定义对象中的属性是不允许的,除非使用函数转换一下
watchEffect
:监听
副作用
(当某个属性值改变时做一些操作)
对监听函数内的响应式属性都会追踪依赖,当响应式的属性发生改变时,会重新执行(拿到的值是更新后的值)。默认会执行一次。
如果监听reactive定义的对象是不起作用的,只能监听对象中的属性
场景案例:网站主题色
themeVal
有多个参数值,当主题色
themeVal
修改时,需要修改主题色。
const themeVal = ref("light");
watchEffect(() => {
theme.setTheme(themeVal.value);
document.body.setAttribute(
"arco-theme",
themeVal.value === "dark" ? "dark" : ""
);
});
计算属性
计算属性具有缓存机制,所以在对数据量较大的属性进行处理时具有优势,相对于method可以节省开销。方法在重渲染时会重新计算。
注意:
1.计算数据不应有副作用,在计算属性内,不建议去对Dom进行操作;
2.避免直接修改计算属性的值:从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。因此更改快照是没有意义的,因此**,**
计算属性的返回值应该被视为只读的
,并且永远不会发生突变。应该更新它所依赖的源状态,以触发新一次计算。
跨组件传值provide和inject
provide
绑定的值如果是响应式的数据,
inject
注入的数据也是具有响应式的。
inject
的第二个参数为默认值参数,如果
provide
没有注入对应的参数值,则采用默认值。
在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在不使用可选值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值:
const value = inject('key', () => new ExpensiveClass())
// 根组件
<script setup lang="ts">
import { provide, ref } from "vue";
import Header from "@/components/Header.vue";
const message = ref("message");
setTimeout(() => {
message.value += "1";
}, 1000);
provide("message", message);
</script>
<script setup lang="ts">
// 子组件:message 为响应式的数据
const message = inject("message", "默认值");
</script>
可以观察到上面案例内,子组件内的
message
的值在1秒之后改变为了
message1
异步组件 |
defineAsyncComponent
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
关于组件加载和错误状态
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载完成的组件前的延迟,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超过了该时间
// 也会显示报错组件
timeout: 3000
})
自定义指令
在setup内以v开头的驼峰命名的自定义指令函数,会自动注入。
const vFocus = {
mounted: (el) => el.focus(),
};
$attr: 支持class 和style继承
vue3自动会继承父级的class 和 style
多个根节点时需要指定接受的节点
<template>
<div v-bind="$attrs"></div>
<div></div>
</template>
extends弃用,使用createApp创建组件
createApp
接受两个参数:第一个参数
vue
组件,第二个参数
props
export function decoratorsRender(
id: string,
name: string,
option: { [key: string]: string }
) {
//异步加载组件
const componentWillMount = defineAsyncComponent(
() => import(`./${name}.vue`)
);
const fvComponent = createApp(componentWillMount, option);
const mountDom = document.getElementById(id);
if (!mountDom) throw new Error("will mount id dom can't be find");
fvComponent.mount(mountDom);
}
组件注册(批量)
vue3 使用
Vue.compoent(componentName, coponent)
注册组件
批量注册组件(组件库)
/components/index.ts
import { App } from "vue";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require("path");
export default {
install(Vue: App<Element>) {
const files = require.context("./", true, /.vue$/);
files.keys().forEach(async (key: string) => {
const name = path.dirname(key).slice(2); //返回文件名 不含后缀名
const componet = files(key).default;
Vue.component(name, componet);
});
},
};
在
main.ts
内注册使用
import FvDecorator from "@/components/index";
const app = createApp(App);
app.use(Antd).use(FvDecorator).use(Vue3ColorPicker).use(router).use(store);
Hooks
hooks
提高了代码的复用性,代码逻辑清晰。
hooks
可以在组件外部调用Vue的所有能力,包括onMounted,ref, reactive等等。
useMousePosition.ts
// hooks
import {onBeforeUnmount, onMounted, ref} from 'vue
export default function () {
const x = ref(-1) ; // x 绑定为响应式数据
const y = ref(-1);
const clickHandler=(event:MouseEvent)=>{
x.value = event.pageX
y.value = event.pageY
}
onMounted(()=>{
window.addEventListener('click', clickHandlker)
})
onBeforeUnmount(()=>{
window.removeEventListner('click', clickHandler)
})
return {
x,
y
}
}
<template>
<div>
<h2>x: {{x}}, y: {{y}}</h2>
</div>
</template>
<script>
import {
ref
} from "vue"
/*
在组件中引入并使用自定义hook
自定义hook的作用类似于vue2中的mixin技术
自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
*/
import useMousePosition from './hooks/useMousePosition'
export default {
setup() {
const {x, y} = useMousePosition() // 这里就用了 hooks 函数, 从而提高了复用性
return {
x,
y,
}
}
}
</script>
Teleport组件
teleport
是
vue3
新增的组件。他可以让我们将组件插入到指定的
Dom
结构下。
使用案例:
在项目中,有一个场景:在页面滚动区域过长的情况下,需要能够一键回到顶部的这样一样操作按钮。
在Vue3中,就可以利用
Teleport
来实现这个功能。只需要操作按钮组件编写完成,然后将
Teleport
的
to
属性绑定到置顶的Dom上即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zer2b1M3-1650615049721)(https://pic.kblue.site/20210625134437.png)]
toUper.vue
<template>
<!-- #app 代表这个 Dom将要挂载到id为App的dom下方 -->
<teleport to="#app">
<div class="toUpper animated flash" v-if="show">
<div class="ic" @click="toHome()">
<i class="iconfont icon-shouye1"></i>
</div>
<div class="ic" @click="toUp()">
<i class="iconfont icon-up"></i>
</div>
</div>
</teleport>
</template>
<script>
import { defineComponent } from "vue";
import { useRouter } from 'vue-router';
import useScrollToTop from "@/hooks/useScrollToTop";
export default defineComponent({
name: "ToUpper",
props: {
show: {
type: Boolean,
default: false,
},
},
setup() {
const router = useRouter();
const toUp = () => {
useScrollToTop();
};
const toHome = () => {
router.push({path: '/'});
}
return {
toUp,
toHome,
};
},
});
</script>
<style lang="scss" scoped>
.toUpper {
position: sticky;
top: 90%;
left: 90%;
}
.ic {
float: right;
display: block;
margin-right: 40px;
width: 40px;
height: 40px;
border-radius: 50%;
background: #528bff;
display: flex;
align-items: center;
justify-content: center;
&:hover {
cursor: pointer;
}
.iconfont {
color: #fff;
}
}
</style>
Home.vue
<template>
<div class="home" if="home">
<Header @changStatus="changStatus" :status="status" />
<div class="main">
<router-view></router-view>
<div id="upper"></div>
</div>
<!-- 当页面滚动区域操作20之后才显示 -->
<to-upper :show="scrollTop > 20" />
</div>**
**</template>
状态管理
利用响应式API来实现状态管理
如果你有一部分状态需要在多个组件实例间共享,你可以使用
reactive()
来创建一个响应式对象,并在不同组件中导入它:
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0
})
<!-- ComponentA.vue -->
<script setup>
import { store } from './store.js'
</script>
<template>来自 A:{{ store.count }}</template>
<!-- ComponentB.vue -->
<script setup>
import { store } from './store.js'
</script>
<template>来自 B:{{ store.count }}</template>
现在每当
store
对象被更改时,
<ComponentA>
与
<ComponentB>
都会自动更新它们的视图。现在我们有了单一的数据源。
然而,这也意味着任意一个导入了
store
的组件都可以随意修改它的状态。