1. 困难

1.1 树组件

在这个挑战中,你需要实现一个树组件,让我们开始吧。

<script setup lang="ts">
interface TreeData {
key: string;
title: string;
children: TreeData[];
}
defineProps<{ data: TreeData[] }>();
</script>

<template>
<ul>
<li :key="item.key" v-for="item in data">
<span>{{ item.title }}</span>
<TreeComponent
:data="item.children"
v-if="item.children && item.children.length"
/>
</li>
</ul>
</template>

1.2 自定义元素

你听说过 Web Components 吗 ?Vue 能很好地解析和创建 Web Components 。在这个挑战中,我们将尝试了解它,让我们开始吧 👇:

<script setup lang="ts">
import { onMounted, defineCustomElement } from "vue";
/**
* Implement the code to create a custom element.
* Make the output of page show "Hello Vue.js".
*/
const VueJs = defineCustomElement({
props: {
message: {
type: String,
default: "",
},
},
template: "<span>{{ message }}</span>",
});
customElements.define("vue-js", VueJs);
onMounted(() => {
document.getElementById("app")!.innerHTML =
'<vue-js message="Hello Vue.js"></vue-js>';
});
</script>

<template>
<div id="app"></div>
</template>

1.3 自定义 ref

防抖函数在输入框操作场景中非常有用。一个防抖的 ref 在 Vue.js 更加灵活,让我们开始吧 👇:

<script setup>
import { customRef, watch } from "vue";

/**
* Implement the function
*/
// 定义一个自定义的 ref,实现防抖逻辑
function useDebouncedRef(value, delay = 200) {
let timeoutId; // 声明一个变量来保存定时器的 ID

// 使用 customRef 创建自定义 ref
return customRef((track, trigger) => {
return {
// 定义 getter
get() {
track(); // 追踪此 ref 的依赖
return value; // 返回当前值
},
// 定义 setter
set(newValue) {
clearTimeout(timeoutId); // 清除之前的定时器

// 设置新的定时器,延迟更新值并触发依赖
timeoutId = setTimeout(() => {
value = newValue; // 更新值
trigger(); // 触发依赖
}, delay);
},
};
});
}
const text = useDebouncedRef("hello");

/**
* Make sure the callback only gets triggered once when entered multiple times in a certain timeout
*/
watch(text, (value) => {
console.log(value);
});
</script>

<template>
<input v-model="text" />
</template>

1.4 激活的样式-指令

在这个挑战中,我们将实现一个"激活的样式"指令,让我们开始吧 👇:

<script setup lang="ts">
import { ref, watchEffect } from "vue";

/**
* Implement the custom directive
* Make sure the list item text color changes to red when the `toggleTab` is toggled
*
*/
const VActiveStyle = {
mounted(el, binding) {
const [activeStyle, isActive] = binding.value;
watchEffect(() => {
for (const key in activeStyle) {
el.style[key] = isActive() ? activeStyle[key] : "";
}
});
},
};

const list = [1, 2, 3, 4, 5, 6, 7, 8];
const activeTab = ref(0);
function toggleTab(index: number) {
activeTab.value = index;
}
</script>

<template>
<ul>
<li
v-for="(item, index) in list"
:key="index"
v-active-style="[{ color: 'red' }, () => activeTab === index]"
@click="toggleTab(index)"
>
{{ item }}
</li>
</ul>
</template>

1.5 实现简易版v-model指令

在这个挑战中,我们将尝试实现一个简单的 v-model 指令,让我们开始吧 👇:

<script setup lang="ts">
import { ref } from "vue";

/**
* Implement a custom directive
* Create a two-way binding on a form input element
*
*/
// 自定义指令:实现表单输入元素和数据的双向绑定
const VOhModel = {
// 指令的钩子函数
mounted(el: HTMLInputElement, binding: Ref<string>) {
el.value = binding.value; // 设置初始值

el.addEventListener("input", (e) => {
value.value = e.target.value; // 监听输入事件,更新数据
});
},
};

const value = ref("Hello Vue.js");
</script>

<template>
<input v-oh-model="value" type="text" />
<p>{{ value }}</p>
</template>