JS如何实现一个单文件组件

概述

前端开发人员只要了解过vue.js框架可能都知道单文件组件。vue.js中的单文件组件允许在一个文件中定义一个组件的所有内容。这是一个非常有用的解决方案,在浏览器网页中已经开始提倡这种机制。但是不幸的是,这个概念自从2017年8月被提出以来,到现在没有任何进展,像是已经要消亡了一样。然而,深入研究这个主题并试着使用现有的技术来实现单文件组件是很有趣的,值得尝试。

单文件组件

知道“渐进增强”这个概念的前端开发人员想必也听说过“分层”这个概念。在组件中,同样有这样的概念。事实上,每个组件至少有3层,甚至多余3层:内容/模板,表现和行为。又或者保守的说,每个组件会被分成至少3个文件,比如:一个按钮组件的文件结构可能是下面这样的:

Button/
| -- Button.html
| -- Button.css
| -- Button.js

采用这种方式分层相当于技术的分离(内容/模板:使用html,表现:使用css,行为:使用JavaScript)。如果没有采用任何构建工具打包,这意味着浏览器需要获取这3个文件。因此,一个想法是:迫切需要一种分离组件代码而不分离技术(文件)的技术来解决这个问题。这就是这篇文章要讨论的主题—单文件组件。

总的来说,我对“技术分层”持怀疑态度。它来自一个事实,就是组件分层常常因为绕不开“技术分层”而被放弃,而这两者是完全分离的。

回到主题,用单文件组件实现按钮可能是这样的:

<template>
  <!-- Button.html contents go here. -->
</template>

<style>
  /* Button.css contents go here. */
</style>

<script>
  // Button.js contents go here.
</script>

可以看到这个单文件组件很像最初前端开发中的html文档,它有自己的style标签和script标签,只是表现层使用一个template标签。由于使用了简单的方式,得到一个强大的分层组件(内容/模板:<template>,表现:<style>,行为:<script>),而不需要使用3个分离的文件。

基本概念

首先,我们创建一个全局函数loadComponent()来加载组件。

window.loadComponent = (function() {
  function loadComponent( URL ) {}
  return loadComponent;
}());

这里使用了JavaScript模块模式。它允许定义所有必要的辅助函数,但是只向外公开loadComponent()函数。当然,现在这个函数还是空的。

后面,我们要创建一个<hello-world>组件,显式下面的内容。

Hello, world! My name is<given name>.

另外,点击这个组件,弹出一个信息:

Don't touch me!

组件代码保存为一个文件HelloWorld.wc(这里.wc代表WebComponent)。初始代码如下:

<template>
  <div class="hello">
    <p>Hello, world! My name is <slot></slot>.</p>
  </div>
</template>
<style>
  div {
    background: red;
    border-radius: 30px;
    padding: 20px;
    font-size: 20px;
    text-align: center;
    width: 300px;
    margin: 0 auto;
  }
</style>
<script></script>

目前,没有给组件添加任何行为,只是定义了模板和样式。模板中,可以使用常见的html标签,比如<div>,另外,template中出现了<slot>元素表明组件将实现影子DOM。并且默认情况下这个DOM自身所有样式和模板都隐藏在这个DOM中。

组件在网页中使用的方式非常简单。

<hello-world>Comandeer</hello-world>
<script src="loader.js"></script>
<script>
  loadComponent( 'HelloWorld.wc' );
</script>

可以像标准的自定义元素一样使用组件。唯一的区别是需要在使用loadComponent()方法前先加载它(这个方法放在loader.js中)。loadComponent()方法完成所有繁重的工作,比如获取组件,并通过customElements.define()注册它。

在了解了所有概念之后,是时候动手实践了。

简单的loader

如果想从外部文件中加载文件,需要使用万能的ajax。但是现在已经是2020年了,在大部分浏览器中,你可以大胆的使用FetchAPI。

function loadComponent( URL ) {
  return fetch( URL );
}

但是,这只是获取到文件,没有对文件做任何处理,接下来要做的是把ajax返回内容转换成text文本,如下:

function loadComponent( URL ) {
  return fetch( URL ).then( ( response ) => {
    return response.text();
  } );
}

由于loadComponent()函数返回的是fetch函数的执行结果,所以它是一个Promise对象。可以在then方法中检查文件(HelloWorld.wc)是不是真的被加载,还有,是不是被转成文本了:

loadComponent('HelloWorld.wc').then((component) => {
    console.log(component);
});

