“在AngularJS中思考”,如果我有jQuery背景?[已关闭]1.不要设计你的页面,然后用DOM操作改变它2. 不要用AngularJS来扩充jQuery3. 始终从架构的角度思考4. 测试驱动开发 - 始终 5. 从概念上讲,指令不是打包的 jQuery总结

2022-08-29 21:44:01

假设我熟悉在jQuery中开发客户端应用程序,但现在我想开始使用AngularJS。您能描述一下必要的范式转变吗?以下是一些可能有助于您构建答案的问题:

  • 如何以不同的方式构建和设计客户端 Web 应用程序?最大的区别是什么?
  • 我应该停止做什么/使用什么;我应该开始做什么/使用什么?
  • 是否有任何服务器端注意事项/限制?

我不是在寻找 和 之间的详细比较。jQueryAngularJS


答案 1

1.不要设计你的页面,然后用DOM操作改变它

在jQuery中,你设计一个页面,然后让它成为动态的。这是因为jQuery是为增强而设计的,并且从这个简单的前提中得到了令人难以置信的发展。

但是在AngularJS中,你必须从头开始,并牢记你的架构。与其从思考“我有 DOM 的这一部分,我想让它做 X”开始,你必须从你想要完成的事情开始,然后开始设计你的应用程序,最后开始设计你的视图。

2. 不要用AngularJS来扩充jQuery

同样,不要从jQuery做X,Y和Z的想法开始,所以我只是在模型和控制器之上添加AngularJS。当你刚刚开始时,这真的很诱人,这就是为什么我总是建议新的AngularJS开发人员根本不使用jQuery,至少在他们习惯于以“Angular方式”做事之前。

我在这里和邮件列表中看到许多开发人员使用150或200行代码的jQuery插件创建这些精心设计的解决方案,然后他们通过一系列令人困惑和复杂的回调和组合将它们粘合到AngularJS中;但他们最终让它工作!问题是,在大多数情况下,jQuery插件可以在AngularJS中用代码的一小部分重写,突然之间一切都变得易于理解和直接。$apply

底线是这样的:在求解时,首先“在AngularJS中思考”;如果您想不出解决方案,请询问社区;如果在这一切之后没有简单的解决方案,那么请随时访问jQuery。但是不要让jQuery成为拐杖,否则你永远不会掌握AngularJS。

3. 始终从架构的角度思考

首先要知道单页应用程序就是应用程序。它们不是网页。因此,除了像客户端开发人员一样思考之外,我们还需要像服务器端开发人员一样思考。我们必须考虑如何将我们的应用程序划分为单独的、可扩展的、可测试的组件。

那么,你是怎么做到的呢?你如何“在AngularJS中思考”?以下是一些与jQuery对比的一般原则。

视图是“官方记录”

在 jQuery 中,我们以编程方式更改视图。我们可以有一个下拉菜单,如下所示:ul

<ul class="main-menu">
    <li class="active">
        <a href="#/home">Home</a>
    </li>
    <li>
        <a href="#/menu1">Menu 1</a>
        <ul>
            <li><a href="#/sm1">Submenu 1</a></li>
            <li><a href="#/sm2">Submenu 2</a></li>
            <li><a href="#/sm3">Submenu 3</a></li>
        </ul>
    </li>
    <li>
        <a href="#/home">Menu 2</a>
    </li>
</ul>

在jQuery中,在我们的应用程序逻辑中,我们将使用如下内容激活它:

$('.main-menu').dropdownMenu();

当我们只看视图时,这里没有任何功能并不明显。对于小型应用,这很好。但对于重要的应用程序,事情很快就会变得混乱且难以维护。

然而,在AngularJS中,视图是基于视图的功能的官方记录。相反,我们的声明将如下所示:ul

<ul class="main-menu" dropdown-menu>
    ...
</ul>

