前言
Vue.js作为前端流行的框架,Vue3中带来了许多新的特性和改进,真正理解掌握Vue3并不是一个简单的事情,为了帮助开发者更好的理解和如何应用,特意写了这个文章,深入探究Vue3的APi如何应用。
第一层:鬼门入口
请验证 Button
组件的 Prop
类型 ,使它只接收: primary
| ghost
| dashed
| link
| text
| default
,且默认值为 default
。
<script setup>
defineProps({
type: {},
});
</script>
<template>
<button>Button</button>
</template>
解题思路
<script setup>
defineProps({
btnText: {
type: String,
required: true,
default: "default",
validator: (value) => {
if (typeof value !== "string") return false;
return ["primary", "ghost", "dashed", "link", "text", "default"].includes(
value
);
},
},
});
</script>
<template>
<button>{{ btnText }}</button>
</template>
第二层:幽灵长廊
请创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值首字母转为大写。
<script setup></script>
<template>
<input type="text" v-model.capitalize="" />
</template>
解题思路
<script setup>
// 导入 ref 和 vModelText 函数
import { ref, vModelText } from 'vue';
// 定义一个响应式变量 value
const value = ref('');
// 使用 vModelText.beforeUpdate 指令,在更新 value 之前对输入值进行操作
vModelText.beforeUpdate = (el, binding) => {
// 如果输入值不为空且包含 capitalize 修饰符,则将输入值的首字母转换为大写
if (el.value && binding.modifiers.capitalize) {
el.value = el.value.charAt(0).toUpperCase() + el.value.slice(1);
}
};
</script>
<template>
<!-- 创建一个文本输入框,使用 v-model.capitalize 指令将 value 变量的值绑定到输入框的值,并在更新 value 之前对输入值进行操作 -->
<input type="text" v-model.capitalize="value" />
</template>
第三层:尸骨迷宫
请创建一个可写的计算属性。
<script setup lang="ts">
import { ref, computed } from "vue";
const count = ref(1);
const plusOne = computed(() => count.value + 1);
/**
* 确保 `plusOne` 可以被写入。
* 最终我们得到的结果应该是 `plusOne` 等于 3 和 `count` 等于 2。
*/
plusOne.value++;
</script>
<template>
<div>
<p>{{ count }}</p>
<p>{{ plusOne }}</p>
</div>
</template>
解题思路
<script setup lang="ts">
import { ref, computed } from "vue";
const count = ref(1);
const plusOne = computed(() => count.value + 1);
/**
* 确保 `plusOne` 可以被写入。
* 最终我们得到的结果应该是 `plusOne` 等于 3 和 `count` 等于 2。
*/
plusOne.value++;
</script>
<template>
<div>
<p>{{ count }}</p>
<p>{{ plusOne }}</p>
</div>
</template>
第四层:邪灵祭坛
请使用 响应式 API: watch 来完成它。
<script setup lang="ts">
import { ref, watch } from "vue";
const count = ref(0);
/**
* 挑战 1: Watch 一次
* 确保副作用函数只执行一次
*/
watch(count, () => {
console.log("Only triggered once");
});
count.value = 1;
setTimeout(() => (count.value = 2));
/**
* 挑战 2: Watch 对象
* 确保副作用函数被正确触发
*/
const state = ref({
count: 0,
});
watch(state, () => {
console.log("The state.count updated");
});
state.value.count = 2;
/**
* 挑战 3: 副作用函数刷新时机
* 确保正确访问到更新后的`eleRef`值
*/
const eleRef = ref();
const age = ref(2);
watch(age, () => {
console.log(eleRef.value);
});
age.value = 18;
</script>
<template>
<div>
<p>
{{ count }}
</p>
<p ref="eleRef">
{{ age }}
</p>
</div>
</template>
解题思路
<template>
<div>
<p>
{{ count }}
</p>
<p ref="eleRef">
{{ age }}
</p>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
const count = ref(0);
/**
* 挑战 1: Watch 一次
* 确保副作用函数只执行一次
*/
const unWatch = watch(count, () => {
console.log("Only triggered once");
unWatch();
});
count.value = 1;
setTimeout(() => (count.value = 2));
/**
* 挑战 2: Watch 对象
* 确保副作用函数被正确触发
*/
const state = ref({
count: 0,
});
watch(
state,
() => {
console.log("The state.count updated");
},
{
deep: true,
}
);
state.value.count = 2;
/**
* 挑战 3: 副作用函数刷新时机
* 确保正确访问到更新后的`eleRef`值
*/
const eleRef = ref();
const age = ref(2);
watch(
age,
() => {
console.log(eleRef.value);
},
{
flush: "post",
}
);
age.value = 18;
</script>
第五层:幻境花园
请使用响应式 API: shallowRef 来完成它。
<script setup lang="ts">
import { shallowRef, watch } from "vue";
const state = shallowRef({ count: 1 });
// 回调没被触发
watch(
state,
() => {
console.log("State.count Updated");
},
{ deep: true }
);
/**
* 修改以下代码使watch回调被触发
*
*/
state.value.count = 2;
</script>
<template>
<div>
<p>
{{ state.count }}
</p>
</div>
</template>
解题思路
<script setup lang="ts">
import { shallowRef, watch } from "vue";
const state = shallowRef({ count: 1 });
// Does NOT trigger
watch(
state,
() => {
console.log("State.count Updated");
},
{ deep: true }
);
/**
* Modify the code so that we can make the watch callback trigger.
*/
state.value = { count: 2 };
</script>
<template>
<div>
<p>
{{ state.count }}
</p>
</div>
</template>
第六层:冰封王座
请实现一个切换状态的可组合函数。
<script setup lang="ts">
/**
* 实现一个切换状态的可组合函数
* 确保该功能正常工作
*/
function useToggle() {}
const [state, toggle] = useToggle(false);
</script>
<template>
<p>State: {{ state ? "ON" : "OFF" }}</p>
<p @click="toggle">Toggle state</p>
</template>
解题思路
<script setup lang="ts">
import { Ref, ref } from "vue";
function useToggle(i: boolean): [state: Ref, toggle: () => void] {
const state = ref(i);
const toggle = () => {
state.value = !state.value;
};
return [state, toggle];
}
const [state, toggle] = useToggle(false);
</script>
<template>
<p>State: {{ state ? "ON" : "OFF" }}</p>
<p @click="toggle">Toggle state</p>
</template>
第七层:龙脉之心
请实现一个防抖点击指令。
<script setup lang="ts">
/**
* 实现以下自定义指令
* 确保在一定时间内当快速点击按钮多次时只触发一次点击事件
* 你需要支持防抖延迟时间选项, 用法如 `v-debounce-click:ms`
*/
const VDebounceClick = {};
function onClick() {
console.log("Only triggered once when clicked many times quicky");
}
</script>
<template>
<button v-debounce-click:200="onClick">Click on it many times quickly</button>
</template>
解题思路
<script setup lang="ts">
function debounce(fn: Function, delay: number) {
let timer = null;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(fn, delay);
};
}
let debounced;
const VDebounceClick = {
created(el, binding) {
const { value: cb, arg: delay } = binding;
debounced = debounce(cb, delay);
el.addEventListener("click", debounced);
},
unmounted(el) {
if (el._debounced) {
el.removeEventListener("click", debounced);
}
},
};
function onClick() {
console.log("Only triggered once when clicked many times quickly");
}
</script>
<template>
<button v-debounce-click:200="onClick">Click on it many times quickly</button>
</template>
第八层:神秘宝库
请使用响应式 API: effectScope 来完成它。
<script setup lang="ts">
import { ref, computed, watch, watchEffect } from "vue";
const counter = ref(1);
const doubled = computed(() => counter.value * 2);
// 使用 `effectScope` API 使这些Effect效果在触发一次后停止
watch(doubled, () => console.log(doubled.value));
watchEffect(() => console.log("Count: ", doubled.value));
counter.value = 2;
setTimeout(() => {
counter.value = 4;
});
</script>
<template>
<div>
<p>
{{ doubled }}
</p>
</div>
</template>
解题思路
<script setup lang="ts">
import { ref, computed, watch, watchEffect, effectScope } from "vue";
const counter = ref(1);
const doubled = computed(() => counter.value * 2);
const scope = effectScope();
scope.run(() => {
const stop1 = watch(doubled, () => console.log(doubled.value));
const stop2 = watchEffect(() => console.log(`Count: ${doubled.value}`));
stop1();
stop2();
});
counter.value = 2;
setTimeout(() => {
counter.value = 4;
});
</script>
<template>
<div>
<p>
{{ doubled }}
</p>
</div>
</template>
第九层:妖塔之巅
请实现一个树组件。
<script setup lang="ts">
interface TreeData {
key: string;
title: string;
children: TreeData[];
}
defineProps<{ data: TreeData[] }>();
</script>
<template>
<!-- do something.... -->
</template>
解题思路
<script setup lang="ts">
import { h } from "vue";
interface TreeData {
key: string;
title: string;
children: TreeData[];
}
const props = defineProps<{ data: TreeData[] }>();
const render = () => {
function makeTree(data?: TreeData, depth: number) {
if (!data) return;
const nodes = [];
for (let i = 0; i < data.length; i++) {
const node = h("ul", [
h("li", { key: data[i].key }, `${data[i].title} - depth (${depth})`),
makeTree(data[i].children, depth + 1),
]);
nodes.push(node);
}
return nodes;
}
return makeTree(props.data, 0);
};
</script>
<template>
<render />
</template>
结尾
子曰:“三人行,必有我师焉。择其善者而从之,其不善者而改之。”——《述而》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。