运行结果如下:

chrome浏览器下,使用console()方法,我们看到HelloWorld.wc的内容被转成了text并输出,所以貌似行得通!

解析组件内容

然而,仅仅把文本输出并没有达到我们的目的。最终要把它转换成DOM用于显示,并能和用户真正交互起来。

在浏览器环境中有一个非常实用的类DOMParser,可以实用它创建一个DOM解析器。实例化一个DOMParser类得到一个对象,实用这个对象可以将组件文本转换成DOM:

window.loadComponent = (function () {
    function loadComponent(URL) {
        return fetch(URL).then((response) => {
            return response.text();
        }).then((html) => {
            const parser = new DOMParser(); // 1
            return parser.parseFromString(html, 'text/html'); // 2
        });
    }
    return loadComponent;
}());

首先,创建一个DOMParser实例parser(1),然后用这个实例将组件内容转化成DOM(2)。值得注意的是,这里实用的是HTML模式(‘text/html')。如果希望代码更好的符合JSX标准或者原始的Vue.js组件,可以实用XML模式(‘text/XML')。但是,在这种情况下,需要更改组件本身的结构(例如,添加可以容纳其他元素的主元素)。

这是再输出loadComponent()函数的返回结果,就是一个DOM树了。

在chrome浏览器下,console.log()输出解析后的HelloWorld.wc文件,是一个DOM树。

注意,parser.parseFromString方法自动给组件添加上<html>,<head>,<body>标签元素。这是由HTML解析器的工作原理造成的。在HTML LS规范中详细描述了构建DOM树的算法。这篇文章很长,要花点时间阅读,可以简单地理解为解析器会默认把所有内容放在<head>元素中,直至遇到一个只能放在<body>标签内的DOM元素。所以,组件代码中所有的元素(<element>,<style>,<script>)都允许放在<head>中。如果在<template>外层包一个<p>元素,那么解析器将把它放在<body>中。

还有一个问题,组件被解析之后并没有<!DOCTYPE html>声明,所以这得到的是一个非正常的html文档,因此浏览器会使用一种被成为怪异模式的方式来渲染这种html文档。所幸的是,在这里它不会带来任何负面作用,因为这里只使用DOM解析器将组件分割成适当的部分。

有了DOM树之后,可以只截取我们需要的部分。

return fetch(URL).then((response) => {
    return response.text();
}).then((html) => {
    const parser = new DOMParser();
    const document = parser.parseFromString(html, 'text/html');
    const head = document.head;
    const template = head.querySelector('template');
    const style = head.querySelector('style');
    const script = head.querySelector('script');

    return {
        template,
        style,
        script
    };
}); 

最后整理一下代码,loadComponent方法如下。

window.loadComponent = (function () {
    function fetchAndParse(URL) {
        return fetch(URL).then((response) => {
            return response.text();
        }).then((html) => {
            const parser = new DOMParser();
            const document = parser.parseFromString(html, 'text/html');
            const head = document.head;
            const template = head.querySelector('template');
            const style = head.querySelector('style');
            const script = head.querySelector('script');

            return {
                template,
                style,
                script
            };
        });
    }

    function loadComponent(URL) {
        return fetchAndParse(URL);
    }

    return loadComponent;
}()); 

从外部文件中获取组件代码的方式不止FetchAPI这一种,XMLHttpRequest有一个专用的文档模式,允许您省略整个解析步骤。但是XMLHttpRequest返回的不是一个Promise,这个需要自己包装。

注册组件

现在有了组件的层,可以创建registerComponent()方法来注册新的自定义组件了。

window.loadComponent = (function () {
    function fetchAndParse(URL) {
        […]
    }
    function registerComponent() {
    }
    function loadComponent(URL) {
        return fetchAndParse(URL).then(registerComponent);
    }
    return loadComponent;
}()); 

要注意的是,自定义组件必须是一个继承自HTMLElement的类。此外,每个组件都将使用用于存储样式和模板内容的影子DOM。所以每个引用这个组件的场合下,这个组件都有相同的样式。方法如下:

function registerComponent({template, style, script}) {
    class UnityComponent extends HTMLElement {
        connectedCallback() {
            this._upcast();
        }

        _upcast() {
            const shadow = this.attachShadow({mode: 'open'});
            shadow.appendChild(style.cloneNode(true));
            shadow.appendChild(document.importNode(template.content, true));
        }
    }
} 