这两者做同样的事情,但在AngularJS版本中,任何查看模板的人都知道应该发生什么。每当开发团队的新成员加入时,她都可以查看此内容,然后知道有一个指令称为“操作”;她不需要直觉正确答案或筛选任何代码。视图告诉我们应该发生什么。更干净。dropdownMenu

刚接触AngularJS的开发人员经常会问这样一个问题:我如何找到所有特定类型的链接并在其上添加指令。当我们回复时,开发人员总是感到震惊:你没有。但是你不这样做的原因是,这就像一半jQuery,一半AngularJS,没有好处。这里的问题是开发人员试图在AngularJS的上下文中“做jQuery”。这永远不会奏效。该视图官方记录。在指令之外(下面将详细介绍),您永远不会,永远不会,永远不会更改DOM。指令在视图中应用,因此意图是明确的。

记住:不要设计,然后标记。你必须设计,然后设计。

数据绑定

这是迄今为止AngularJS最令人敬畏的功能之一,并且消除了我在上一节中提到的各种DOM操作的大量需求。AngularJS将自动更新您的视图,因此您不必这样做!在jQuery中,我们响应事件,然后更新内容。像这样:

$.ajax({
  url: '/myEndpoint.json',
  success: function ( data, status ) {
    $('ul#log').append('<li>Data Received!</li>');
  }
});

对于如下所示的视图:

<ul class="messages" id="log">
</ul>

除了混合关注之外,我们还有我之前提到的相同的表示意图问题。但更重要的是,我们必须手动引用和更新 DOM 节点。如果我们想删除一个日志条目,我们也必须针对DOM进行编码。除了 DOM 之外,我们如何测试逻辑?如果我们想更改演示文稿怎么办?

这有点凌乱,有点小瑕疵。但是在AngularJS中,我们可以这样做:

$http( '/myEndpoint.json' ).then( function ( response ) {
    $scope.log.push( { msg: 'Data Received!' } );
});

我们的视图可以如下所示:

<ul class="messages">
    <li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>

但就此而言,我们的观点可能如下所示:

<div class="messages">
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div>
</div>

现在,我们不再使用无序列表,而是使用 Bootstrap 警报框。而且我们从来不需要更改控制器代码!但更重要的是,无论日志在何处如何更新,视图也会发生变化。自然而然。整洁!

虽然我没有在这里展示它,但数据绑定是双向的。因此,只需执行以下操作,这些日志消息也可以在视图中进行编辑:。这让人欢欣鼓舞。<input ng-model="entry.msg" />

独特的模型层

在jQuery中,DOM有点像模型。但是在AngularJS中,我们有一个单独的模型层,我们可以以我们想要的任何方式进行管理,完全独立于视图。这有助于上述数据绑定,保持关注点分离,并引入更大的可测试性。其他答案提到了这一点,所以我就不说了。

关注点分离

上述所有内容都与这个首要主题有关:将你的关注点分开。你的观点是应该发生的事情的官方记录(在大多数情况下);您的模型代表您的数据;你有一个服务层来执行可重用的任务;你做DOM操作,并用指令增强你的视图;你用控制器把它们粘在一起。这在其他答案中也提到过,我唯一要添加的与可测试性有关,我将在下面的另一节中讨论。

依赖注入

为了帮助我们分离关注点是依赖注入(DI)。如果你来自服务器端语言(从JavaPHP),你可能已经熟悉这个概念,但如果你是一个来自jQuery的客户端人,这个概念可能看起来从愚蠢到多余再到时髦。但事实并非如此。

从广义的角度来看,DI意味着您可以非常自由地声明组件,然后从任何其他组件中,只需请求它的实例,它就会被授予。您不必了解加载顺序,文件位置或类似内容。功率可能不会立即可见,但我只提供一个(常见)示例:测试。

假设在我们的应用程序中,我们需要一个通过 REST API 实现服务器端存储的服务,并且根据应用程序状态,还需要本地存储。在控制器上运行测试时,我们不希望必须与服务器通信 - 毕竟我们正在测试控制器。我们可以添加一个与原始组件同名的模拟服务,注入器将确保我们的控制器自动获得假服务 - 我们的控制器不需要也不需要知道区别。

