1. 热身

Hello,World!在这个挑战中,我们使用基于 vuejs/repl 的 SFC 编码游乐场进行在线编码且通过 StackBlitz 和 Vitest 进行挑战判断。对于这个挑战,您将需要更改以下代码,以使页面正确显示“Hello World”。

<script setup>
import { ref } from "vue";
const msg = ref("Hello World");
</script>

<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>

2. 简单

2.1 生命周期钩子

在这个挑战中,你将使用 组合式 API: 生命周期钩子 来完成它。 以下是你要实现的内容 👇:

<script setup lang="ts">
import { onMounted, inject, onUnmounted } from "vue";

const timer = inject("timer");
const count = inject("count");

onMounted(() => {
timer.value = window.setInterval(() => {
count.value++;
}, 1000);
});

onUnmounted(() => {
window.clearInterval(timer.value);
});
</script>

<template>
<div>
<p>Child Component: {{ count }}</p>
</div>
</template>

2.2 下一次 DOM 更新

在 Vue.js 中改变响应式状态时,DOM 不会同步更新。 Vue.js 提供了一个用于等待下一次 DOM 更新的方法,让我们开始吧 👇:

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

const count = ref(0);

function increment() {
count.value++;
nextTick(() => {
console.log(+counter.value.textContent === 1);
});
}
</script>

<template>
<button ref="counter" @click="increment">
{{ count }}
</button>
</template>

2.3 DOM 传送门

Vue.js 提供了一个内置组件,将其插槽内容渲染到另一个 DOM,成为该 DOM 的一部分。你知道它是什么吗 ? 让我们试试 👇:

<script setup>
const msg = "Hello World";
</script>

<template>
<!-- Renders it to a child element of the `body` -->
<teleport to="body">
<span>{{ msg }}</span>
</teleport>
</template>

2.4 动态 CSS

Vue 单文件组件 <style> 模块支持给 CSS 绑定动态值。你知道它是什么吗 ? 让我们试试 👇:

<script setup>
import { ref } from "vue";
const theme = ref("red");

const colors = ["blue", "yellow", "red", "green"];

setInterval(() => {
theme.value = colors[Math.floor(Math.random() * 4)];
}, 1000);
</script>

<template>
<p>hello</p>
</template>

<style scoped>
/* Modify the code to bind the dynamic color */
p {
color: v-bind(theme);
}
</style>

2.5 ref 全家桶

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

<script setup lang="ts">
import { ref, Ref, reactive, isRef, unref, toRef } from "vue";

const initial = ref(10);
const count = ref(0);

// Challenge 1: Update ref
function update(value) {
count.value = value;
}

/**
* Challenge 2: Check if the `count` is a ref object.
* Make the output be 1
*/
console.log(
// impl ? 1 : 0
isRef(count) ? 1 : 0
);

/**
* Challenge 3: Unwrap ref
* Make the output be true
*/
function initialCount(value: number | Ref<number>) {
// Make the output be true
// console.log(value === 10)
console.log(unref(value) === 10);
}

initialCount(initial);

/**
* Challenge 4:
* create a ref for a property on a source reactive object.
* The created ref is synced with its source property:
* mutating the source property will update the ref, and vice-versa.
* Make the output be true
*/
const state = reactive({
foo: 1,
bar: 2,
});
const fooRef = toRef(state, "foo"); // change the impl...

// mutating the ref updates the original
fooRef.value++;
console.log(state.foo === 2);

// mutating the original also updates the ref
state.foo++;
console.log(fooRef.value === 3);
</script>

<template>
<div>
<p>
<span @click="update(count - 1)">-</span>
{{ count }}
<span @click="update(count + 1)">+</span>
</p>
</div>
</template>

2.6 阻止事件冒泡

在这个挑战中,你需要阻止点击事件的冒泡,让我们开始吧。

<script setup lang="ts">
const click1 = () => {
console.log("click1");
};

const click2 = () => {
console.log("click2");
};
</script>

<template>
<div @click="click1()">
<div @click.stop="click2()">click me</div>
</div>
</template>

2.7 响应性丟失

在 JavaScript 中,我们经常解构/扩展对象。在 Vue.js 中,我们同样解构/扩展“响应式”对象,但它会失去响应性。如何保证解构/扩展不丢失响应性 ? 让我们开始吧 !

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

function useCount() {
const state = reactive({
count: 0,
});

function update(value: number) {
state.count = value;
}

return {
// state,
state: toRefs(state),
update,
};
}

// Ensure the destructured properties don't lose their reactivity
const {
state: { count },
update,
} = useCount();
</script>

<template>
<div>
<p>
<span @click="update(count - 1)">-</span>
{{ count }}
<span @click="update(count + 1)">+</span>
</p>
</div>
</template>

2.8 大写

请创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值首字母转为大写:

<script setup>
import { ref, vModelText } from "vue";
const value = ref("");
vModelText.beforeUpdate = (el, binding) => {
if (el.value && binding.modifiers.capitalize) {
el.value = el.value.charAt(0).toUpperCase() + el.value.slice(1);
}
};
</script>

<template>
<input type="text" v-model.capitalize="value" />
</template>

2.9 Prop 验证

请验证 Button 组件的 Prop 类型 ,使它只接收: primary | ghost | dashed | link | text | default ,且默认值为 default。

<script setup>
defineProps({
type: String,
default: "default",
validator(value) {
return ["primary", "ghost", "dashed", "link", "text", "default"].includes(
value
);
},
});
</script>

<template>
<button>Button</button>
</template>

2.10 可写的计算属性

在这个挑战中,你需要创建一个可写的计算属性 :

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

const count = ref(1);
const plusOne = computed({
get: () => {
return count.value + 1;
},
set: () => {
count.value++;
},
});

plusOne.value++;
</script>

<template>
<div>
<p>{{ count }}</p>
<p>{{ plusOne }}</p>
</div>
</template>

2.11 watch 全家桶

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

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

const count = ref(0);

const unwatch = watch(count, () => {
console.log("Only triggered once");
unwatch();
});

count.value = 1;
setTimeout(() => (count.value = 2));

const state = ref({
count: 0,
});

watch(
state,
() => {
console.log("The state.count updated");
},
{
deep: true,
}
);

state.value.count = 2;

const eleRef = ref();
const age = ref(2);
watch(
age,
() => {
console.log(eleRef.value);
},
{
flush: "post",
}
);
age.value = 18;
</script>

<template>
<div>
<p>
{{ count }}
</p>
<p ref="eleRef">
{{ age }}
</p>
</div>
</template>

2.12 浅层 ref

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

<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>

2.13 依赖注入

在这个挑战中,你将使用 组合式 API: 依赖注入 来完成它。 以下是你要实现的内容 👇:

<script setup lang="ts">
// Add a piece of code to make the `count` value get injected into the child component.
import { inject } from "vue";
const count = inject("count");
</script>

<template>
{{ count }}
</template>