应该在registerComponent()方法内创建UnityComponent类,因为这个类要使用传入registerComponent()的参数。这个类会使用一种稍加修改的机制来实现影子DOM,在这篇关于影子DOM的文章(波兰文)中我有详细介绍。

关于注册组件,现在只剩下一件事,给单文件组件一个名字,并把它加到当前页面的DOM中。

function registerComponent( { template, style, script } ) {
  class UnityComponent extends HTMLElement {
    [...]
  }

  return customElements.define( 'hello-world', UnityComponent );
} 

现在可以打开看一下了,如下:

在chrome中,这个按钮组件中,有一个红色矩形,文字内容:Hello, world! My name is Comandeer。

获取脚本内容

现在一个简单的按钮组件已经实现。现在要实现最困难的部分,添加行为层,并自定义按钮内内容。在上面的步骤中,我们应该使用到按钮的地方传入内容,而不是在组件代码中硬编码按钮内的文字内容。同理还要处理组件内绑定的事件监听,这里我们使用类似Vue.js的约定,如下:

<template>
  […]
</template>

<style>
  […]
</style>

<script>
  export default { // 1
    name: 'hello-world', // 2
    onClick() { // 3
      alert( `Don't touch me!` );
    }
  }
</script> 

可以假设组件内<script>标签中的内容是一个JavaScript模块,它导出内容(1)。模块导出的对象包含组件的名称(2)和一个已“on..”开头的事件监听方法(3)。

这看上去很工整,没有内容暴露在模块外部(因为JavaScript中modules并不是在全局作用域中)。这里有一个问题:没有一个标准可以处理从内部模块导出的对象(这些代码直接定义在HTML文档中)。import语句会假设获取到一个模块标识,根据这个标识导入。最常见的是从一个包含代码的文件的URL路径。组件不是一个js文件,没有这样一个标识,内部的模块是没有这样的标识的。

在缴械投降之前,可以使用跟一个超级脏的hack。最少有2中方式让浏览器像处理一个文件一样处理一段文本:DataURI和ObjectURI。也有一些建议是使用ServiceWorker。但是在这里显得有点大材小用。

DataURI和ObjectURI

DataURI是一个古老,原始的方法。它的基础是将文件内容转换成URL,去掉不必要的空格,然后使用Base64对所有内容进行编码。假设有一个JavaScript文件,内容如下:

export default true; 

转换成DataURI如下:

data:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQgdHJ1ZTs= 

然后,可以像引入一个文件一样引入这个URI:

import test from 'data:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQgdHJ1ZTs=';
console.log( test ); 

DataURI这种方式的一个明显的缺点是随着JavaScript文件内容增多,这个URL的长度会随之变得很长。还有把二进制数据放在DataURI非常困难。

所以,现在有一种新的ObjectURI。它是从几种标准中衍生出来的,包括FileAPI和HTML5中的<video>和<audio>标签。ObjectURI的目的很简单,从给定的二进制数据创建一个“伪文件”,在当前上下文中给出一个唯一URI。简单点说,就是在内存中创建一个有唯一名称的文件。ObjectURI有DataURI所有的优点(一种创建"文件"的方法),而没它的缺点(即使文件有100M也没关系)。

对象uri通常是从多媒体流(例如在<video>或<audio>上下文中)或通过输入[type=file]和拖放机制发送的文件创建的。还可以使用File,Blob这两个类手动创建。在本例中,我们使用Bolb,先把内容放在模块中,然后转换成ObjectURI:

const myJSFile = new Blob( [ 'export default true;' ], { type: 'application/javascript' } );
const myJSURL = URL.createObjectURL( myJSFile );

console.log( myJSURL ); // blob:https://blog.comandeer.pl/8e8fbd73-5505-470d-a797-dfb06ca71333 

动态导入

不过,还有一个问题:import语句不接受变量作为模块标识符。这意味着除了使用该方法将模块转换成“文件”之外,还是无法导入它。还是无解的吗?

也不尽然。这个问题在很久之前就被提出了,使用动态导入机制可以解决。它是ES2020标准的一部分,并且在Firefox,Safari和Node.js13.x中已经被实现。使用一个变量作为要动态导入的模块的标示符已经不再是一个难题了:

const myJSFile = new Blob( [ 'export default true;' ], { type: 'application/javascript' } );
const myJSURL = URL.createObjectURL( myJSFile );

import( myJSURL ).then( ( module ) => {
  console.log( module.default ); // true
}); 

