具名插槽
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>
二、toRefs
toRefs
用于将一个 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>
{{ 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");