试题链接:https://vuejs-challenges.netlify.app/
目录
热身
1 - 你好!
题目
修改以下代码,以使页面正确显示 Hello World
。
<script setup>
import { ref } from "vue"
const msg = ref("Hello World")
</script>
<template>
<div>
<!-- 页面的期望输出是Hello World -->
<h1>msg</h1>
</div>
</template>
答案与解析
<script setup>
// 1. 引入响应式函数 ref
import { ref } from "vue"
// 2. 运用响应式函数 ref 声明响应式变量 msg,并赋初始值为字符串"Hello World"
const msg = ref("Hello World")
</script>
<template>
<div>
<!-- 3. 在模板中渲染响应式变量 msg -->
<h1>{{msg}}</h1>
</div>
</template>
简单
2 - ref 全家桶
题目
在这个挑战中,你将使用 响应式 API: ref
来完成它。 以下是你要实现的内容 👇:
<script setup lang="ts">
import { ref, Ref, reactive } from "vue"
const initial = ref(10)
const count = ref(0)
// 挑战 1: 更新 ref
function update(value) {
// 实现...
}
/**
* 挑战 2: 检查 count 是否为一个 ref 对象
* 确保以下输出为1
*/
console.log(
// impl ? 1 : 0
)
/**
* 挑战 3: 如果参数是一个 ref,则返回内部值,否则返回参数本身
* 确保以下输出为true
*/
function initialCount(value: number | Ref<number>) {
// 确保以下输出为true
console.log(value === 10)
}
initialCount(initial)
/**
* 挑战 4:
* 为源响应式对象上的某个 `property` 新创建一个 `ref`。
* 然后,`ref` 可以被传递,它会保持对其源`property`的响应式连接。
* 确保以下输出为true
*/
const state = reactive({
foo: 1,
bar: 2,
})
const fooRef = ref() // 修改这里的实现...
// 修改引用将更新原引用
fooRef.value++
console.log(state.foo === 2)
// 修改原引用也会更新`ref`
state.foo++
console.log(fooRef.value === 3)
</script>
<template>
<div>
<h1>msg</h1>
<p>
<span @click="update(count-1)">-</span>
{{ count }}
<span @click="update(count+1)">+</span>
</p>
</div>
</template>
答案与解析
<script setup lang="ts">
import { ref, type Ref, reactive, isRef, unref, toRef } from 'vue'
const initial = ref(10)
const count = ref(0)
// 挑战 1: 更新 ref
function update(value: number) {
// 考察点一:count 是一个 ref 对象,因此可以为 .value 赋予新的值。
count.value = value
}
/**
* 挑战 2: 检查`count`是否为一个 ref 对象
* 确保以下输出为1
*/
// 考察点二:应用 isRef 函数检查 count 是否为 ref 对象。
console.log(isRef(count) ? 1 : 0) // 1
/**
* 挑战 3: 如果参数是一个 ref,则返回内部值,否则返回参数本身
* 确保以下输出为true
*/
function initialCount(value: number | Ref<number>) {
// 考察点三:应用 unRef 函数,检查 value 是否为 ref 对象。如果是,返回 .value 属性,否则返回参数本身。
// 相当于 val = isRef(value)? value.value : value
console.log(unref(value) === 10) // true
}
initialCount(initial)
/**
* 挑战 4:
* 为源响应式对象上的某个 `property` 新创建一个 `ref`。
* 然后,`ref` 可以被传递,它会保持对其源`property`的响应式连接。
* 确保以下输出为true
*/
const state = reactive({
foo: 1,
bar: 2
})
/**
* 考察点四:
* 1. toRef 函数可以创建一个 ref 对象,其内部值为源对象的某个属性。
* 2. ref 可以被传递,它会保持对其源对象属性的响应式连接。
* 3. 也就是说,修改 fooRef.value 的值,也会同步修改 state.foo 的值。
* 4. 反之亦然,修改 state.foo 的值,也会同步修改 fooRef.value 的值。
*/
const fooRef = toRef(state, 'foo')
// 修改引用将更新原引用
fooRef.value++
console.log(state.foo === 2) // true
// 修改原引用也会更新`ref`
state.foo++
console.log(fooRef.value === 3) // true
</script>
<template>
<div>
<h1>msg</h1>
<p>
<span @click="update(count - 1)">-</span>
{{ count }}
<span @click="update(count + 1)">+</span>
</p>
</div>
</template>
知识点巩固
ref()
- 接受一个内部值,返回一个响应式的、可更改的 ref 对象。
- 此对象只有一个指向其内部值的属性
.value
。 - ref 对象是可更改的,因此可以为
.value
赋予新的值。 - 它也是响应式的,即所有对
.value
的操作都将被追踪,并且写操作会触发与之相关的副作用。 - 如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。
const count = ref(0)
console.log(count.value) // 0
count.value = 1
console.log(count.value) // 1
isRef()
- 检查某个值是否为 ref。
unref()
- 如果参数是 ref,则返回内部值,否则返回参数本身。
- 这是
val = isRef(val) ? val.value : val
计算的一个语法糖。
3 - 响应式丢失
题目
在 JavaScript
中,我们经常解构/扩展对象。
在Vue.js
中,我们同样解构/扩展“响应式”对象,但它会失去响应性。
如何保证解构/扩展不丢失响应性 ? 让我们开始吧 !
<script setup lang="ts">
import { reactive } from "vue"
function useCount() {
const state = reactive({
count: 0,
})
function update(value: number) {
state.count = value
}
return {
state,
update,
}
}
// 确保解构不丢失响应性
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>
答案与解析
<script setup lang="ts">
import { reactive, toRefs } from 'vue'
function useCount() {
const state = reactive({
count: 0
})
function update(value: number) {
state.count = value
}
return {
// 1.使用toRefs方法可以将一个对象转换为一系列响应式的属性组成的对象。
state: toRefs(state),
update
}
}
const {
// 2.解构赋值,得到的 count 是一个 ref 对象。
state: { count },
update
} = useCount()
</script>
<template>
<div>
<p>
<span @click="update(count - 1)">-</span>
{{ count }}
<span @click="update(count + 1)">+</span>
</p>
</div>
</template>
知识点巩固
toRef()
-
可以基于响应式对象上的一个属性,创建一个对应的 ref 。
-
这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
const state = reactive({
foo: 1,
bar: 2
})
// 双向ref,会与源对象同步
const fooRef = toRef(state,'foo')
// 更改 fooRef 的值会更改源对象 state
fooRef.value++
console.log(state.foo) // 2
// 更改源 state 对象属性值,fooRef的值也会更改。
state.foo++
console.log(fooRef.value) // 3
toRefs()
- 将一个响应式对象转换为一个普通对象,
- 这个普通对象的每个属性都是指向源对象相应属性的 ref。
- 其中每个单独的 ref 都是使用
toRef()
创建的。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
// 解构赋值
const {foo,bar} = toRefs(state)
// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2
console.log(foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
foo.value++
console.log(state.foo) //4
4 - 可写的计算属性
题目
在这个挑战中,你需要创建一个可写的计算属性 :
<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)
// 1. 创建一个只读的计算属性 ref
const plusOne = computed(() => count.value + 1)
plusOne.value++ // 报错,因为 plusOne 是只读的,不能修改 plusOne.value 的值。
// 2. 创建一个可写的计算属性 ref
const plusOne = computed({
get: () => count.value + 1,
set: (value) => {
count.value = value - 1
}
})
plusOne.value++ // 不报错,因为 plusOne 是可写的。
console.log('plusOne', plusOne.value) // 3
console.log('count', count.value) // 2
</script>
...
知识点巩固
computed()
-
默认接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过
.value
暴露 getter 函数的返回值。 -
也可以接受一个带有
get
和set
函数的对象来创建一个可写的 ref 对象。
5 - watch 全家桶
题目
在这个挑战中,你将使用 响应式 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>
答案与解析
挑战一
const count = ref(0)
/**
* 挑战 1: Watch 一次
* 确保副作用函数只执行一次
*/
// 考察点1:watch函数返回一个停止观察的函数,可以用来停止对特定数据的监听。
const stop = watch(count, () => {
console.log('Only triggered once')
// 当不需要再监听时,调用停止观察的函数即可。
stop()
})
count.value = 1
setTimeout(() => (count.value = 2))
挑战二
/**
* 挑战 2: Watch 对象
* 确保副作用函数被正确触发
*/
const state = ref({
count: 0
})
// 考察点2:如果想让回调在深层级变更时也能触发,需要使用 { deep: true } 强制侦听器进入深层级模式。
watch(
state,
() => {
console.log('The state.count updated')
},
{ deep: true }
)
state.value.count = 2
挑战三
/**
* 挑战 3: 副作用函数刷新时机
* 确保正确访问到更新后的`eleRef`值
*/
const eleRef = ref()
const age = ref(2)
/**
* 考察点3:当你更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调。
* 默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。
* 这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
* 如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项。
*/
watch(
age,
() => {
console.log(eleRef.value)
},
{ flush: 'post' }
)
age.value = 18
知识点巩固
watch()
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
-
watch()
默认是懒侦听
的,即仅在侦听源发生变化时才执行回调函数。 -
第一个参数
是侦听器的源。这个来源可以是以下几种:- 一个 getter 函数,返回一个值
const state = reactive({ count: 0 }) watch( () => state.count, // 侦听一个 getter 函数 (count, prevCount) => { /* ... */ } )
- 一个 ref 对象。
const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })
- 一个 reactive 对象,
侦听器默认启动深层模式
。
const state = reactive({ count: 0 }) watch(state, () => { /* 深层级变更状态所触发的回调 */ })
- 由以上类型的值组成的数组
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ })
-
第二个参数
是在数据源发生变化时要调用的回调函数
。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。 -
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
-
第三个参数
(可选)是一个对象,支持以下这些选项:immediate
:在侦听器创建时立即触发回调。第一次调用时旧值是undefined
。deep
:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。flush
:调整回调函数的刷新时机。参考回调的刷新时机及watchEffect()
。onTrack / onTrigger
:调试侦听器的依赖。参考调试侦听器。
6 - ShallowRef
题目
在这个挑战中,你将使用 响应式 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 })
// 回调没被触发
watch(
state,
() => {
console.log('State.count Updated')
},
{ deep: true }
)
/**
* 修改以下代码使watch回调被触发
*/
/**
* 考察点:shallowRef()
* 1. shallowRef() 是 ref() 的浅层作用形式。
* 2. shallowRef() 内部的值,不会被深层递归地转化为响应式。
* 3. 只有.value 的访问是响应式的。
* 4. 所以修改.value的值会触发更改,修改.value.count的值不会触发更改。
*/
// state.value.count = 2 // 不会触发更改
state.value = { count: 2 } // 会触发更改
</script>
<template>
<div>
<p>
{{ state.count }}
</p>
</div>
</template>
知识点巩固
shallowRef()
-
ref()
的浅层作用形式。 -
和
ref()
不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对.value
的访问是响应式的。 -
shallowRef()
常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
9 - 依赖注入
题目
在这个挑战中,你将使用 组合式 API: 依赖注入
来完成它。 以下是你要实现的内容 👇:
- 父组件:App.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">
// 添加代码,使`count`值注入子组件
</script>
<template>
{{ count }}
</template>
答案与解析
- 父组件:App.vue
<script setup lang="ts">
import { ref, provide } from 'vue'
import Child from './Child.vue'
const count = ref(1)
// 1. 在父组件中,运用 provide函数提供一个值,key 为 'count',值为一个响应式变量 count
provide('count', count)
setInterval(() => {
count.value++
}, 1000)
</script>
<template>
<Child />
</template>
- 子组件:Child.vue
<script setup lang="ts">
// 2. 在子组件中,运用 inject 函数注入父组件提供的响应式变量 count
import { inject } from 'vue'
const count = inject('count')
</script>
<template>
{{ count }}
</template>
知识点巩固
provide()
-
提供一个值,可以被
后代组件
注入。 -
provide()
接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。 -
与注册生命周期钩子的 API 类似,
provide()
必须在组件的setup()
阶段同步调用。
inject()
- 注入一个由
祖先组件
或整个应用
全局 (通过app.provide()
) 提供的值。 第一个参数
是注入的 key。- Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。(
就近原则
) - 如果没有能通过 key 匹配到值,
inject()
将返回undefined
,除非提供了一个默认值。 第二个参数
是可选的,即在没有匹配到 key 时使用的默认值。- 与注册生命周期钩子的 API 类似,
inject()
必须在组件的setup()
阶段同步调用。
305 - 大写
题目
请创建一个自定义的修饰符 capitalize
,它会自动将 v-model
绑定输入的字符串值首字母转为大写:
<script setup>
</script>
<template>
<input type="text" v-model.capitalize="" />
</template>
答案与解析
<template>
<input type="text" v-model.capitalize="myText" />
</template>
<script setup lang="ts">
// 引入 vModelText 函数
import { ref, vModelText } from 'vue'
const myText = ref('')
/** beforeUpdate 是 Vue 指令 vModelText 的生命周期函数。
* 在每次 myText 更新前,它会执行以下操作:
* 1. 检查 input 元素 el 中是否有值。
* 2. 检查是否存在 capitalize 修饰符。
* 3. 如果有值且有 capitalize 修饰符,则将值转换为首字母大写。
*/
vModelText.beforeUpdate = (el, binding) => {
if (el.value && binding.modifiers.capitalize) {
el.value = el.value.charAt(0).toUpperCase() + el.value.slice(1)
}
}
</script>
知识点巩固
beforeUpdate 是 Vue 指令 vModelText 的生命周期函数。
在每次 myText 更新前,它会执行以下操作:
- 检查 input 元素 el 中是否有值。
- 检查是否存在 capitalize 修饰符。
- 如果有值且有 capitalize 修饰符,则将值转换为首字母大写。
323 - Prop 验证
题目
请验证Button
组件的Prop
类型 ,使它只接收: primary | ghost | dashed | link | text | default
,且默认值为default
。
<script setup>
defineProps({
type: {},
})
</script>
<template>
<button>Button</button>
</template>
答案与解析
- 父组件:App.vue
<template>
<!-- 在模板中使用自定义组件 Button -->
<Button></Button>
</template>
<script setup lang="ts">
// 导入自定义组件 Button
import Button from './components/Button.vue'
</script>
- 子组件:Button.vue
<template>
<button>Botton</button>
</template>
<script setup lang="ts">
// 要声明对 props 的校验,你可以向 defineProps() 宏提供一个带有 props 校验选项的对象。
defineProps({
type: {
type: String, // String 类型校验
default: 'default', // 默认值为 'default'
validator: (value: string) => {
// 校验函数:type 值必须为 'primary', 'ghost', 'dashed', 'link', 'dashed', 'default' 中的一个,否则会在控制台
return ['primary', 'ghost', 'dashed', 'link', 'dashed', 'default'].includes(value)
}
}
})
</script>
知识点巩固
Prop 校验
-
Vue 组件可以在子组件中,声明对父组件中传入的 props 的校验要求。
-
如果校验失败,则会在控制台中打印警告。
-
校验规则:
- type: 校验类型,可以是 String, Number, Boolean, Array, Object, Function, Symbol, BigInt, null, undefined, 或者自定义的构造函数
- default: 默认值
- required: 是否必填
- validator: 自定义校验函数,返回一个布尔值。
-
校验规则的优先级:
校验函数 > 类型校验 > 默认值校验 > 必填校验。
中等
内容正快马加鞭赶来…
困难
内容正快马加鞭赶来…