说到测试...

4. 测试驱动开发 - 始终

这实际上是关于架构的第3节的一部分,但它非常重要,以至于我把它作为自己的顶级部分。

在你见过、使用或编写的所有 jQuery 插件中,有多少有随附的测试套件?不是很多,因为jQuery不太适合这一点。但是AngularJS是。

在jQuery中,测试的唯一方法通常是使用示例/演示页面独立创建组件,我们的测试可以针对该页面执行DOM操作。因此,我们必须单独开发一个组件,然后将其集成到我们的应用程序中。多么不方便!很多时候,当使用jQuery进行开发时,我们选择迭代而不是测试驱动开发。谁能责怪我们呢?

但是由于我们有关注点分离,我们可以在AngularJS中迭代地进行测试驱动的开发!例如,假设我们想要一个超级简单的指令来指示我们当前的路由是什么。我们可以在应用程序的视图中声明我们想要的内容:

<a href="/hello" when-active>Hello</a>

好了,现在我们可以为不存在的指令编写一个测试:when-active

it( 'should add "active" when the route changes', inject(function() {
    var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );

    $location.path('/not-matching');
    expect( elm.hasClass('active') ).toBeFalsey();

    $location.path( '/hello' );
    expect( elm.hasClass('active') ).toBeTruthy();
}));

当我们运行测试时,我们可以确认它失败了。现在我们才应该创建我们的指令:

.directive( 'whenActive', function ( $location ) {
    return {
        scope: true,
        link: function ( scope, element, attrs ) {
            scope.$on( '$routeChangeSuccess', function () {
                if ( $location.path() == element.attr( 'href' ) ) {
                    element.addClass( 'active' );
                }
                else {
                    element.removeClass( 'active' );
                }
            });
        }
    };
});

我们的测试现已通过我们的菜单按要求执行。我们的开发既是迭代的,也是测试驱动的。邪恶的酷。

5. 从概念上讲,指令不是打包的 jQuery

您经常会听到“仅在指令中执行 DOM 操作”。这是必要的。以应有的尊重对待它!

但是,让我们更深入地了解一下...

一些指令只是装饰视图中已经存在的内容(想想),因此有时立即进行DOM操作,然后基本上完成。但是,如果一个指令像一个“小部件”并且有一个模板,它也应该尊重关注点的分离。也就是说,模板也应该在很大程度上独立于它在链接和控制器函数中的实现。ngClass

AngularJS附带了一整套工具,使这变得非常容易;我们可以动态更新类; 允许双向数据绑定; 并以编程方式显示或隐藏元素;还有更多 - 包括我们自己写的那些。换句话说,我们可以在没有DOM操作的情况下完成各种令人敬畏的事情。DOM操作越少,指令就越容易测试,它们就越容易设计样式,将来就越容易更改,并且它们的可重用性和可分发性就越强。ngClassngModelngShowngHide

我看到很多刚接触AngularJS的开发人员使用指令作为抛出一堆jQuery的地方。换句话说,他们认为“由于我不能在控制器中执行DOM操作,因此我将把代码放在指令中”。虽然这当然要好得多,但它通常仍然是错误的

想想我们在第3节中编程的记录器。即使我们把它放在指令中,我们仍然希望以“角度方式”做到这一点。它仍然不需要任何DOM操作!很多时候,DOM操作是必要的,但它比你想象的要罕见得多!在应用程序中的任何地方执行 DOM 操作之前,请问问自己是否真的需要这样做。可能有更好的方法。

下面是一个快速示例,显示了我最常看到的模式。我们想要一个可切换的按钮。(注意:这个例子有点人为的,并且是一个详细的skosh,以表示以完全相同的方式解决的更复杂的情况。

.directive( 'myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            var on = false;

            $(element).click( function () {
                on = !on;
                $(element).toggleClass('active', on);
            });
        }
    };
});

