vue3挑战-简单篇[2](适合入门)

296 阅读6分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

Vue挑战是一个非常适合Vue3入门的项目,里面的题目基本涵盖了Vue3的所有基础用法(特别是Vue3新引入的组合式写法),并且还在持续更新中,关于这个项目的具体介绍和原理,可以看一下这篇文章。并且在看这篇文章之前,你最好自己先去做一遍,这个文章里的写法只是我自己的方式(当然基础用法大家应该都大同小异)

浅层ref

点击跳转到题目

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

使用shallowRef创建的对象跟ref不同,它只会为最外层注册响应性,因此只有通过访问和修改.value才能使响应性生效。

这个API常被用于处理较大的响应性数据结构以节约开销。

更多详情可以查看vue文档的浅层ref

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

依赖注入

点击跳转到题目

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

依赖注入是父子组件值传递中经常使用的方式,在vue3的组合式API中变成了使用Provide Inject进行操作(实际上选项式写法的时候他们也要成对出现的,一个作用于父组件,一个作用于子组件)

更多详情可以查看vue文档的依赖注入

<script setup lang="ts">
import { ref, provide } from "vue"
import Child from "./Child.vue"
const count = ref(1)
provide("count", count)
setInterval(() => {
  count.value++
}, 1000)
</script>

<template>
  <Child />
</template>
// Child.vue
<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>

生命周期钩子

点击跳转到题目

在这个挑战中,你将使用 组合式 API: 生命周期钩子 来完成它。 以下是你要实现的内容 👇: 切换子组件时, 定时器将不正常工作, 让我们来修复它

题目没有什么特别的,就是通过依赖注入来保存当前的计数值count和定时器的idtimer,然后在子组件初始化的生命周期钩子mount中开始定时器,如果子组件卸载后没有取消定时器,那么再次加载组件的时候就会在前一个定时器继续运行的基础上开启另一个定时器,导致其不能正常工作,只要在子组件的onUnmounted周期钩子中停掉定时器即可。

更多详情可以查看vue文档的生命周期钩子

// Child.vue
<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(() => {
  clearInterval(timer.value)
})

</script>

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

下一次DOM更新

点击跳转到题目

Vue.js中改变响应式状态时,DOM不会同步更新。 Vue.js 提供了一个用于等待下一次DOM更新的方法,让我们开始吧 👇: DOM还未更新,如何确保DOM已经更新 请保证以下输出为true

当我们去改变响应式对象的值时,diff算法的触发和DOM的重新渲染是在异步进行的,也就是说我们无法在修改响应式对象后马上获得修改后的DOM状态,因此引入了一个APInextTick,向其中传入一个钩子函数,以达到在下一次渲染后获取最新DOM状态的目的。

更多详情可以查看vue文档的nextTick

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

const count = ref(0)
const counter = ref(null)

function increment() {
  count.value++

  /**
   * DOM is not yet updated, how can we make sure that the DOM gets updated
   * Make the output be true
  */
  nextTick(() => {
    console.log(+counter.value.textContent === 1)
  })
}
</script>

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

DOM传送门

点击跳转到题目

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

在某些场景下,我们不想将某些元素的DOM渲染在#app中(比如覆盖全局的弹窗),因为它可能会受到父元素的样式影响(比如父元素设定了透明度、背景颜色),这时候Vue提供的传送门组件<Teleport>就可以派上用场,这个组件接受一个to的属性,属性值为一个选择器(标签、类、id),即可将<Teleport>中包裹的内容放入到选择器的DOM中作为该DOM的子元素。

值得一提的是,这个操作仅仅是移动了内容的位置,并不改变vue中父子组件的关系,你仍然可以使用prop``emit之类的方式完成父子组件的值传递。

更多详情可以查看vue文档的Teleport

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

动态CSS

点击跳转到题目

Vue单文件组件 <style> 模块支持给CSS绑定动态值。 修改以下代码绑定动态颜色

