Vue3 笔记
Vue3 笔记
setup 函数
setup
函数是 vue3
的核心 也是所有函数的入口 这个函数传入两个参数,分别为 props
和 context
props
为 父组件传递的参数,而 context
为 attrs
, emit
, slots
props
是响应式的,但是不可以 使用 解构或者展开,这样会 导致响应式 失败(原因会在第二点讲) context
可以使用解构,slots
相当于以前的 $slots
emit
相当于以前的 $emit
,attrs
则是在组件标签上的内容 props
和 attrs
的区别: 如下面代码 所示,name
属性 在 props
参数对象中定义了的,就会进入 props
里面, 否则 其他在组件标签上的内容会进入 attrs
里面
<template>
<div class="hello">
<span>{{ msg }}</span>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup(props, { attrs, emit, slots }) {
console.log(props, attrs, emit, slots)
}
}
</script>
reactive、ref 与 toRefs
ref
和 reactive
是构建响应式对象的 函数,但是 也有着显著的区别 在使用 ref
定义的值之后,必须使用 xxx.value
才能获得对应的值,而 reactive
则不需要 reactive
响应化的数据不能使用解构或者展开,要不然会失去响应 那么为什么 props
和 reactive
不能解构或者展开,而 ref
之后的数据又必须使用 .value
访问 呢? 无论是 Object.defineProperty
还是 proxy
,只能对 对象数据保持 响应式 如果是一个 基本属性的话,那改变了就是改变了,vue 内部是不能监听到他的变化的 所以 在 ref
中,一个 基本类型 变成了对象,而且使用 value
来获取 当 ref
作为 reactive
对象的 property
被访问或修改时,也将自动解套 value
值,其行为类似普通属性 当 reactive
props
结构的结果为基本类型 ,那么同样也是失去了监听的效果 但是当 reactive
内部的值是一个对象的话,那么解构或者展开依旧保持响应,这是内部处理了深度响应结果 一、reactive、ref
<template>
<div class="hello">
<button @click="inc">点击</button>
<p>{{ state.username }}</p>
<p>{{ state.password }}</p>
<p>{{ count }}</p>
</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
name: '',
setup() {
const count = ref(0)
const inc = () => {
count.value++
}
const state = reactive({
username: 'jack',
password: 'rose'
})
return {
count,
inc,
state
}
}
}
</script>
二、toRefstoRefs
用于将一个 reactive
对象转化为属性全部为 ref
对象的普通对象
<template>
<div class="homePage">
<p>第 {{ year }} 年</p>
<p>姓名: {{ nickname }}</p>
<p>年龄: {{ age }}</p>
</div>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from 'vue'
export default defineComponent({
setup() {
const year = ref(0)
const user = reactive({ nickname: 'xiaofan', age: 26, gender: '女' })
setInterval(() => {
year.value++
user.age++
}, 1000)
return {
year,
// 使用reRefs
...toRefs(user)
}
}
})
</script>
生命周期钩子
TODO: 作用待补充
vue2 | 作用 | vue3 | 作用 |
---|---|---|---|
beforeCreate | ---- | setup | ---- |
created | ---- | setup | ---- |
beforeMount | ---- | onBeforeMount | ---- |
mounted | ---- | onMounted | ---- |
beforeUpdate | ---- | onBeforeUpdate | ---- |
updated | ---- | onUpdated | ---- |
beforeDestroy | ---- | onBeforeUnmount | ---- |
destroyed | ---- | onUnmounted | ---- |
activated | ---- | onActivated | ---- |
deactivated | ---- | onDeactivated | ---- |
errorCaptured | ---- | onErrorCaptured | ---- |
---- | onRenderTriggered | ---- | |
---- | onRenderTracked | ---- |
watch 与 watchEffect
一、computed、readonly、watch、watchEffect a. computed
返回的值 就和 ref
一样,都是 需要使用 .value
获取,理由同上 b. watch
可以监听一个值,也可以同时监听多个值 c. readonly
返回一个只读代理,即使是对象里面的对象,也是 readonly
的
<script>
import {
ref,
computed,
reactive,
readonly,
toRefs,
watch,
watchEffect
} from 'vue'
export default {
name: '',
setup() {
// 建立一个响应式对象
const count = ref(0)
const count2 = ref(0)
const double = computed(() => count.value * 2)
const state = reactive({
username: {
fistname: 'smit'
},
password: 'rose'
})
const copy = readonly(state) // 即使是 username.firstname 也是只读的
// watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。
watch(count, (value) => {
// 监听 ref
console.log(value, 'obj.double')
})
watch(
() => state.password,
(value) => {
// 监听 state
console.log(value)
}
)
// 监听 多个数据源
watch([count, count2], ([countNow, count2Now], [countPrev, count2Prev]) => {
console.log([countNow, count2Now], [countPrev, count2Prev])
})
return {
double,
count,
...toRefs(state) //将响应式的对象变为普通对象
}
}
}
// 如果想要watch 函数立即执行的话,就可以使用 watchEffect 了
watchEffect(() => {
console.log('watch', 'count.value')
})
//watchEffect 会在第一时间执行,在执行的同时会收集内部的依赖,和computed类似,所以不需要指定依赖
//正如名称所示,可以执行一些有副作用的函数,比如 ajax 请求
//例如 使用一个 响应式的参数,参数为 page.page, page.pagesize ,就可以在这里调用
//这个一般还可以和 onMounted 生命周期组合
</script>
二、侦听复杂嵌套对象 侦听复杂嵌套对象必须传递第三个参数 deep:true
<script>
import { watch } from 'vue'
export default {
name: '',
setup() {
const state = reactive({
room: {
id: 100,
attrs: {
size: '140平方米',
type: '三室两厅'
}
}
})
watch(
() => state.room,
(newType, oldType) => {
console.log('新值:', newType, '老值:', oldType)
},
{ deep: true }
)
}
}
</script>
三、stop 停止监听 在组件销毁之前我们想要停止掉某个监听可以调用 watch()
函数的返回值
<script>
import { watch } from 'vue'
export default {
name: '',
setup() {
const state = reactive({
room: {
id: 100,
attrs: {
size: '140平方米',
type: '三室两厅'
}
}
})
const stopWatchRoom = watch(
() => state.room,
(newType, oldType) => {
console.log('新值:', newType, '老值:', oldType)
},
{ deep: true }
)
setTimeout(() => {
// 停止监听
stopWatchRoom()
}, 3000)
}
}
</script>
父子传值
一、 props 父组件向子组件传参(最常用)二、provide 父组件向子组件传参 父组件 provide 传递数据 vue
<template>
<div>
<provideComponent />
</div>
</template>
<script>
// provide 父组件向子组件传参
import { provide } from "vue";
import provideComponent from "../components/provide.vue";
export default {
name: "",
components: {
provideComponent,
},
setup() {
// provide('数据名称', 要传递的数据)
provide("success", "我是父组件向子组件传递的值");
},
};
</script>
子组件
<template>
<div>
{{ childMessage }}
</div>
</template>
<script>
import { inject } from 'vue'
// provide 父组件向子组件传参 子组件 引入 inject 接收父组件传递的值
export default {
name: '',
setup() {
// 调用 inject 函数,通过指定的数据名称(和父级provide数据名一致),获取到父级传的值,一定要return出去
const childMessage = inject('success')
return {
childMessage
}
}
}
</script>
通过 ref 获取 dom 元素
console.dir()可以显示一个对象的所有属性和方法 一、ref 获取单个 dom 元素
<template>
<div ref="myref">获取单个DOM</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
name: '',
setup() {
const myref = ref(null)
onMounted(() => {
console.dir(myref.value)
})
return {
myref
}
}
}
</script>
二、ref 获取多个 dom 元素
<template>
<ul>
<li v-for="(item, index) in arr" :key="index" :ref="setRef">
{{ item }}
</li>
</ul>
</template>
<script>
import { ref, nextTick } from 'vue'
export default {
name: '',
setup() {
const arr = ref([1, 2, 3])
// 存储 dom 数组
const fefArr = ref([])
const setRef = (el) => {
fefArr.value.push(el)
}
nextTick(() => {
console.dir(fefArr.value)
})
return {
arr,
setRef
}
}
}
</script>
自定义 Hooks
封装成一个 hook, 我们约定这些「自定义 Hook」以 use 作为前缀,和普通的函数加以区分。 useCount.ts 实现
import { ref, Ref, computed } from 'vue'
type CountResultProps = {
count: Ref<number>
multiple: Ref<number>
increase: (delta?: number) => void
decrease: (delta?: number) => void
}
export default function useCount(initValue = 1): CountResultProps {
const count = ref(initValue)
const increase = (delta?: number): void => {
if (typeof delta !== 'undefined') {
count.value += delta
} else {
count.value += 1
}
}
const multiple = computed(() => count.value * 2)
const decrease = (delta?: number): void => {
if (typeof delta !== 'undefined') {
count.value -= delta
} else {
count.value -= 1
}
}
return {
count,
multiple,
increase,
decrease
}
}
组件中使用 useCount 这个 hook:
<template>
<p>count: {{ count }}</p>
<p>倍数: {{ multiple }}</p>
<div>
<button @click="increase()">加1</button>
<button @click="decrease()">减一</button>
</div>
</template>
<script lang="ts">
import useCount from "../hooks/useCount";
setup() {
const { count, multiple, increase, decrease } = useCount(10);
return {
count,
multiple,
increase,
decrease,
};
},
</script>
移除过滤器 filters 使用 computed 替代
<template>
<h1>Bank Account Balance</h1>
<p>{{ accountInUSD }}</p>
</template>
<script>
export default {
props: {
accountBalance: {
type: Number,
required: true
}
},
computed: {
accountInUSD() {
return '$' + this.accountBalance
}
}
}
</script>
实例之 button 组件
components 文件夹下 ZButton.vue 文件
<template>
<button
class="z-button"
:class="classes"
:type="nativeType"
:disabled="buttonDisabled || loading"
@click="handleClick"
>
<i class="z-icon-loading" v-if="loading"></i>
<i :class="icon" v-else-if="icon"></i>
<slot></slot>
</button>
</template>
<script>
import { computed, inject, toRefs, getCurrentInstance } from 'vue'
export default {
name: 'ZButton',
props: {
size: {
type: String,
validator(val) {
if (val === '') return true
return ['medium', 'small', 'mini'].indexOf(val) !== -1
}
},
type: {
type: String,
validator(val) {
if (val === '') return true
return (
['primary', 'success', 'warning', 'danger', 'info', 'text'].indexOf(
val
) !== -1
)
}
},
nativeType: {
type: String,
default: 'button'
},
plain: Boolean,
round: Boolean,
circle: Boolean,
loading: Boolean,
disabled: Boolean,
icon: String
},
setup(props, context) {
// context
/*
context是setup的第二个参数,context是一个对象,里边包含了三个属性。分别是
attrs:attrs与vue2.0的this.$attrs是一样的,即外部传入的未在props中定义的属性。对于attrs与props一样,我们不能对attrs使用es6解构,必须使用attrs.name的写法。
slots:slots对应的是组建的插槽,与Vue2.0的this.$slots是对应的,与props和attrs一样,slots也是不能解构的。
emit:emit对应的是Vue2.0的this.$emit,即对外暴露事件
*/
const { size, disabled } = toRefs(props)
// 按钮大小
const buttonSize = useButtonSize(size)
// 按钮禁用
const buttonDisabled = useButtonDisabled(disabled)
// 按钮样式
const classes = useClasses({
props,
size: buttonSize,
disabled: buttonDisabled
})
// 绑定事件
const handleClick = (e) => {
context.emit('click', e)
}
return {
classes,
buttonDisabled,
handleClick
}
}
}
/* 按钮样式 */
const useClasses = ({ props, size, disabled }) => {
return computed(() => {
return [
size.value ? `z-button--${size.value}` : '',
props.type ? `z-button--${props.type}` : '',
{
'is-plain': props.plain,
'is-round': props.round,
'is-circle': props.circle,
'is-loading': props.loading,
'is-disabled': disabled.value
}
]
})
}
/* 按钮是否禁用 */
const useButtonDisabled = (disabled) => {
return computed(() => {
const elForm = inject('elForm', {})
return disabled?.value || elForm.disabled
})
}
/* 按钮大小 */
const useButtonSize = (size) => {
return computed(() => {
// inject 注入
const elFormItem = inject('elFormItem', {})
return (
size?.value ||
elFormItem.elFormItemSize ||
getCurrentInstance().ctx.$ELEMENT?.size
)
})
}
</script>
<style lang="scss" scoped>
$name: 'z-button';
.#{$name} {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 0;
transition: 0.1s;
font: {
weight: 500;
size: 14px;
}
// 禁止元素的文字被选中
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
padding: 12px 20px;
border-radius: 4px;
&:hover,
&:focus {
color: #409eff;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
}
// type样式 start
.d-button--primary {
color: #fff;
border-color: #409eff;
background-color: #409eff;
&:hover,
&:focus {
background: #66b1ff;
border-color: #66b1ff;
color: #fff;
}
}
.#{$name}--success {
color: #fff;
border-color: #67c23a;
background-color: #67c23a;
&:hover,
&:focus {
background: #85ce61;
border-color: #85ce61;
color: #fff;
}
}
.#{$name}--info {
color: #c0bbc5;
border-color: #f2fced;
background-color: #f2fced;
&:hover,
&:focus {
background: #e0ebdb;
border-color: #e0ebdb;
color: #fff;
}
}
.#{$name}--warning {
color: #fff;
border-color: #e6a23c;
background-color: #e6a23c;
&:hover,
&:focus {
background: #ebb563;
border-color: #ebb563;
color: #fff;
}
}
.#{$name}--danger {
color: #fff;
border-color: #f56c6c;
background-color: #f56c6c;
&:hover,
&:focus {
background: #fc6666;
border-color: #fc6666;
color: #fff;
}
}
// type样式 end
// 朴素按钮样式 start
.#{$name}.is-plain {
&:hover,
&:focus {
background: #fff;
border-color: #489eff;
color: #409eff;
}
}
.#{$name}--primary.is-plain {
color: #409eff;
background: #ecf5ff;
&:hover,
&:focus {
background: #409eff;
border-color: #409eff;
color: #fff;
}
}
.#{$name}--success.is-plain {
color: #67c23a;
background: #c2e7b0;
&:hover,
&:focus {
background: #67c23a;
border-color: #67c23a;
color: #fff;
}
}
.#{$name}--info.is-plain {
color: #909399;
background: #d3d4d6;
&:hover,
&:focus {
background: #909399;
border-color: #909399;
color: #fff;
}
}
.#{$name}--warning.is-plain {
color: #e6a23c;
background: #f5dab1;
&:hover,
&:focus {
background: #e6a23c;
border-color: #e6a23c;
color: #fff;
}
}
.#{$name}--danger.is-plain {
color: #f56c6c;
background: #fbc4c4;
&:hover,
&:focus {
background: #f56c6c;
border-color: #f56c6c;
color: #fff;
}
}
// 朴素按钮样式 end
// round属性
.#{$name}.is-round {
border-radius: 20px;
padding: 12px 23px;
}
// circle属性
.#{$name}.is-circle {
border-radius: 50%;
padding: 12px;
}
// icon配套样式
// 找到css中类名包含d-icon-且后面有个span的元素
.#{$name} [class*='d-icon-'] + span {
margin-left: 5px;
}
// disabled属性
.#{$name}.is-disabled {
cursor: no-drop;
}
</style>
plugins 插件文件下 ZComponents.js 文件
import ZButton from '../components/ZComponents/ZButton'
export default {
install(Vue) {
Vue.component(ZButton.name, ZButton)
}
}
main.js 中使用
// 自定义UI组件
import ZComponents from './plugins/ZComponents'
createApp(App).use(ZComponents).mount('#app')