1. 中等

1.1 优化性能的指令

Vue.js 提供了一个指令,以便只渲染一次元素和组件,并且跳过以后的更新。你知道它是什么吗 ? 让我们试试 👇:

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

const count = ref(0);

setInterval(() => {
count.value++;
}, 1000);
</script>

<template>
<span v-once> Make it never change: {{ count }}</span>
</template>

1.2 切换器

这个挑战开始,我们将尝试编写可组合函数,让我们从 useToggle 开始 👇:

<script setup lang="ts">
import { ref } from "vue";
/**
* Implement a composable function that toggles the state
* Make the function work correctly
*/
function useToggle(value) {
const state = ref(value);
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>

1.3 until

有些时候,我们需要依赖于异步的返回结果做一些后续处理,until 函数在这种场景下非常有用,你能实现它吗 ? 让我们来试试吧 👇:

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

const count = ref(0);

/**
* Implement the until function
*/

function until(initial) {
function toBe(value) {
return new Promise((resolve) => {
initial.value = value;
resolve(initial.value);
});
}

return {
toBe,
};
}

async function increase() {
count.value = 0;
setInterval(() => {
count.value++;
}, 1000);
await until(count).toBe(3);
console.log(count.value === 3); // Make sure the output is true
}
</script>

<template>
<p @click="increase">Increase</p>
</template>

1.4 计数器

在这个挑战中,我们将实现一个计数器。 👇:

<script setup lang="ts">
import { ref } from "vue";
interface UseCounterOptions {
min?: number;
max?: number;
}

/**
* Implement the composable function
* Make sure the function works correctly
*/
function useCounter(initialValue = 0, options: UseCounterOptions = {}) {
const count = ref(initialValue);

const inc = () => count.value < options.max && count.value++;
const dec = () => count.value > options.min && count.value--;
const reset = () => (count.value = initialValue);

return { count, inc, dec, reset };
}

const { count, inc, dec, reset } = useCounter(0, { min: 0, max: 10 });
</script>

<template>
<p>Count: {{ count }}</p>
<button @click="inc">inc</button>
<button @click="dec">dec</button>
<button @click="reset">reset</button>
</template>

1.5 实现本地存储函数

我们经常需要使用 localStorageAPI,一个好用的可组合函数封装将帮助我们更好地使用它,让我们开始吧 👇:

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

/**
* Implement the composable function
* Make sure the function works correctly
*/
function useLocalStorage(key: string, initialValue: any) {
const value = ref(localStorage.getItem(key) ?? initialValue);
watchEffect(() => localStorage.setItem(key, value.value));
return value;
}

const counter = useLocalStorage("counter", 0);

// We can get localStorage by triggering the getter:
console.log(counter.value);

// And we can also set localStorage by triggering the setter:

const update = () => counter.value++;
</script>

<template>
<p>Counter: {{ counter }}</p>
<button @click="update">Update</button>
</template>

1.6 切换焦点指令

这个挑战开始,我们将尝试编写自定义指令,让我们从 v-focus 开始 👇:

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

const state = ref(false);

/**
* Implement the custom directive
* Make sure the input element focuses/blurs when the 'state' is toggled
*
*/

const VFocus = {
updated: (el, binding) => {
binding.value ? el.focus() : el.blur();
},
};

setInterval(() => {
state.value = !state.value;
}, 2000);
</script>

<template>
<input v-focus="state" type="text" />
</template>

1.7 防抖点击指令

在这个挑战中,我们将尝试实现一个防抖点击指令,让我们开始吧 👇:

<script setup lang="ts">
/**
* Implement the custom directive
* Make sure the `onClick` method only gets triggered once when clicked many times quickly
* And you also need to support the debounce delay time option. e.g `v-debounce-click:ms`
*
*/

const VDebounceClick = {
mounted: (el: HTMLElement, binding: { value: Function; arg: number }) => {
el.addEventListener("click", debounce(binding.value, binding.arg));
},
};

function debounce(fn: Function, delay: number) {
let timer = null;
let clickCount = 0;
return function () {
clickCount++;
if (clickCount === 1) return void fn();
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
fn();
}, delay);
};
}

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>

1.8 函数式组件

在这个挑战中,我们将尝试实现一个函数式组件,让我们开始吧 👇:

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