从上面代码中可以看到,import()命令可以像方法一样使用,它返回一个Promise对象,then方法中得到模块对象。在它的default属性中包含了模块中定义的所有导出对象。

实现

现在我们已经知道思路了,现在可以着手实现它。在添加一个工具方法,getSetting()。在registerComponents()方法之前调用它,进而从脚本代码中获取所有信息。

function getSettings( { template, style, script } ) {
  return {
    template,
    style,
    script
  };
}
[...]
function loadComponent( URL ) {
  return fetchAndParse( URL ).then( getSettings ).then( registerComponent );
} 

现在,这个方法返回了所有传入的参数。按照上面介绍的逻辑,将脚本代码转换成ObjectURI:

const jsFile = new Blob( [ script.textContent ], { type: 'application/javascript' } );
const jsURL = URL.createObjectURL( jsFile ); 

下一步,使用import加载模块,返回模板,样式和组件的名称:

return import( jsURL ).then( ( module ) => {
  return {
    name: module.default.name,
    template,
    style
  }
} ); 

由于这个原因,registerComponent()仍然获得3个参数,但是现在它获取的是name,而不是脚本。正确的代码如下:

function registerComponent( { template, style, name } ) {
  class UnityComponent extends HTMLElement {
    [...]
  }

  return customElements.define( name, UnityComponent );
} 

行为层

组件还剩下最后一层:行为层,用来处理事件。现在我们只是在getSettings()方法中获取到了组件的名字,还要获取事件监听。可以使用Object.entrie()方法获取。在getSettings()方法中添加合适的代码:

function getSettings( { template, style, script } ) {
  [...]

  function getListeners( settings ) { // 1
    const listeners = {};

    Object.entries( settings ).forEach( ( [ setting, value ] ) => { // 3
      if ( setting.startsWith( 'on' ) ) { // 4
        listeners[ setting[ 2 ].toLowerCase() + setting.substr( 3 ) ] = value; // 5
      }
    } );

    return listeners;
  }

  return import( jsURL ).then( ( module ) => {
    const listeners = getListeners( module.default ); // 2

    return {
      name: module.default.name,
      listeners, // 6
      template,
      style
    }
  } );
} 

现在方法变得有点复杂了。添加了一个新的函数getListeners()(1) ,将模块的输出传入这个参数中。

然后使用Object.entries()(3)方法遍历导出的模块。如果当前属性以“on”(4)开头,说明是一个监听函数,将这个节点的值(监听函数)添加到listeners对象中去,使用setting[2].toLowerCase()+setting.substr(3)(5)得到键值。

键值是通过去掉开头的“on”,并将后面的“Click”首字母转换成小写组成的(就是从onClick得到click作为建值)。然后传入isteners对象(6)。

可以使用[].reduce()方法代替[].forEach()方法,这样可以省略掉listeners这个变量,如下:

function getListeners( settings ) {
  return Object.entries( settings ).reduce( ( listeners, [ setting, value ] ) => {
    if ( setting.startsWith( 'on' ) ) {
      listeners[ setting[ 2 ].toLowerCase() + setting.substr( 3 ) ] = value;
    }

    return listeners;
  }, {} );
} 

现在,可以将监听绑定在组件内部的类中:

function registerComponent( { template, style, name, listeners } ) { // 1
  class UnityComponent extends HTMLElement {
    connectedCallback() {
      this._upcast();
      this._attachListeners(); // 2
    }
    [...]
    _attachListeners() {
      Object.entries( listeners ).forEach( ( [ event, listener ] ) => { // 3
        this.addEventListener( event, listener, false ); // 4
      } );
    }
  }
  return customElements.define( name, UnityComponent );
} 

在listeners方法(1)上增加了一个参数,并且在class中添加了一个新方法_attachListeners()(2)。在这里可以再次使用Object.entries()来遍历listeners(3),并把他们绑定到element(4)。

最后,点击组件可以弹出“Don't touch me!”,如下

兼容性问题及其他

可以看到,为了实现这个单文件组件,大部分工作围绕如何支持基本的Form。很多部分使用了脏hacks(使用ObjectURI来加载ES中的模块,没有浏览器的支持,这种技术没有什么意义)。还好,所有的技术在主流浏览器下运行正常,包括:Chrome,Firefox和Safari。

尽管如此,创建一个这样的项目会接触到很多浏览器技术和最新的web标准,也是一件很有趣的事情。

以上就是JS如何实现一个单文件组件的详细内容,更多关于JS实现一个单文件组件的资料请关注我们其它相关文章!

