Vue3学习 --- 知识点补充

141 阅读5分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

nextTick

将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它

需求

页面上有一个按钮,点击按钮后会在h2标签中添加内容,我们需要在控制台中输出添加内容后,h2容器所占的高度

<template>
  <div>
   <h2 ref="h2Ref">{{ msg }}</h2>
   <button @click="addContent">add content</button>
  </div>
</template>

<script>
import { ref } from 'vue'

export default {
  setup() {
    const h2Ref = ref(null)
    const msg = ref('')

    const addContent = () => {
      msg.value = 'Foo'
      // 此时只是修改了msg的值,还未真正修改DOM
      // 所以下面这行代码只能获取DOM更新前的状态
      console.log(h2Ref.value.offsetHeight) // => 0
    }

    return {
      h2Ref,
      msg,
      addContent
    }
  }
}
</script>

如果我们想要获取DOM更新后的状态,也就是等待DOM更新完毕以后再去执行对应的代码

<template>
  <div>
   <h2 ref="h2Ref">{{ msg }}</h2>
   <button @click="addContent">add content</button>
  </div>
</template>

<script>
import { ref, onUpdated } from 'vue'

export default {
  setup() {
    const h2Ref = ref(null)
    const msg = ref('')

    const addContent = () => {
      msg.value = 'Foo'
    }

    // 我们可以等到DOM更新完毕后,在onUpdated函数的回调中去执行对应的逻辑
    // 但是界面的更新并不一定是由于button的点击触发的,可能是页面的其它部分触发
    // 所以将对应的逻辑放置在onUpdated方法中是不合理的
    onUpdated(() => console.log(h2Ref.value.offsetHeight))

    return {
      h2Ref,
      msg,
      addContent
    }
  }
}
</script>

为此vue提供了nextTick方法,这个方法会在某一个具体的DOM更新完毕后被调用

<template>
  <div>
   <h2 ref="h2Ref">{{ msg }}</h2>
   <button @click="addContent">add content</button>
  </div>
</template>

<script>
import { ref, nextTick } from 'vue'

export default {
  setup() {
    const h2Ref = ref(null)
    const msg = ref('')

    const addContent = () => {
      msg.value = 'Foo'

      // nextTick的参数是一个回调函数, 该回调函数会dom完全被更新完毕时立即执行
      nextTick(() => console.log(h2Ref.value.offsetHeight))
    }

    return {
      h2Ref,
      msg,
      addContent
    }
  }
}
</script>

原理

在浏览器中存在一个处理异步操作的机制,那就是事件循环

在事件循环中维护着两个队列,分别是微任务队列(主要为promise等)宏任务队列(主要为定时器,dom更新等)

当异步函数执行完毕,需要被回调的时候,这些异步函数会根据他们的类别被放入对应的微任务队列或宏任务队列中

等主线程(同步代码)执行完毕后,浏览器会去查看微任务队列中查看有没有任务需要执行

等微任务队列执行完毕以后才会去执行对应的宏任务队列

并且在每次执行宏任务队列之前,都会确保此时微任务队列中并没有微任务存在,如果存在微任务会优先执行微任务后在执行对应的宏任务

可见宏任务的优先级是最低的

而在vue中,如果修改了响应式数据,vue需要进行一系列的操作,如执行watch,dom更新,触发对应的生命周期等

而vue将这些操作都封装成了一个个的队列,并将这些队列放入了微任务队列中

主要可以分为:

flushPreQueue ---- 主要是watch,computed等相关操作

queue ---- 组件更新的相关操作

flushPostQueue ---- 主要是一些生命周期函数(如updated,mounted)

所以如果我们在vue中编写了对应的更新dom的代码,在更新代码后是无法获取到最新的dom相关属性值

因为如果在更新dom的代码后直接获取dom相关属性值是位于主任务队列中的同步代码

此时位于微任务中的queueu队列并没有执行,也就是此时组件并没有执行对应的更新操作

而我们又需要可以在dom更新后,可以拿到更新后的属性值,所以vue提供了newTick方法

该方法会返回一个promise,并被插入到微任务队列中最后

此时queue队列已经执行完毕,所以我们可以获取到最新的dom属性值

在此过程中,我们将一次微任务队列的执行被称之为一个tick周期

所以在一个周期执行后立即执行,因此该方法被称之为nextTick

这么做存在一个优势在于

const increment = () => {
	// 当vue在执行以下循环的时候
  // 因为dom的更新是在微任务队列中的
  // 而循环是在主任务队列中的
  // 所以vue会等待循环执行完毕后再去更新对应的dom元素
  // 此时就确保了在这种情况下,对应的dom元素只会被更新一次
  // 而不会更新100次
  for(let i = 0; i < 100; i++) {
    counter.value++
  }
}

historyApiFallback

historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在history模式下,进行路由跳转之后,进行页面刷新 时,返回404的错误

因为是前端路由,所以刷新的时候,去后端请求对应资源的时候,是请求不到对应的资源的,所以会报404错误

historyApiFallback是DevServer的一个选项,是通过connect-history-api-fallback库,这个选项是适合于解决开发环境的问题

如果需要解决线上问题的时候,还是需要对nginx服务器进行对应的配置(比如配置try_files字段对应的值)

vue.config.js

这个文件是@vue/cli的配置文件,可以配置自己对于脚手架的设置,其中设置的规则会和预先定义的webpack规则进行合并

module.exports = {
  // configureWebpack对应的值,最后会和掉脚手架中默认的webpack配置选项进行合并
  // 值可以是对象,也可以是函数,如果是函数的时候,会将设置的env等传入进来
  // 注意: 如果在这里配置错误,界面上可能不会有任何的效果,
  // 因为合并的属性配置错误的时候,合并失败,就会直接使用预先定义为webpack的预设
  configureWebpack: {
    devServer: {
      // 设置为true的时候,如果找不到资源,直接返回index.html
      historyApiFallback: true
    }
  }
}
module.exports = {
  configureWebpack: {
    devServer: {
      historyApiFallback: {
        // 路径忽略点规则
        // 设置为true的时候 www.example.com/home 和 www.example.com/home.html 所需要显示的组件是一样的
        // 设置为false的时候 www.example.com/home.html会直接返回404
        disableDotRule: true
      }
    }
  }
}
module.exports = {
  configureWebpack: {
    devServer: {
      historyApiFallback: {
        disableDotRule: true,

        rewrites: [
          // 可以根据不同的路径去返回不同的资源
          {
            from: /.*/, // 正则 用于匹配对应的路径
            to: '/index.html' // 返回开发服务器根目录下的index.html
          }
        ]
      }
    }
  }
}