单个vue文件怎么运行,vue表单数据和文件一起提交
整个项目结构清晰,尤其是单文件组件的表现力特别突出,使得各个组件的逻辑不会太复杂。所以本文主要介绍vue实现一个单文件组件的相关信息,有需要的朋友可以参考一下。
目录
前言文档组件
基本概念
简单装载机
解析组件内容
注册组件
获取脚本内容
数据URI和对象URI
动态导入
实现
行为层
兼容性问题和其他问题
摘要
前言
前端开发人员只要了解过vue.js框架,就可能知道单文件组件。vue.js中的单文件组件允许在一个文件中定义组件的所有内容。这是一个非常有用的解决方案,这种机制已经在浏览器网页中得到提倡。但遗憾的是,这个概念从2017年8月提出至今,没有任何进展,好像要消亡了。不过深入研究这个话题,尝试利用现有技术实现单文件组件,还是很有意思的,值得一试。
单文件组件
知道“逐步增强”这个概念的前端开发者,一定也听说过“分层”这个概念。在组件中,也有这样一个概念。事实上,每个组件至少有3层,甚至3层以上:内容/模板、表现和行为。或者保守一点,每个组件至少会分成三档。例如,按钮组件的文件结构可能如下所示:
按钮/
-Button.html
- Button.css
- Button.js
这样分层相当于技术的分离(内容/模板:使用html,性能:使用css,行为:使用JavaScript)。如果不使用任何构建工具进行打包,就意味着浏览器需要获取这3个文件。因此,一个想法是迫切需要一种分离组件代码而不分离技术(文件)的技术来解决这个问题。这就是本文的主题——单个文件组件。
总的来说,我对“技术分层”持怀疑态度。它来源于组件分层往往因为绕不开“技术分层”而被抛弃,两者完全割裂。
回到主题,用单文件组件实现按钮可能如下所示:
模板
!-Button.html目录在这里。-
/模板
风格
/* Button.css内容放在这里。*/
/风格
脚本
//Button.js内容放在这里。
/脚本
可以看出,这个单文件组件非常类似于原来前端开发中的html文档。它有自己的样式标签和脚本标签,但是表示层使用模板标签。由于这种简单的方式,一个强大的分层组件(内容/模板:模板,性能:样式,行为:脚本)无需使用三个单独的文件就可以获得。
基本概念
首先,我们创建一个全局函数loadComponent()来加载组件。
window . load component=(function(){
函数加载组件(URL ) {}
返回loadComponent
}());
这里使用的是JavaScript模块模式。它允许定义所有必要的辅助函数,但只公开loadComponent()函数。当然这个功能现在还是空的。
稍后,我们将创建一个hello-world组件来显式显示以下内容。
你好,世界!我的名字是教名。
另外,点击这个组件,会弹出一条消息:
别碰我!
代码保存为文件HelloWorld.wc(其中。wc代表Web组件)。初始代码如下:
模板
你好
费洛,世界!我的名字是插槽/插槽。/p
/div
/模板
风格
部门{
背景:红色;
边框半径:30px
填充:20px
字体大小:20px
文本对齐:居中;
宽度:300px
边距:0自动;
}
/风格
脚本/脚本
目前,组件中没有添加任何行为,只定义了模板和样式。模板,可以使用常见的html标签,比如div。另外,模板中的slot元素表示组件将实现shadow DOM。并且默认情况下,这个DOM本身的所有样式和模板都隐藏在这个DOM中。
组件在网页中的使用方式非常简单。
你好-世界指挥官/你好-世界
script src=loader.js/script
脚本
load component( hello world . WC );
/脚本
您可以使用像标准定制元素这样的组件。唯一的区别是,在使用loadComponent()方法之前,需要先加载它(这个方法放在loader.js中)。load()方法完成所有繁重的工作,比如获取组件并通过customElements.define()注册它。
知道了所有的概念之后,就该练习了。
简单的loader
如果要从外部文件加载文件,就需要使用万能的ajax。但现在是2020年。在大多数浏览器中,可以大胆使用Fetch API。
函数加载组件(URL ) {
返回fetch(URL);
}
但是,这只是获取文件,没有对文件进行任何处理。接下来要做的是将ajax返回的内容转换成文本文本文本,如下所示:
函数加载组件(URL ) {
返回获取(URL)。然后( (响应)={
返回response . text();
} );
}
loadComponent()函数是一个Promise对象,因为它返回fetch函数的执行结果。您可以检查文件(HelloWorld.wc)是否实际加载到then方法中,以及它是否已经转换为文本:
运行结果如下:
在chrome浏览器下,使用console()方法,我们看到HelloWorld.wc的内容被转换成文本并输出,看起来是可以工作的!
解析组件内容
然而,仅仅输出文本并没有达到我们的目的。最后要转换成DOM进行展示,并能真正与用户互动。
浏览器环境中有一个非常实用的类DOMParser,可以用来创建一个DOM解析器。实例化一个DOMParser类以获得一个对象,该对象可用于将组件文本转换为DOM:
window . load component=(function(){
函数加载组件(URL) {
返回获取(URL)。然后((响应)={
返回response . text();
}).然后((html)={
const parser=new DOM parser();//1
return parser . parsefromstring(html, text/html );//2
});
}
返回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元素。因此,组件代码中的所有元素(元素、样式、脚本)都允许放在头部。如果一个P元素被包装在模板之外,解析器会把它放在主体中。
还有一个问题。组件解析后,并没有!DOCTYPE html语句,所以这得到了一个异常的html文档,所以浏览器会使用一种叫做怪异模式的方式来渲染这个html文档。幸运的是,这里不会带来任何负面影响,因为这里只使用了DOM parser将组件分成适当的部分。
有了DOM树,我们可以只截取我们需要的部分。
返回获取(URL)。然后((响应)={
返回response . text();
}).然后((html)={
const parser=new DOM parser();
const document=parser . parsefromstring(html, text/html );
const head=document.head
const template=head . query selector( template );
const style=head . query selector( style );
const script=head . query selector( script );
返回{
模板,
风格,
脚本
};
});
最后整理一下代码。loadComponent方法如下。
window . load component=(function(){
函数fetchAndParse(URL) {
返回获取(URL)。然后((响应)={
返回response . text();
}).然后((html)={
const parser=new DOM parser();
const document=parser . parsefromstring(html, text/html );
const head=document.head
const template=head . query selector( template );
const style=head . query selector( style );
const script=head . query selector( script );
返回{
模板,
风格,
脚本
};
});
}
函数加载组件(URL) {
返回fetchAndParse(URL);
}
返回loadComponent
}());
Fetch API并不是从外部文件获取组件代码的唯一方式。XMLHttpRequest有一个专用的文档模式,允许您省略整个解析步骤。但是XMLHttpRequest返回的不是承诺。这个需要自己包。
注册组件
现在已经有了组件层,可以创建registerComponent()方法来注册一个新的定制组件。
window . load component=(function(){
函数fetchAndParse(URL) {
[…]
}
函数注册组件(){
}
函数加载组件(URL) {
返回fetchAndParse(URL)。then(register component);
}
返回loadComponent
}());
请注意,自定义组件必须是从HTMLElement继承的类。此外,每个组件都将使用一个影子DOM来存储样式和模板内容。所以每次引用这个组件,这个组件的样式都是一样的。该方法如下:
函数registerComponent({template,style,script}) {
类UnityComponent扩展HTMLElement {
connectedCallback() {
这个。_ upcast();
}
_upcast() {
const shadow=this . attach shadow({ mode: open });
shadow . appendchild(style . clone node(true));
shadow . appendchild(document . import node(template . content,true));
}
}
}
UnityComponent类应在registerComponent()方法中创建,因为该类使用传递给registerComponent()的参数。这个类将使用稍微修改过的机制来实现影子DOM,我在这篇关于影子DOM(波兰语)的文章中已经详细介绍过了。
关于注册组件只剩下一件事了。给单文件组件命名,并将其添加到当前页面的DOM中。
函数registerComponent( { template,style,script } ) {
类UnityComponent扩展HTMLElement {
[.]
}
返回custom elements . define( hello-world ,unity component);
}
现在可以打开看看了,如下图:
在chrome中,在这个按钮组件中,有一个红色的矩形,上面写着:Hello,world!我叫科曼迪尔.
获取脚本内容
现在已经实现了一个简单的按钮组件。现在来实现最难的部分,添加一个行为层,自定义按钮里面的内容。在上面的步骤中,我们应该使用按钮传入的地方,而不是在组件代码中硬编码按钮内部的文本内容。同样,我们必须处理绑定在组件中的事件监控。这里,我们使用类似于Vue.js的约定,如下所示:
模板
[…]
/模板
风格
[…]
/风格
脚本
导出默认值{ //1
名称: hello-world ,//2
onClick() { //3
警惕(`别碰我!` );
}
}
/脚本
可以假设组件中script标签中的内容是一个JavaScript模块,它导出内容(1)。模块的导出对象包含组件的名称(2)和以“on .”开头的事件监听方法(3)。
这看起来很整洁,没有任何东西暴露在模块之外(因为JavaScript中的模块不在全局范围内)。这里有一个问题:没有一个标准可以处理从内部模块导出的对象(这些代码直接在HTML文档中定义)。import语句将假设获得了一个模块ID,并根据这个ID进行导入。最常见的是来自包含代码的文件的URL路径。组件不是js文件,没有这样的标识,内部模块也没有这样的标识。
在投降之前,你可以用一个超级脏的黑客。浏览器至少有两种方法来处理像文件一样的文本:数据URI和对象URI。也有一些使用服务人员的建议。但在这里似乎有点大材小用。
Data URI和Object URI
数据URI是一种古老而原始的方法。它的基础是将文件内容转换成URL,去掉不必要的空格,然后用Base64对所有内容进行编码。假设有一个包含以下内容的JavaScript文件:
导出默认值true
转换为数据URI,如下所示:
数据:应用/JavaScript;base64,ZXhwb3J0IGRlZmF1bHQgdHJ1ZTs=
然后,这个URI可以像一个文件一样被介绍:
从“数据:应用程序/javascript”导入测试;base64,zxhwb 3j 0 igrlzm f1 bhqgdhj 1 zts=;
console.log(测试);
URI的一个明显的缺点是,随着JavaScript文件内容的增加,这个URL的长度会变得很长。将二进制数据放入数据URI非常困难。
所以,现在有了一个新的对象URI。它源自几个标准,包括文件API和HTML5中的视频和音频标签。对象URI的目的很简单,从给定的二进制数据创建一个“伪文件”,并在当前上下文中给出一个唯一的URI。简单来说就是在内存中创建一个唯一名称的文件。对象URI具有数据URI(一种创建“文件”的方法)的所有优点,而没有它的缺点(即使文件有100M长)。
对象URIs通常是从多媒体流(例如,在视频或音频环境中)或通过input [type=file]和拖放机制发送的文件中创建的。您也可以使用两个类File和Blob来手动创建它。在本例中,我们使用Bolb,首先将内容放入模块,然后将其转换为对象URI:
const myJSFile=new Blob([ export default true;],{ type: application/JavaScript });
const myJSURL=URL . createobjecturl(myJSFile);
console . log(myJSURL);//blob:https://blog . comandeer . pl/8e 8 FBD 73-5505-470d-a797-DFB 06 ca 71333
动态导入
但是,还有一个问题:import语句不接受变量作为模块标识符。这意味着除了使用此方法将模块转换为“文件”之外,不能导入模块。还是无解?
不完全是。这个问题很久以前就提出来了,使用动态导入机制就可以解决。它是ES2020标准的一部分,已在Firefox、Safari和Node.js13.x中实现。使用变量作为要动态导入的模块的标识符不再是问题:
const myJSFile=new Blob([ export default true;],{ type: application/JavaScript });
const myJSURL=URL . createobjecturl(myJSFile);
导入(myJSURL)。然后( (模块)={
console . log(module . default);//真
});
从上面的代码可以看出,import()命令可以像方法一样使用。它返回一个Promise对象,模块对象是在then方法中获得的。它的默认属性包含模块中定义的所有导出对象。
实现
既然我们知道了这个想法,我们就可以开始实现它了。添加一个工具方法getSetting()。在registerComponents()方法之前调用它,从脚本代码中获取所有信息。
函数getSettings( { template,style,script } ) {
返回{
模板,
风格,
脚本
};
}
[.]
函数加载组件(URL ) {
返回fetchAndParse( URL)。然后(获取设置)。then(register component);
}
现在,这个方法返回所有传入的参数。根据上面介绍的逻辑,将脚本代码转换成对象URI:
const jsFile=new Blob([script . text content],{ type: application/JavaScript });
const jsURL=URL . createobjecturl(jsFile);
接下来,使用import加载模块并返回模板、样式和组件的名称:
返回导入(jsURL)。然后( (模块)={
返回{
名称:module.default.name,
模板,
风格
}
} );
出于这个原因,registerComponent()仍然获得3个参数,但现在它获得的是name而不是script。正确的代码如下:
函数registerComponent( { template,style,name } ) {
类UnityComponent扩展HTMLElement {
[.]
}
返回customElements.define( name,unity component);
}
行为层
组件中还剩下最后一层:行为层,用于处理事件。现在,我们只需在getSettings()方法中获取组件的名称,并获取事件监控。您可以使用Object.entrie()方法获得它。在getSettings()方法中添加适当的代码:
函数getSettings( { template,style,script } ) {
[.]
函数getListeners( settings ) { //1
const listeners={ };
对象.条目(设置)。forEach( ( [设置,值] )={ //3
if(setting . starts with( on ){//4
侦听器[设置[ 2 ]。toLowerCase()setting . substr(3)]=value;//5
}
} );
返回侦听器;
}
返回导入(jsURL)。然后( (模块)={
const listeners=get listeners(module . default);//2
返回{
名称:module.default.name,
侦听器,//6
模板,
风格
}
} );
}
现在方法变得有点复杂了。添加了一个新函数getListeners()(1)来将模块的输出传递给这个参数。
然后使用Object.entries()(3)方法遍历导出的模块。如果当前属性以“on”(4)开头,则它是一个侦听器函数。将该节点的值(listener函数)添加到listeners对象,并使用设置[2]。to lower case()setting . substr(3)(5)获取键值。
键值是通过去掉开头的“on”并将后面的“click”的首字母转换成小写(即从onClick中获取Click作为构建值)而形成的。然后传入isteners对象(6)。
您可以使用[]。reduce()方法而不是[]。forEach()方法,以便可以省略侦听器的变量,如下所示:
函数getListeners(设置){
返回Object.entries(设置)。reduce( ( listeners,[ setting,value ] )={
if(setting . starts with( on ){
侦听器[设置[ 2 ]。toLowerCase()setting . substr(3)]=value;
}
返回侦听器;
}, {} );
}
现在,您可以将监视绑定到组件内部的类中:
function register component({ template,style,name,listeners } ) { //1
类UnityComponent扩展HTMLElement {
connectedCallback() {
这个。_ upcast();
这个。_ attach listeners();//2
}
[.]
_attachListeners() {
对象.条目(侦听器)。forEach( ( [事件,侦听器] )={ //3
this.addEventListener( event,Listener,false);//4
} );
}
}
返回customElements.define( name,unity component);
}
listeners方法(1)中添加了一个参数,该类中添加了一个新的method _attachListeners()(2)。在这里,您可以再次使用Object.entries()来遍历侦听器(3)并将它们绑定到元素(4)。
最后点击组件弹出“别碰我!”,如下所示:
兼容性问题及其他
可以看到,为了实现这个单文件组件,大部分工作都集中在如何支持基本表单上。他们中的许多人使用肮脏的手段(在es中使用对象URI加载模块,没有浏览器的支持,这项技术毫无意义)。幸运的是,所有技术在主流浏览器中都运行良好,包括Chrome、Firefox和Safari。
尽管如此,创建这样一个项目还是很有趣的,它将接触到许多浏览器技术和最新的web标准。
最后,你可以在网上得到这个项目的代码。
总结
这就是这篇关于vue实现单个文件组件的文章。有关vue单文件组件的更多信息,请搜索我们以前的文章或继续浏览下面的相关文章。希望大家以后能多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。