/**
* Implement a functional component :
* 1. Render the list elements (ul/li) with the list data
* 2. Change the list item text color to red when clicked.
*/
const ListComponent = (props, { emit }) => {
// 使用 map 方法遍历列表数据,并为每个数据项创建一个 <li> 元素
return h(
"ul",
{},
props.list.map((item, index) =>
h(
"li",
{
style: index === props["active-index"] ? { color: "red" } : null, // 如果当前列表项是选中的,将其文本颜色设置为红色
onClick: () => toggle(index), // 为 <li> 元素添加点击事件处理函数
},
item.name
)
)
);
};

const list = [
{
name: "John",
},
{
name: "Doe",
},
{
name: "Smith",
},
];

const activeIndex = ref(0);

function toggle(index: number) {
activeIndex.value = index;
}
</script>

<template>
<list-component :list="list" :active-index="activeIndex" @toggle="toggle" />
</template>

1.9 渲染函数[h()]

在这个挑战中,你需要使用 h 渲染函数来实现一个组件。

请注意: 你应该确保参数被正确传递、事件被正常触发和插槽内容正常渲染。让我们开始吧。

import { defineComponent, h } from "vue";

export default defineComponent({
name: "MyButton",
props: {
disabled: {
type: Boolean,
default: false,
},
},
emits: ["custom-click"],
render(ctx) {
return h(
"button",
{
disabled: ctx.disabled,
onClick: () => {
ctx.$emit("custom-click");
},
},
ctx.$slots
);
},
});

1.10 按键修饰符

在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许为 v-on 或者 @ 在监听键盘事件时添加按键修饰符:在这个挑战中,我们将尝试它,让我们开始吧:

<template>
<!-- Add key modifiers made this will fire even if Alt or Shift is also pressed -->
<button @click.alt="onClick1" @click.shift="onClick1">A</button>

<!-- Add key modifiers made this will only fire when Shift and no other keys are pressed -->
<button @click.shift.exact="onCtrlClick">A</button>

<!-- Add key modifiers made this will only fire when no system modifiers are pressed -->
<button @click.exact="onClick2">A</button>
</template>

<script setup>
function onClick1() {
console.log("onClick1");
}
function onCtrlClick() {
console.log("onCtrlClick");
}
function onClick2() {
console.log("onClick2");
}
</script>

1.11 鼠标坐标

在使用 Vue.js 时,我们应该关注可复用性,可组合函数是一个很好的方式,让我们开始吧 👇:

<script setup lang="ts">
import { ref } from "vue";
// Implement ...
function useEventListener(target, event, callback) {
target.addEventListener(event, callback);
}

// Implement ...
function useMouse() {
const x = ref(0);
const y = ref(0);
useEventListener(window, "mousemove", (event) => {
x.value = event.clientX;
y.value = event.clientY;
});
return { x, y };
}
const { x, y } = useMouse();
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

1.12 全局 CSS

有些时候,我们想在具有 CSS 作用域的 Vue 单文件组件设置全局 CSS 样式, 该怎么设置呢 ? 让我们开始吧 👇:

<template>
<p>Hello Vue.js</p>
</template>

<style scoped>
p {
font-size: 20px;
color: red;
text-align: center;
line-height: 50px;
}

/* Make it work */
:global(body) {
width: 100vw;
height: 100vh;
background-color: burlywood;
}
</style>

1.13 原始值 API

在这个挑战中,你将使用 响应式 API: [xx]Raw 来完成它。 以下是你要实现的内容 👇:

<script setup lang="ts">
import { reactive, toRaw, isReactive, markRaw } from "vue";

const state = { count: 1 };
const reactiveState = reactive(state);

/**
* Modify the code so that we can make the output be true.
*/
console.log(toRaw(reactiveState) === state);

/**
* Modify the code so that we can make the output be false.
*/
const info = markRaw({ count: 1 });
const reactiveInfo = reactive(info);
console.log(isReactive(reactiveInfo));
</script>

<template>
<div>
<p>{{ reactiveState.count }}</p>
</div>
</template>

1.14 Effect 作用域 API

在这个挑战中,你将使用 响应式 API: effectScope 来完成它。 以下是你要实现的内容 👇

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

const counter = ref(1);
const doubled = computed(() => counter.value * 2);

// use the `effectScope` API to make these effects stop together after being triggered once
const scope = effectScope();
scope.run(() => {
watch(doubled, () => console.log(doubled.value));
watchEffect(() => console.log(`Count: ${doubled.value}`));
});
counter.value = 2;

setTimeout(() => {
scope.stop();
counter.value = 4;
});
</script>

<template>
<div>
<p>{{ doubled }}</p>
</div>
</template>