(0)

相关推荐

  • JavaScript 实现拖拽效果组件功能(兼容移动端)

    页面元素拖拽是一种非常实用的前端效果,基于元素拖拽可以实现很多不同的功能,增加客户端许多操作的便捷性,大大提高用户体验.日常生活中大家多多少少都见过这种效果,所以就不废话了,直接开干吧. 预期目标 实现一个 Class 类,通过该 Class,可以将任意 DOM 元素(比如 div)一键变为可拖拽状态,也可以恢复成原来的状态,例如这样: <!DOCTYPE html> <html lang="en"> <head> <meta charset=

  • Vuejs 单文件组件实例详解

    在之前的实例中,我们都是通过 Vue.component 或者 components 属性的方式来定义组件,这种方式在很多中小规模的项目中还好,但在复杂的项目中,下面这些缺点就非常明显了: 字符串模板:缺乏高亮,书写麻烦,特别是 HTML 多行的时候,虽然可以将模板写在 html 文件中,但是侵入性太强,不利于组件解耦分离. 不支持CSS:意味着当 HTML 和 JavaScript 组件化时,CSS明显被遗漏了 没有构建步骤:限制只能使用 HTML 和 ES5 JavaScript,而不能使用

  • 详解Vue.js3.0 组件是如何渲染为DOM的

    本文主要是讲述 Vue.js 3.0 中一个组件是如何转变为页面中真实 DOM 节点的.对于任何一个基于 Vue.js 的应用来说,一切的故事都要从应用初始化「根组件(通常会命名为 APP)挂载到 HTML 页面 DOM 节点(根组件容器)上」说起.所以,我们可以从应用的根组件为切入点. 主线思路:聚焦于一个组件是如何转变为 DOM 的. 辅助思路: 涉及到源代码的地方,需要明确标记源码所在文件,同时将 TS 简化为 JS 以便于直观理解 思路每前进一步要能够得出结论 尽量总结归纳出流程图 应用

  • 详解在WebStorm中添加Vue.js单文件组件的高亮及语法支持

    本文介绍了详解在WebStorm中添加Vue.js单文件组件的高亮及语法支持,分享给大家,具体如下: 一个小遗憾 能来看这篇文章的想必不用我来介绍vue是什么了.先让我们膜拜大神!vue项目的创建者尤大写了个sublime下语法高亮的插件,有人问他how about webstorm support?他是这么回答的.默哀一分钟. 添加高亮和语法支持 这个我是通过插件来实现的.网上目前有两个插件: 插件1:https://github.com/henjue/vue-for-idea 插件2:htt

  • vue.js封装switch开关组件的操作

    我的项目本来用的element,但是switch开关不符合设计要求,于是自己封装了一个switch组件,并且实现了switch开关的双向数据绑定 <template> <label role="checkbox" :class="['switch', { toggled }]"> <input type="checkbox" class="switch-input" @change="t

  • Vuejs实现带样式的单文件组件新方法

    本文实例为大家分享了Vuejs实现单文件组件的方法,供大家参考,具体内容如下 代码如下: example.html <script src="vue.js"></script> <div id="example"> <h3>Vue component<h3> <counter></counter> <counter></counter> </div>

  • Vue.js桌面端自定义滚动条组件之美化滚动条VScroll

    前言 前段时间有给大家分享一个vue桌面端弹框组件,今天再分享最近开发的一个vue pc端自定义滚动条组件. vscroll 一款基于vue2.x开发的网页端轻量级超小巧自定义美化滚动条组件.支持是否原生滚动条.鼠标移出是否自动隐藏.自定义滚动条尺寸及颜色等功能. 组件在设计开发之初借鉴了 el-scrollbar 及 vuebar 等组件设计思想. 通过简单的标签写法<v-scroll>...</v-scroll> 即可快速生成一个漂亮的替换原生滚动条. 参数配置 props:

  • vue.js单文件组件中非父子组件的传值实例

    最近在研究vue.js,总体来说还算可以,但是在web开发群里有一些人问在单文件组件开发模式中非父子组件如何传值的问题,今天在这里讲讲,希望对大家有所帮助! 在官网api中的这段讲解很少,也很模糊:官网中说明如下: 非父子组件通信: 有时候两个组件也需要通信 (非父子关系).在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线: var bus = new Vue(): // 触发组件 A 中的事件 bus.$emit('id-selected', 1) // 在组件 B 创建的钩子中

  • vuejs 单文件组件.vue 文件的使用

    vuejs 自定义了一种.vue文件,可以把html, css, js 写到一个文件中,从而实现了对一个组件的封装, 一个.vue 文件就是一个单独的组件.由于.vue文件是自定义的,浏览器不认识,所以需要对该文件进行解析. 在webpack构建中,需要安装vue-loader 对.vue文件进行解析.在 sumlime 编辑器中,我们 书写.vue 文件,可以安装vue syntax highlight 插件,增加对文件的支持. 用vue-cli 新建一个vue项目,看一下.vue文件长什么样

  • JS如何实现一个单文件组件

    概述 前端开发人员只要了解过vue.js框架可能都知道单文件组件.vue.js中的单文件组件允许在一个文件中定义一个组件的所有内容.这是一个非常有用的解决方案,在浏览器网页中已经开始提倡这种机制.但是不幸的是,这个概念自从2017年8月被提出以来,到现在没有任何进展,像是已经要消亡了一样.然而,深入研究这个主题并试着使用现有的技术来实现单文件组件是很有趣的,值得尝试. 单文件组件 知道"渐进增强"这个概念的前端开发人员想必也听说过"分层"这个概念.在组件中,同样有这

  • vue实现一个单文件组件的完整过程记录

    目录 前言 单文件组件 基本概念 简单的loader 解析组件内容 注册组件 获取脚本内容 Data URI和Object URI 动态导入 实现 行为层 兼容性问题及其他 总结 前言 前端开发人员只要了解过vue.js框架可能都知道单文件组件.vue.js中的单文件组件允许在一个文件中定义一个组件的所有内容.这是一个非常有用的解决方案,在浏览器网页中已经开始提倡这种机制.但是不幸的是,这个概念自从2017年8月被提出以来,到现在没有任何进展,像是已经要消亡了一样.然而,深入研究这个主题并试着使

  • vue单文件组件的实现

    最近翻阅了一下vue.发觉有一个单文件组件之前基本忽视掉了.vue.js中的单文件组件允许在一个文件中定义一个组件的所有内容.也就是说,一个页面或者是一个组件,我们想将他们捆绑在一起,那么vue的这个单文件组件可以做到.正如vue的官网说的,"在很多 Vue 项目中,我们使用 app.component 来定义全局组件,紧接着用 app.mount('#app') 在每个页面内指定一个容器元素."这里的组件,都是相对简单的,而面对一个比较复杂的项目,这种方式就行不通.原因如下: 全局定

  • Vue单文件组件的如何使用方式介绍

    在很多 vue项目中,我们使用 vue.component 来定义全局组件,紧接着用 new vue(el: ")在每个页面内指定一个容器元素 这种方式在很多中小规模的项目中运作的很好,在这些项目里 JavaScript 只被用来加强特定的视图. 但挡在更复杂的项目中,或者你的前端完全由 javascript 驱动的时候,下面这些缺点将变得非常明显: 全局定义 (global definitions)强制要求每个 component 中的命名不能重复 字符串模板 (string template

  • 基于Vue单文件组件详解

    本文将详细介绍Vue单文件组件 概述 在很多 Vue 项目中,使用 Vue.component 来定义全局组件,紧接着用 new Vue({ el: '#container '}) 在每个页面内指定一个容器元素. 这种方式在很多中小规模的项目中运作的很好,在这些项目里 JavaScript 只被用来加强特定的视图.但当在更复杂的项目中,或者前端完全由 JavaScript 驱动的时候,下面这些缺点将变得非常明显: 1.全局定义 (Global definitions) 强制要求每个 compon

  • Vue单文件组件基础模板小结

    背景 相信大家在使用Vue开发项目时,基本都是以单文件组件的形式开发组件的,这种方式好处多多: 1.代码集中,便于开发.管理和维护 2.可复用性高,直接将vue文件拷贝到新项目中 我暂时就想到这两点,童鞋们可以在评论里帮我补充:因为有这么多优点,所以决定有必要将vue组件的常用配置项提炼出来,形成一个组件模板,方便日后项目开发复用 <template> <div> <h1>{{title}}</h1> <ChildComponents></

  • vue单文件组件无法获取$refs的问题

    记录一下学习webpack+vue碰到的一个大坑,踩这个坑是我才疏学浅的表现,特此引以为戒.因为该坑实在是太坑了! 代码 header.html <body> <div id="popup-wrap"> <popup ref="popup"></popup> </div> </body> header.js import popup from '../../components/popup/po

随机推荐