添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
爱喝酒的火车  ·  Element UI DatePicker ...·  1 月前    · 
玩滑板的键盘  ·  MQ ...·  1 年前    · 
犯傻的韭菜  ·  【MAC版】Android ADB ...·  1 年前    · 
低调的绿茶  ·  iOS ...·  1 年前    · 

我们在初学Vue时,第一个上手的例子基本都是 new Vue({el:’#app’}),但是为什么Vue实例只能挂载在一个div上呢?同样的当我们开始写第一个Vue页面的时候,我们试图在template标签下写两个div,Vue提醒我们只能写一个元素,但是为什么只能有一个元素呢?很多时候我们都已经习以为常,但是却说不上来为什么。

笔者入坑Vue也有一段时间了,对Vue也算了解,Vuex、Vue-Router也用了不少;但是前几天一看到这个面试问题却感觉一下子回答不上了,想来每次写代码也都是拿来就用,也没有仔细的思考过里面的原因;每每报错了就换一种写法,能用就行,仅此而已。

这个问题要从两个方面来说:

  • new Vue({el:’#app’})
  • 单文件组件中,template下的元素div
  • Vue实例

    当我们实例化Vue的时候,填写一个el选项,来指定我们的SPA入口:

    1
    2
    3
    4
    let vm = new Vue({
    el:'#app',
    data:{ msg: 'Hi boy' }
    })

    谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

    1
    2
    3
    <body>
    <div id='app'>{{msg}}</div>
    </body>

    如果我们把代码改造一下,变成两个入口。

    谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

    1
    2
    3
    4
    let vm = new Vue({
    el:'.app',
    data:{ msg: 'Hi boy' }
    })
    1
    2
    3
    4
    <body>
    <div class='app'>{{msg}}</div>
    <div class='app'>{{msg}}</div>
    </body>

    这时候会发现只有第一个div被渲染出来,而第二个div还是原封不动。我们简单来看一下Vue的源码是如何实现的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Vue.prototype.$mount = function (
    el?: string | Element,
    hydrating?: boolean
    ): Component {
    el = el && query(el)
    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
    `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
    }
    //以下省略无关代码
    //...
    }

    可以看到挂载函数传了一个el参数,这个参数可以是string类型,也可以是一个element元素,也就是dom节点。最重要的是 el = el && query(el) 这一行代码,那就继续看一下query函数是做什么的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * Query an element selector if it's not an element already.
    */
    export function query (el: string | Element): Element {
    if (typeof el === 'string') {
    const selected = document.querySelector(el)
    if (!selected) {
    process.env.NODE_ENV !== 'production' && warn(
    'Cannot find element: ' + el
    )
    return document.createElement('div')
    }
    return selected
    } else {
    return el
    }
    }

    首先query函数判断是否是string类型,如果是string类型,就通过 querySelector 函数获取页面中的元素,但是querySelector仅仅返回匹配指定选择器的第一个元素,所以这就解释了为什么第二个div会原封不动。

    Vue其实并不知道哪一个才是我们的入口,因为对于一个入口来讲,这个入口就是一个 Vue类 ,Vue需要把这个入口里面的所有东西拿来渲染、处理,最后再重新插入到dom中。如果同时设置了多个入口,那么vue就不知道哪一个才是这个

    单文件组件

    当我们在vue-cli脚手架搭建的vue开发环境下使用单文件组件时,一般会这么写:

    1
    2
    3
    4
    5
    <template>
    <div class="box">
    这里是页面内容
    </div>
    </template>

    谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

    如果我们尝试在template标签下写两个div,那么编辑器会提示我们 The template root requires exactly one element 。那这里为什么template下也必须有且只能有一个div呢?

    这里我们要先看一看template这个标签,这个标签是HTML5出来的新标签,它有三个特性:

  • 隐藏性:该标签不会显示在页面的任何地方,即便里面有多少内容,它永远都是隐藏的状态;
  • 任意性:该标签可以写在页面的任何地方,甚至是head、body、sciprt标签内;
  • 无效性:该标签里的任何HTML内容都是无效的,不会起任何作用;
  • 但是我们可以通过innerHTML来获取到里面的内容。

    知道了这个,我们再来看.vue的单文件组件。其实本质上,一个单文件组件会被各种各样的loader处理成为.js文件(因为当你import一个单文件组件并打印出来的时候,是一个vue实例),通过template的任意性我们知道,template包裹的HTML可以写在任何地方,那么对于一个.vue来讲,这个template里面的内容就是会被vue处理为虚拟dom并渲染的内容,导致结果又回到了开始 :既然一个.vue单文件组件是一个vue实例,那么这个实例的入口在哪里?

    如果在template下有多个div,那么该如何指定这个vue实例的根入口?
    为了让组件能够正常的生成一个vue实例,那么这个div会被自然的处理成程序的入口。

    通过这个‘根节点’,来递归遍历整个vue‘树’下的所有节点,并处理为vdom,最后再渲染成真正的HTML,插入在正确的位置。