2

前言

Vue.js作为前端流行的框架,Vue3中带来了许多新的特性和改进,真正理解掌握Vue3并不是一个简单的事情,为了帮助开发者更好的理解和如何应用,特意写了这个文章,深入探究Vue3的APi如何应用。

image.png

第一层:鬼门入口

请验证 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>

结尾

子曰:“三人行,必有我师焉。择其善者而从之,其不善者而改之。”——《述而》


along
1.7k 声望1.4k 粉丝

暂时还不知道写点什么,想起来了再写...