xinpianchang / fe-weekly

weekly for fe-team
10 stars 2 forks source link

Nuxt Loading 的内部机制 #101

Open dailynodejs opened 2 years ago

dailynodejs commented 2 years ago

nuxt 编译之后会有一个目录

image

看一下 nuxt-loading.vue 文件

<script>
export default {
  name: 'NuxtLoading',
  data () {
    return {
      percent: 0,
      show: false,
      canSucceed: true,
      reversed: false,
      skipTimerCount: 0,
      rtl: false,
      throttle: 200,
      duration: 5000,
      continuous: false
    }
  },
  computed: {
    left () {
      if (!this.continuous && !this.rtl) {
        return false
      }
      return this.rtl
        ? (this.reversed ? '0px' : 'auto')
        : (!this.reversed ? '0px' : 'auto')
    }
  },
  beforeDestroy () {
    this.clear()
  },
  methods: {
    clear () {
      clearInterval(this._timer)
      clearTimeout(this._throttle)
      this._timer = null
    },
    start () {
      this.clear()
      this.percent = 0
      this.reversed = false
      this.skipTimerCount = 0
      this.canSucceed = true

      if (this.throttle) {
        this._throttle = setTimeout(() => this.startTimer(), this.throttle)
      } else {
        this.startTimer()
      }
      return this
    },
    set (num) {
      this.show = true
      this.canSucceed = true
      this.percent = Math.min(100, Math.max(0, Math.floor(num)))
      return this
    },
    get () {
      return this.percent
    },
    increase (num) {
      this.percent = Math.min(100, Math.floor(this.percent + num))
      return this
    },
    decrease (num) {
      this.percent = Math.max(0, Math.floor(this.percent - num))
      return this
    },
    pause () {
      clearInterval(this._timer)
      return this
    },
    resume () {
      this.startTimer()
      return this
    },
    finish () {
      this.percent = this.reversed ? 0 : 100
      this.hide()
      return this
    },
    hide () {
      this.clear()
      setTimeout(() => {
        this.show = false
        this.$nextTick(() => {
          this.percent = 0
          this.reversed = false
        })
      }, 500)
      return this
    },
    fail (error) {
      this.canSucceed = false
      return this
    },
    startTimer () {
      if (!this.show) {
        this.show = true
      }
      if (typeof this._cut === 'undefined') {
        this._cut = 10000 / Math.floor(this.duration)
      }

      this._timer = setInterval(() => {
        /**
         * When reversing direction skip one timers
         * so 0, 100 are displayed for two iterations
         * also disable css width transitioning
         * which otherwise interferes and shows
         * a jojo effect
         */
        if (this.skipTimerCount > 0) {
          this.skipTimerCount--
          return
        }

        if (this.reversed) {
          this.decrease(this._cut)
        } else {
          this.increase(this._cut)
        }

        if (this.continuous) {
          if (this.percent >= 100) {
            this.skipTimerCount = 1

            this.reversed = !this.reversed
          } else if (this.percent <= 0) {
            this.skipTimerCount = 1

            this.reversed = !this.reversed
          }
        }
      }, 100)
    }
  },
  render (h) {
    let el = h(false)
    if (this.show) {
      el = h('div', {
        staticClass: 'nuxt-progress',
        class: {
          'nuxt-progress-notransition': this.skipTimerCount > 0,
          'nuxt-progress-failed': !this.canSucceed
        },
        style: {
          width: this.percent + '%',
          left: this.left
        }
      })
    }
    return el
  }
}
</script>

<style>
.nuxt-progress {
  position: fixed;
  top: 0px;
  left: 0px;
  right: 0px;
  height: 2px;
  width: 0%;
  opacity: 1;
  transition: width 0.1s, opacity 0.4s;
  background-color: #e74b4b;
  z-index: 999999;
}

.nuxt-progress.nuxt-progress-notransition {
  transition: none;
}

.nuxt-progress-failed {
  background-color: red;
}
</style>

loading 配置

https://www.nuxtjs.cn/api/pages-loading

dailynodejs commented 2 years ago

源码部分:

https://github.com/nuxt/nuxt.js/blob/2.x/packages/vue-app/template/components/nuxt-loading.vue

Echo-mz commented 2 years ago

axios.js

文件地址.nuxt/axios.js当前文件

import Axios from 'axios'

image

  1. this.$loading.start()路由更新(即浏览器地址变化)时调用, 请在该方法内显示组件
  2. this.$loading.finish()路由更新完毕(即asyncData方法调用完成且页面加载完)时调用,请在该方法内隐藏组件
  3. fail(error)路由更新失败时调用(如asyncData方法返回异常
  4. increase(num)页面加载过程中调用, num 是小于 100 的整数。

code2

Echo-mz commented 2 years ago

this.$loading.start()执行步骤

  1. .nuxt/index.js中抛出createApp方法
async function createApp(ssrContext, config = {}) {
}

export { createApp, NuxtError }
  1. .nuxt/client.js文件中引入并创建并挂载

    // Create and mount App
    createApp(null, NUXT.config).then(mountApp).catch(errorHandler)
    async function mountApp (__app) {}
  2. mountApp中执行路由钩子调用loadAsyncComponents和render

    //Add beforeEach router hooks
    router.beforeEach(loadAsyncComponents.bind(_app))
    router.beforeEach(render.bind(_app))
Echo-mz commented 2 years ago

client.js中执行router.beforeEach(loadAsyncComponents.bind(_app))

文件地址.nuxt/client.js

  1. 首先判断是否存在app.nuxt.err或路由to.name与from.name不一致,由变量this._routeChanged接收
  2. 排除非上述情况,由from.path 与to.path不一致,由变量this._paramChanged接收
  3. !this._routeChanged且!this._paramChanged的情况下,如果from.fullPath !== to.fullPath,则由变量this._paramChanged接收
  4. 在this._paramChanged改变的情况下,this._diffQuery接收getQueryDiff(to.query, from.query)的结果。
  5. this.$loading.manual为自定义参数,其默认值为false
    // Check disabled page loading
    this.$loading.manual = Component.options.loading === false
    • 改变会执行$loading.start()方法开启loading client0 client1
Echo-mz commented 2 years ago

client.js中执行router.beforeEach(loadAsyncComponents.bind(_app))之后执行router.beforeEach(render.bind(_app))

文件地址.nuxt/client.js

  1. 判定路由切换执行this.$loading.start()开启loading之后,执行render方法,以下条件均不满足,直接执行next()
    if (this._routeChanged === false && this._paramChanged === false && this._queryChanged === false) {
    return next()
    }

    2.定义 nextCalled判断是否路由重定向,其默认值为false

    // nextCalled is true when redirected
    let nextCalled = false
    const _next = (path) => {
    if (from.path === path.path && this.$loading.finish) {
      this.$loading.finish()
    }
    .....
    }

    上述条件成立则执行this.$loading.finish(),再执行next(),渲染结束

    // If not redirected
    if (!nextCalled) {
      if (this.$loading.finish && !this.$loading.manual) {
        this.$loading.finish()
      }
      next()
    }
dailynodejs commented 2 years ago

如何与 vue-router 关联

.nuxt/router.js

import Router from 'vue-router'

export function createRouter (ssrContext, config) {

}

image

重写了 push

image

dailynodejs commented 2 years ago

.nuxt/index.js

async function createApp(ssrContext, config = {}) {
}

image

dailynodejs commented 2 years ago

如何处理 store

.nuxt/index.js

import { createStore } from './store.js'

image

.nuxt/store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const createStore = store instanceof Function ? store : () => {
}

image

dailynodejs commented 2 years ago

mountApp 核心

加载 layout