在过去的几个月里,我一直在棱角分明的世界里旅行。现在回想起来,很难想象如果没有Angular.js、Backbone.js及其合作伙伴下划线. js这样的数据绑定框架,我怎么能每天编写一个大型前端应用程序,真不敢相信我已经和他们一起完成了这项工作。
也许我有点偏见,但考虑到我一直在做的应用是在浏览器中实现Photoshop类型的编辑器,它以几种完全不同的方式呈现相同的数据。
图层以图形方式呈现,占据了屏幕的大部分。它们列在面板中,您可以删除它们。当您选择一个图层时,其边缘将被虚线包围,并在列表中高亮显示。同样,面板中图层的尺寸及其大小取决于画布。我上面提到的面板可以拖动、折叠和关闭。如果不是像Augular这样的框架,这种交互、数据连接和视图同步很容易成为一场持续不断的噩梦。使用Augular纠正局部模型和所有相关视图的能力听起来几乎像是一个谎言。添加、移除或更改级别只是更改对象的问题。Level,x=10,完整。因为这个问题,没有地方可以手动浪费视图,手动修改DOM层次结构中的每个实例,甚至与DOM交互。
Augular使我们能够去我们从未想过的地方,比如设置一系列键盘快捷键,使我们能够在现有环境中应用。例如,文件编辑快捷方式(比如?b:用于切换粗体文本)仅允许我们编辑文件级别。
同样,我们为这些快捷方式附加一个描述(通过我们创建的服务注册),然后我们可以在一个方便栏上显示一个快捷方式列表以及它们的描述。此外,我们编写了一个指令,使我们能够将单个DOM元素与其快捷键绑定在一起。当您的鼠标悬停在元素上一段时间时,会出现一个提示,让您知道此时可用的快捷键。
棱角分明可以让我们做一些做梦都想不到的事情。老实说,这就好像我们不是在写一个web应用程序。网络只是一种媒介。当我们提高了对Angular的理解,代码就变得更加模块化,更加独立,更具交互性。它自然会变得更有棱角。
然后,通过Augular,我指的是Augular背后那些高度交互和丰富的应用程序开发理念。Javascript,一个类似的东西,使我们能够开发一些软件,我们认为是不可能的前一段时间。
我们甚至有能力开发一个成熟的历史控制板,用于将DOM修改为历史中的选定点,并使其工作良好。退一步说,当你兴奋地返回历史控制面板查看与Augular能力相关的数据时,你可以在查看工作中完美地更新每一个微小的细节。
这并不总是容易的。基本代码总是变得无法控制的混乱。
事实上,在过去的几周里,我们一直在更新和重写我们前端的整个架构。在我们开始重写之前,让我们看一下更新Angular的过程,以便从0.10.6开始有优势。如果你阅读变更日志,你会知道这是一个相当长的过程。
在这个重建的过程中,我们已经从以错误的方式对待Angular变成了以Angular的方式对待Angular。
在我们的例子中,错误的方法包含许多问题,我们必须在这个时候解决它们,然后才能使我们的代码库可爱。
在全局范围内声明控制器
对于Angular初学者来说,这是一个简单的例子。如果你熟悉Angular,你也会熟悉这个模式。
//缠绕在窗户上。LoginCtrl.var LoginCtrl=function ($scope,dep1,dep 2){//scope defaults };loginctrl . prototype . resetpassword=function(){//重置密码按钮点击处理程序};//关于这一点的更多信息。$inject=['$scope ',dep1 ',' dep 2 '];这个代码不包含在闭包中,或者所有声明都在根作用域和全局窗口对象上,混蛋。用一种真实的角度方式来写就是使用它提供的模块应用编程接口。但是正如您所看到的,即使是文档和建议的步骤仍然建议您使用全局范围:
这样做,伟大的事情就会发生。
//您的应用程序的控制器var XmplController=function($ scope,greeter,user){ $ scope . helling=greeter . greet(user . name);} - Angular.js文档
使用模块允许我们以下列方式重写控制器:
angular.module('myApp ')。controller('loginCtrl ',[ '$scope ',' dep1 ',' dep2 ',function ($scope,dep1,dep2){ ' use strict ';//作用域默认值$ scope . reset password=function(){//重置密码按钮单击处理程序};}]);我发现使用Angular controller的漂亮方法是必须处处使用controller函数,因为你需要controller的依赖注入,而controller提供了一个新的作用域,把我们从需求绑定到把我们所有的脚本文件打包成自调用函数表达式,就像这样(function(){})()。
依赖注入在最早的例子中,您可能已经注意到依赖是用$inject注入的。另一方面,大多数模块API允许您传入一个函数作为参数,或者传入一个包含依赖项的数组作为参数,然后传入一个依赖于这些依赖项的函数。这是Angular里我不喜欢的,但应该是它文档的错。文档中的大多数示例认为您不需要它。但现实是,你需要它。如果在使用压缩器压缩代码之前不运行ngmin,情况会变得更糟。
因为您没有使用数组格式['$scope '显式声明依赖包,】,你看似简洁的方法参数会被缩短成b、c、d、e的样子,有效杀伤Angular的依赖注入能力。我觉得他们构建框架的思路有一个很大的错误,和我最后的推断差不多,就是我非常不喜欢Require.js和他们麻烦的AMD模块。
如果不能用在产品上有什么用?
我的态度是因为你在产品中使用的框架中的部分代码已经被写死了。这对于经常在开发中使用并且偶尔在产品中使用的实用程序来说是很好的,例如控制台和错误报告。如果语法的甜度(可读性)只用于开发,那就变得毫无意义了。
这些破东西让我很生气,现在我发泄完了。我们来谈谈$ fu.
减少jQuery扩散
深入来说,这个应用是一个‘类似Angular的程序’,这意味着它只被包装在Angular中,大部分DOM交互都是通过jQuery来处理的,这给Angular带来了相当大的争议。
如果我今天想从头开始写一个Angular.js应用程序,我不会立即将其包含在jQuery中。我会强迫自己用angular.element来代替。
如果jQuery存在,API angular.element将包装它。同时,它为Angular团队实现jQuery的API提供了一个替代选择,命名为jqLite。这并不是说jQuery不好,或者我们需要另一个实现来映射它们的API。只是使用jQuery没那么有棱角。
让我们看一个具体的,愚蠢的例子。在声明控制器的地方,它使用jQuery对元素进行类操作。
div . foo(ng-controller=' FOoctrl ')angular . module(' foo ')。控制器(' fooCtrl ',函数($scope) { $(')。foo’)。addCLaSS(' foo-init ');$scope。$watch('某物',function () { $(')。foo’)。toggle class(' foo-某物-else ');});});然而,我们可以用我们期望的方式来使用Angular。
angular.module('foo ')。控制器(' fooCtrl ',函数($scope,$ element){ $ element . AddClass(' FOo-init ');$scope。$watch('某物',function(){ $ element . toggleclass(' foo-某物-else ');});});在最后一行,您不能直接或通过jQuery操作DOM(更改属性和添加事件侦听器)。你应该用说明书来代替。那篇文章很棒。去读一读。
如果您仍然是面向jQuery的,那么有很多文章可以阅读,例如本迁移指南和我对如何使用jQuery的批判性思考。
我并不是说我们要完全删除jQuery。我们还有其他更重要的目标,比如发布我们的产品。此时,消除jQuery的依赖性是有意义的。通过这样做,我们的控制器可以简化。我们创建指令来处理DOM,并使用angular.element,即使它实际上映射了jQuery。
我们依赖恶心的jQuery UI。当然,我们使用它不仅仅是为了它的对话框,还有很多目的。例如,如果不使用jQuery UI,拖动列表项并将其放入排序列表将涉及大量代码。所以,其实jQuery UI没有真正好的替代品。拖拽功能可以用轻量级的拖拽库angular-dragon-drop代替,但是对于元素排序插件,还是要依赖jQuery UI。
管理代码库
我们在迁移中需要解决的另一个问题是整个代码库被挤到一个大文件中。该文件包含所有控制器、所有服务、所有指令和每个控制器的特定代码。我指出,我们可以准确地把每个文件只包含一个组件。目前,我们的文件很少,但我们不知道一个组件。大多数是因为指令使用服务与外界共享数据。
虽然它与Angular无关,但是我们模块化了我们的CSS样式表。我们在每个组件中使用的CSS类名前加两个字。例如,pn-作为前缀,代表面板;ly前缀,代表层等等。这样做的直接好处是,您不必费心考虑哪个组件的CSS类是什么样的。因为已经为它们设置了名称空间,所以很少重用CSS类名。另一个优点是减少了嵌套。我们曾经使用# layouteditordiv这样复杂的选择器表达式。layer.handleddiv,但现在我们只需要。ly-手柄-内容。深度嵌套现在只发生在额外的选择器重写上,例如。foobar[disabled]:hover,或者,在最坏的情况下,比如。foo-bar.br-baz。
下面是我们为CSS类设置的一些命名规则:
使用两个字符来描述组件名称:ly-,dd-,dg-等。使用。ly-foo-barname而不是嵌套命名。莉福。栏避免内联样式,并始终使用CSS类名。这样可以降低不同接口的一致性,提高语义解释能力。不要在CSS中使用ID赋值。实现了这个面向组件的CSS声明方法之后,我对“类汤方式”思考了很久。
角度迫使你写好代码,但在更深层次上,它迫使你思考。过一段时间,就像是服务器端的实现,或者变成了不堪忍受的“黑客大会”。这完全取决于你的选择。
近乎完美
让我们分析应用程序的一个组件,层。
div . cv-layer(Ng-repeat=' page . layers | reverse '中的层',ap-layer,Ng-mouse down=' selectLayer(layer . id)',Ng-mouse up=' selectLayer(layer . id)',Ng-dblclick='双击层(layer)',ng-hide=' layer。不可见’)在这里,我们使用了cv-layer类,这意味着这个元素是canvas组件的一部分(canvas指的是我们绘制图层的地方,不应该和HTML5canvas混淆)。然后,我们在一个类似的foreach循环中使用ngRepeat标签为每个层创建一个类似的元素。并通过我们编写的反向过滤器,所以最后一层位于顶部,用户可以看到。apLayer标签实际上用于绘制图层的任务,无论是图片、一些文本、HTML还是其他东西。事件标记(ng-mousedown、ng-mouseup、ng-dblclick)只是用作事件的代理,这将由我们的层选择服务来处理。最后,我认为关于ngHide这个标签没有必要多说。
有这么多功能,但Angular成功地让它看起来如此简单,在可读的HTML中,它在某种程度上告诉你它们都是关于什么的。更重要的是,它允许你分解不同的问题来考虑,这样你就可以编写简洁的代码,而不必马上考虑所有的事情。简而言之,它降低了复杂性(其实Angular本身就很复杂,呵呵),让复杂性变得简单。让“难以简单衡量的问题”成为可能。
我预计很快会有更多关于Angular代码的文章。特别是,我很乐意讨论在升级代码时遇到的一些边缘情况,以及如何解决问题并使其余部分工作相同。