zoukankan      html  css  js  c++  java
  • 由simpleMDE 看Nuxt的cdn模块协作

    Nuxt 加载外部cdn模块

    Vue加载外部cdn模块的时候,是通过配置vue.config.jsconfigureWebpack.externals,从而告诉webpack跳过模块依赖

    // vue.config.js
    {
        configureWebpack: {
            externals: {
                axios: 'axios',
                vue: 'Vue',
                loadsh: '_',
            }
        }
    }
    

    Nuxt 加载cdn模块比较直接,在nuxt.config.js配置head即可全局加载

    如何在 Nuxt.js 应用中使用外部资源?

    // nuxt.config.js
    {
        head: {
            script: [
                {
                    src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'
                }
            ],
            link: [
                {
                    rel: 'stylesheet',
                    href: 'https://fonts.googleapis.com/css?family=Roboto'
                }
            ]
        }
    }
    

    或者直接在page组件中配置head(),可实现局部加载

    // pages/example.vue
    export default {
        head: () => {
            return {
                //...
            }
        }
    }
    

    封装sampleMDE 组件

    封装cdn库为独立组件,并实现按需加载

    目前暂时没有找到解决办法

    1. npm安装,封装为组件。实际使用时按需加载chunk。
      • 缺点: 无法使用公共cdn
    2. 全局配置nuxt的head,引入cdn,再将sampleMDE封装为独立组件
      • 缺点: 无法按需加载cdn

    局部加载引入sampleMDE的cdn

    考虑在page视图里通过局部加载cdn

    // pages/a.vue
    <template>
        <textarea ref="editor"></textarea>
    </template>
    <script>
    export default {
        mounted() {
            let editor = new SimpleMDE({ element: this.$refs.editor })
        },
        // spa模式下,由其他页面路由跳转,将进行异步加载,无法预知什么时候加载完成(nuxt是否有hook?)
        head: () => {
            return {
                script: [
                    {
                        src: 'https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js',
                    },
                ],
                link: [
                    {
                        rel: 'stylesheet',
                        href: 'https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css',
                    },
                ],
            }
        },
    }
    </script>
    
    // pages/b.vue
    <template>
        <nuxt-link :to="/a">Go to pageA</nuxt-link>
    </template>
    

    spa模式下我们直接访问/aSampleMDE渲染成功。但是如果我们一开始访问的是/b,然后通过route跳转到/a,会发现SampleMDE不存在。稍加思索可以想到

    • 直接访问/a的时候,nuxt发现page组件里的head,于是等待cdn模块加载完成后触发mounted
    • 先访问/b,此时页面未加载head中的cdn
    • route跳转到/a时,nuxt开始异步加载head中的cdn,同时触发mounted,因此SimpleMDE is not defined

    找到原因之后,那就想办法在cdn模块加载完成之后再进行editor的初始化:

    1. 上策 反馈nuxt,并期望它暴露head中的资源加载完成的钩子,然后在钩子函数中优雅的进行初始化
    2. 中策 在vue-router的钩子中自行实现loadScript的钩子
    3. 下策 定时检查引入的cdn是否加载完成

    所以我立马选择了下策,顺便演练了一下如何递归的调用一个Promise

    // pages/a.vue
    mounted() {
        this.$nextTick(() => {
            this.detectMDE().then(() => {
                this.simpleMDE = new SimpleMDE({ element: this.$refs.editor })
            })
        })
    },
    methods: {
        detectMDE() {
            console.log('detecting...')
            return new Promise((resolve) => {
                setTimeout(() => {
                    if (typeof SimpleMDE === 'undefined') {
                        resolve(false)
                    } else {
                        resolve(true)
                    }
                }, 100)
            }).then((r) => {
                // 演示如何递归调用一个promise
                return r ? true : this.detectMDE()
            })
        },
    },
    

    然后证明下策是行得通的!

    更优雅的交互?

    没错,在等待simpleMDE加载完成的过程中,不妨应用一下骨架

    // pages/a.vue
    <template>
      <div>
        <textarea v-if="loadMDE" ref="editor" style=" 100%; height: calc(100% - 40px)"></textarea>
        <v-skeleton-loader v-else class="mx-auto" max-width="100%" type="card-heading, image"> </v-skeleton-loader>
      </div>
    </template>
    
    // pages/a.vue
    mounted() {
        this.$nextTick(() => {
            this.detectMDE().then(() => {
                this.loadMDE = true
                this.simpleMDE = new SimpleMDE({ element: this.$refs.editor })
            })
        })
    },
    

    然后很不幸,这个时候报错this.$refs.editor is undefined
    难道ref不能和v-if一起使用?
    通过搜索引擎发现网上类似的问题还比较多,refv-if,v-show,v-else一起使用时通常获取不到dom。

    其实这个问题很简单,让我们回忆一下vue的响应式原理

    可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。

    例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。

    v-if的状态改变时,dom还未被更新,因此ref引用为空。

    网上有用watch监听来处理,找到原因之后发现完全没必要。我们只需要再套一层$nextTick

    mounted() {
        this.$nextTick(() => {
            this.detectMDE().then(() => {
                this.loadMDE = true
                // dom将在下一个事件循环中更新,因此此时 ref 为undefined
                console.log(this.$refs.editor) // undefined
                this.$nextTick(() => {
                    // eslint-disable-next-line no-undef
                    this.simpleMDE = new SimpleMDE({ element: this.$refs.editor })
                })
            })
        })
    },
    
    

    小结

    由于莫名其妙的坚持按需cdn加载,以及剑走偏锋的脑回路检测cdn模块加载完成,倒是误打误撞解锁/复习了3个技能点

    1. Nuxt 的page组件的head加载漏洞 spa模式下,路由跳转导致外部cdn资源异步加载
    2. Promise对象的递归调用
    3. Vue的响应式原理,修改状态与更新dom的时机
  • 相关阅读:
    jQuery实现 自动滚屏操作
    jQuery实现全选、全不选以及反选操作
    读曾国藩
    把时间当作朋友 之感知时间
    把时间当作朋友4未知永远存在
    Android N 设置中语言列表介绍
    如何编译ICU资源
    idea常用快捷键
    shell 笔记
    Json笔记
  • 原文地址:https://www.cnblogs.com/dapianzi/p/13611513.html
Copyright © 2011-2022 走看看