Vue3的单文件组件的<style>模块也支持动态绑定了!而且绑定的对象具有响应性,当响应性对象发生变化时,样式也会随之变化,使用方法就是在需要取值的位置填写v-bind('要绑定的对象名'),无需填写.value,Vue会像模板中一样自动对其进行解构。

注意,在对象调用中,绑定的对象+属性名最好用单引号'来包裹(否则在部分旧版本中可能会出现错误)。

更多详情可以查看vue文档的CSS 中的 v-bind()

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

阻止事件冒泡

点击跳转到题目

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

事件修饰符是在vue事件中常用而且非常方便的工具,使用.stop可以阻止事件冒泡,使用.prevent可以阻止默认行为(比如表单提交),使用.self可以仅捕捉自身触发的事件,使用.capture可以优先在父元素处理冒泡而来的事件,并且,事件修饰符也可以用链式写法来使用多个:

<a @click.stop.prevent="clickHyperLink">Click Me</a>

更多详情可以查看vue文档的事件修饰符

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

自定义的修饰符

点击跳转到题目

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

这道题的解答有些争议,原题目的描述似乎是直接修改原生input标签的v-model修饰符,也有人给出了使用vModelText.updated来在DOM更新之前监听全局变化来处理大小写的妙招,不过我更倾向于这道题的描述不准确,实际上要求是在一个自定义组件中实现自定义修饰符,因为这道题位于简单的分类中,而且参照前面几题都是可以在官方文档中找到答案的情况,实现自定义组件中的修饰符比较合理。

v-model是一个语法糖,实际上就是帮你在子组件上实现了传值和赋值,也就是这样:

<Input v-model="value" />
// 等价于
<Input :modelValue="value" @update:modelValue="e => value = e" />

也就是说,我们可以在子组件上使用modelValue来接收v-model传入的值:

const props = defineProps({
    modelValue: String
})

使用modelModifiers来接收v-model上的修饰符:

<Input v-model.capitalize.native.prevert="value" />
const props = defineProps({
    modelValue: String,
    modelModifiers: {
      type: Object,
      default: () => ({})
    }
})
console.log(props.modelModifiers);//{capitalize: true, native: true, prevert: true}

因此可以用它来判断传入的修饰符,对传入capitalizev-model返回值进行处理。

更多详情可以查看vue文档的配合v-model使用

// App.vue
<script setup>
    import { ref } from 'vue'
    import Input from './Input.vue'
    const value = ref("")
</script>

<template>
  <Input v-model.capitalize.native.prevert="value" />
</template>
// input.vue
<script setup lang="ts">
    import { defineProps, defineEmits } from 'vue'
    const prop = defineProps({
        modelValue: String,
        modelModifiers: {
          type: Object,
          default: () => ({})
        }
      })

    console.log(prop.modelModifiers)

    const emit = defineEmits(['update:modelValue'])

    function emitChange(e) {
      let value = e.target.value
      if (prop.modelModifiers.capitalize) {
        value = (value[0]?.toUpperCase() ?? "") + value.slice(1)
      }
      emit("update:modelValue",value)
    }
</script>
<template>
  <input type="text" :value="modelValue" @change="emitChange" />
</template>

prop验证

点击跳转到题目

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

props中除了指定类型和默认值外,还可以传入一个validator作为值验证,验证通过返回true,验证不通过返回false,不通过时会在控制台中抛出一个警告(开发模式下)

// App.vue
<script setup>
    import Button from "./Button.vue"
</script>

<template>
     <Button type="default"/>
</template>
// Button.vue
<script setup>
  import { defineProps } from 'vue'
  defineProps({
    type: {
      default: 'defalut',
      validator: (value) => {
         const validList = ['primary','ghost','dashed','link','text','default']
         return validList.includes(value)
      }
    },
  })
</script>

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

下一篇文章将会是中等难度的问题:

image.png