vue3挑战-中等篇[1]

299 阅读4分钟

我正在参加「掘金·启航计划」

前言

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

这篇文章里的题目将会用到一些比较偏门但是对特殊场景大有裨益的API,能够熟悉并掌握它们,能够大幅提升开发效率。

原始值API

点击跳转到题目

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

在一些场景下,我们需要获取已经成为响应式的对象原始值用于临时读取而不引起额外的开销,或者是某些特殊场景下改变对象的值而不触发响应更新,这时候就需要用到toRaw来将获取响应式对象的原始对象。

同理,某些对象可能始终不应该具有响应性(比如庞大但不需要副作用的对象),则可以使用markRaw来标记他们,使得他们永远不会被代理。

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

<script setup lang="ts">
import { reactive, isReactive, toRaw, 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>

Effect作用域 API

点击跳转到题目

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

根据前面的题目,我们知道要停止一个响应式副作用的监听,直接执行它返回的方法就可以了:

var a = watch(doubled, () => console.log(doubled.value))
var b = watchEffect(() => console.log(`Count: ${doubled.value}`))

counter.value = 2
nextTick(() => {
  a()
  b()
})

但是,如果监听过多,想要一并处理掉呢,一个个写就比较麻烦了,这时候就可以使用effectScope将他们包裹起来,一并停止掉:

const scope = effectScope()

scope.run(() => {
  watch(doubled, () => console.log(doubled.value))
  watchEffect(() => console.log(`Count: ${doubled.value}`))
})

scope.stop()

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

<script setup lang="ts">
import { ref, computed, watch, watchEffect, effectScope, nextTick } 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
nextTick(() => {
  scope.stop()
})

setTimeout(() => {
  counter.value = 4
})

</script>

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

优化性能的指令

点击跳转到题目

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

这是性能优化方面的老生常谈了,前面也做过很多思路一样的处理,也就是在某些场景下仅需要渲染一次的内容我们给他做个标记,以取消之后的所有副作用,以此来减少性能开销,只不过之前的操作都是js层面对响应式对象进行操作,而这里是使用v-once对模板的元素进行标记,使得元素和组件只渲染一次。

更多详情可以查看vue文档的v-once

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

组合式函数

点击跳转到题目

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

组合式函数是Vue中很常用的一个语法糖,封装一个有状态的逻辑的函数,并将其状态逻辑和更新方法返回,在函数调用时使用解构的方式进行状态的获取。

更多详情可以查看vue文档的组合式函数

<script setup lang='ts'>

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

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++
    if (count.value > options.max) {
      count.value = options.max
    }
  }
  const dec = () => { 
    count.value-- 
    if (count.value < options.min) {
      count.value = options.min
    }
  }
  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>

组合式函数练习-鼠标坐标

点击跳转到题目

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

继续练习组合式函数(这个单元测试不知道为啥没通过,可能是环境有点问题报了target.addEventListener is not a function的错误,但是右侧页面的交互是正常的):

<script setup lang="ts">

import { ref, onMounted, onUnmounted } from 'vue'

// Implement ...
function useEventListener(target, event, callback) {
  // 将函数赋给一个变量以便页面卸载的时候移除监听
  const eventCallback = ({clientX, clientY}) => {
    callback(clientX, clientY)
  }

  target.addEventListener(event, eventCallback)

  onUnmounted(() => {
    target.removeEventListener(event, eventCallback)
  })
}

// Implement ...
function useMouse() {
  let x = ref(0)
  let y = ref(0)
  useEventListener(window, "mousemove", (resX, resY) => {
    x.value = resX
    y.value = resY
  })
  return {x, y}
}
const { x, y } = useMouse()
</script>

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

组合式函数练习-实现本地存储函数

点击跳转到题目

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

组合式函数大杂烩啊,不过确实配合watch实现了对localStorage的“双向绑定”,开发起来方便了不少:

<script setup lang='ts'>

import { ref, watchEffect, unref } from "vue"

/**
 * Implement the composable function
 * Make sure the function works correctly
*/
function useLocalStorage(key: string, initialValue: any) {
  const value = ref(initialValue)
  value.value = localStorage.getItem(key) || initialValue

  watchEffect(() => {
    localStorage.setItem(key, unref(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>

until异步处理

点击跳转到题目

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

asyncawait的组合是promise的语法糖,避免了回调地狱,使得异步处理的写法看起来像同步一样更加直观:

async function increase() {
  const res = await until(count).toBe(3)
  console.log(res) // Make sure the output is true
}
// 等价于
function increase() {
  until(count).toBe(3).then(res => {
      console.log(res)
  });
}

因此这道题可以配合watch来进行处理(因为watch之前讲过而且这道题没有引入什么新的API所以就没有参考资料了):

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

const count = ref(0)

/**
 * Implement the until function
*/

function until(initial) {
  function toBe(value) {
    return new Promise(resolve => {
      const unWatch = watch(count, newCount => {
        if (newCount === value) {
          unWatch()
          resolve()
        }
      })
    });
  }

  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>