这有几处错误:

  1. 首先,jQuery从来都不是必需的。我们在这里所做的任何事情都不需要jQuery!
  2. 其次,即使我们的页面上已经有了jQuery,也没有理由在这里使用它;我们可以简单地使用,我们的组件在放入没有jQuery的项目中时仍然可以工作。angular.element
  3. 第三,即使假设此指令需要 jQuery 才能工作,如果加载了 jqLite (),它将始终使用 jQuery!因此,我们不需要使用 - 我们可以只使用.angular.element$angular.element
  4. 第四,与第三个密切相关的是,jqLite元素不需要被包装进去 - 传递给函数的已经是一个jQuery元素了!$elementlink
  5. 第五,正如我们在前面的章节中提到的,为什么我们要将模板的东西混合到我们的逻辑中?

这个指令可以重写(即使对于非常复杂的情况!)更简单,就像这样:

.directive( 'myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            scope.on = false;

            scope.toggle = function () {
                scope.on = !scope.on;
            };
        }
    };
});

同样,模板内容在模板中,因此您(或您的用户)可以轻松地将其换成满足任何必要样式的模板,并且逻辑永远不必触及。可重用性 - 轰!

还有所有其他好处,比如测试 - 这很容易!无论模板中有什么内容,指令的内部 API 都不会被触及,因此重构很容易。您可以根据需要更改模板,而无需触摸指令。无论您更改什么,您的测试仍然通过。

w00t!

那么,如果指令不仅仅是类似jQuery的函数的集合,那么它们是什么呢?指令实际上是HTML的扩展。如果HTML没有做你需要它做的事情,你写一个指令来为你做这件事,然后使用它,就好像它是HTML的一部分一样。

换句话说,如果AngularJS没有做一些开箱即用的事情,想想团队将如何完成它以适应,等人。ngClickngClass

总结

Don't even use jQuery. Don't even include it. It will hold you back. And when you come to a problem that you think you know how to solve in jQuery already, before you reach for the , try to think about how to do it within the confines the AngularJS. If you don't know, ask! 19 times out of 20, the best way to do it doesn't need jQuery and to try to solve it with jQuery results in more work for you.$


答案 2

Imperative → declarative

In jQuery, selectors are used to find DOM elements and then bind/register event handlers to them. When an event triggers, that (imperative) code executes to update/change the DOM.

In AngularJS, you want to think about views rather than DOM elements. Views are (declarative) HTML that contain AngularJS directives. Directives set up the event handlers behind the scenes for us and give us dynamic databinding. Selectors are rarely used, so the need for IDs (and some types of classes) is greatly diminished. Views are tied to models (via scopes). Views are a projection of the model. Events change models (that is, data, scope properties), and the views that project those models update "automatically."

In AngularJS, think about models, rather than jQuery-selected DOM elements that hold your data. Think about views as projections of those models, rather than registering callbacks to manipulate what the user sees.

Separation of concerns

jQuery employs unobtrusive JavaScript - behavior (JavaScript) is separated from the structure (HTML).

AngularJS uses controllers and directives (each of which can have their own controller, and/or compile and linking functions) to remove behavior from the view/structure (HTML). Angular also has services and filters to help separate/organize your application.

See also https://stackoverflow.com/a/14346528/215945

Application design

One approach to designing an AngularJS application:

  1. Think about your models. Create services or your own JavaScript objects for those models.
  2. Think about how you want to present your models -- your views. Create HTML templates for each view, using the necessary directives to get dynamic databinding.
  3. Attach a controller to each view (using ng-view and routing, or ng-controller). Have the controller find/get only whatever model data the view needs to do its job. Make controllers as thin as possible.

Prototypal inheritance

You can do a lot with jQuery without knowing about how JavaScript prototypal inheritance works. When developing AngularJS applications, you will avoid some common pitfalls if you have a good understanding of JavaScript inheritance. Recommended reading: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?