15、同构应用开发实践与JavaScript发展趋势

同构应用开发实践与JavaScript发展趋势

1. 模块对象与数据传递

模块对象并非全局对象,当需要将数据传递给特定模块且不暴露给其他模块时会用到它。例如,在处理条目中的项目列表(如图像库)时,为每个项目渲染一个图像模块,但使用不同的数据。此时,可通过模块对象将必要数据直接传递给模块,而非让模块以索引为键从条目中提取数据。

2. 转译视图模型
  • 定义内部结构 :在定义了顶级数据结构后,需要定义每个对象的内部结构。由于C#是强类型语言,不能像JavaScript那样随意传递动态对象字面量。每个视图模型都需要严格定义属性及其类型。为了让前端负责视图模型设计,决定用JavaScript编写视图模型,这样前端团队成员可以轻松测试模板渲染,而无需与.NET后端集成。
  • JavaScript构造函数示例 :以下是一个典型的JavaScript视图模型示例,描述了一个Bundle并继承自另一个名为Product的模型:
var Bundle = function() {
    Product.apply(this);
    this.Title      = '';
    this.Director   = '';
    this.Synopsis   = '';
    this.Artwork    = new Image();
    this.Trailer    = new Video();
    this.Film       = new Video();
    this.Extras     = [new Extra()];
    this.IsNew      = false;
};
  • 模板逻辑处理 :模板通常需要在显示或隐藏特定元素之前检查多个数据。为保持模板简洁,Handlebars中的#if语句只能评估单个属性,没有自定义辅助函数则不允许进行比较。通过ES5 JavaScript中的“getters”,可以轻松为构造函数添加动态评估属性,用于模板中的复杂评估和比较。示例如下:
var Bundle = function() {
    // ...
    this.Trailer    = new Video();
    this.IsNew      = false;
    // ...
    Object.defineProperty(this, 'HasWatchTrailerBadge', {
        get: function() {
            return this.IsNew && this.Trailer !== null;
        }
    });
};
  • JavaScript转译到C# :虽然所有视图模型都已用类型化属性和getters定义,但它们仅存在于JavaScript中。最初尝试手动将它们重写为C#,但发现这是重复工作且不可扩展。于是创建了一个简单的Node.js应用,使用枚举、类型检查和原型继承为每个构造函数创建描述性“清单”。将每个视图模型解析为清单后,可将数据输入到C#类的Handlebars模板中,生成后端的.cs文件。例如,上述Bundle视图模型转译为C#如下:
namespace Colony.Website.Models
{
    public class Bundle : Product
    {
        public string Title { get; set; }
        public string Director { get; set; }
        public string Synopsis { get; set; }
        public Image Artwork { get; set; }
        public Video Trailer { get; set; }
        public Video Film { get; set; }
        public List<Extra> Extras { get; set; }
        public Boolean IsNew { get; set; }
        public bool HasWatchTrailerBadge
        {
            get
            {
                return this.IsNew && this.Trailer != null;
            }
        }
    }
}
3. 布局定义
  • 简单布局示例 :需要一种方法来定义特定视图应渲染哪些模块以及渲染顺序。为避免在C#和JavaScript中重复模块列表和相关逻辑,考虑将每个视图表示为JSON文件,可在前端和后端共享。例如,描述主页可能布局的简单JSON文件如下:
[
    "Head",
    "Header",
    "Welcome",
    "FilmCollection",
    "SignUpCta",
    "Footer",
    "Foot"
]
  • 条件渲染模块 :希望能够根据特定条件有条件地渲染模块。受Handlebars逻辑的启发,可在JSON中引用全局数据结构中的属性来表达所需逻辑。示例如下:
[
    // ...
    {
        "Name": "UserSidebar",
        "If": ["User.IsSignedIn"]
    },
    {
        "Name": "Modal",
        "If": ["ViewState.IsTrailerOpen"],
        "Import": "Entry.Trailer"
    }
]
  • 复杂视图结构 :进一步使用这种格式描述更复杂的视图结构,模块可以嵌套在其他模块中。示例如下:
[
    // ...
    {
        "Name": "TabsNav",
        "Unless": ["Entry.UserAccess.IsLocked"]
    },
    {
        "Name": "TabContainer",
        "Unless": ["Entry.UserAccess.IsLocked"],
        "Modules": [
            {
                "Name": "ExploreTab",
                "If": ["ViewState.IsExploreTabActive"],
                "Modules": [
                    {
                        "Name": "Extra",
                        "ForEach": "Entry.Extras"
                    }
                ]
            },
            {
                "Name": "AboutTab",
                "If": ["ViewState.IsAboutTabActive"]
            }
        ]
    }
    // ...
]
4. 页面生成器

为了在栈的任一侧得到最终渲染的HTML,需要将模板、布局和数据组合在一起生成视图。为此设计了一个名为“Page Maker”的类,它需要遍历给定的布局文件,根据布局文件中的条件用相应数据渲染每个模块,最后返回渲染后的HTML字符串。为确保C#和JavaScript实现返回相同的输出,将两者作为团队编程练习编写,这也为前后端团队成员提供了学习彼此技术的机会。

5. 前端单页应用

开发团队倾向于自主构建技术。在单页应用方面,没有选择现成的框架,而是决定自己构建解决方案。遵循的原则包括:UI行为不应与特定标记紧密耦合;应用状态的变化和模板及布局中已定义的Handlebars逻辑应足以实现页面上任何元素的动态重新渲染。最终的解决方案不仅轻量级,而且高度模块化。
- 状态管理 :最低层是从服务器传递的状态和完全渲染的视图,可指定任意标记部分“订阅”状态变化。状态变化通过DOM事件发出信号,比Angular的摘要循环或各种实验性的“可观察”实现更高效。当状态发生变化时,DOM的相应部分会重新渲染并替换。
- 用户界面行为 :用户界面“行为”与状态管理过程完全分离,可逐步增强任意标记部分。例如,可将相同的“滑块”UI行为应用于不同组件。
- 路由管理 :最高层是启用了History API的路由器,用于拦截所有链接点击并确定渲染结果视图所需的布局文件。为减少前端和后端资源的差异,决定扩展REST API以提供模块模板和JSON布局。

以下是一个简单的流程图,展示前端单页应用的基本架构:

graph LR
    A[服务器] --> B[状态和视图]
    B --> C[DOM订阅状态变化]
    C --> D[状态变化触发DOM重新渲染]
    E[用户界面行为] --> F[增强标记]
    G[链接点击] --> H[路由器确定布局文件]
    I[REST API] --> J[提供模块模板和JSON布局]
6. 最终架构

初始服务器端请求和所有后续客户端请求的完整生命周期如图所示(此处省略原书中的图),涉及各个组件的使用。

综上所述,在同构应用开发中,通过模块对象、转译视图模型、布局定义、页面生成器和前端单页应用的设计,实现了前后端代码的有效共享和高效开发。同时,JavaScript的发展为同构应用提供了更多可能性,未来有望在更多技术领域看到同构应用的发展。

同构应用开发实践与JavaScript发展趋势

7. 后续改进方向

虽然通过共享各种组件大大减少了应用中的代码重复,但仍存在一些重复的地方,例如控制器和路由。基于布局文件的经验,可以将路由和部分控制器逻辑表示为共享的JSON文件,然后输入到C#和JavaScript实现的轻量级解析器中。

8. 同构JavaScript的独特优势

与一些允许在非JavaScript引擎上渲染Angular应用和React组件的解决方案不同,当前的解决方案在ASP.NET生态系统中具有独特性,不依赖特定框架。后端团队在开发过程中只需编写一些额外代码(如Page Maker),数据服务、控制器和应用逻辑基本保持不变。通过解耦,后端能够移除大量前端UI代码,使后端应用更加精简,目标更加明确。该应用在一定程度上实现了“同构”,同时保持了清晰的关注点分离。

9. 设计模式与同构JavaScript的演变
  • 设计模式的家族性 :软件设计模式的发展类似于宗教,同一模式的核心思想有多种解释,导致不同的具体实现,这就是“设计模式家族”原则。以MVC模式为例,Xerox Palo Alto Research Center(PARC)在SmallTalk中的原始实现与Ruby on Rails中的Model2-like实现有很大差异,主要是因为Ruby on Rails是基于服务器的框架,而原始MVC实现侧重于前端桌面GUI。
  • Flux模式的兴起 :随着同构JavaScript的发展,设计模式和软件架构需要不断演变。向同构JavaScript和单向不可变数据流的转变通过Flux模式家族的出现验证了“设计模式家族”原则。Flux催生了大量实现,其中大多数是同构的。这些实现的流行程度与它们支持客户端和服务器端渲染的能力相关。React、Flux和Redux等流行实现的兴起,验证了同构JavaScript的重要性。

以下是一个简单的表格,对比不同实现的MVC模式:
| 实现 | 特点 | 应用场景 |
| ---- | ---- | ---- |
| SmallTalk MVC | 侧重于前端桌面GUI | 早期桌面应用开发 |
| Ruby on Rails MVC | 基于服务器的框架 | 现代Web应用开发 |

10. JavaScript的发展趋势
  • 无处不在的JavaScript :自2011年10月首次提及同构JavaScript以来,JavaScript的应用范围不断扩大。以npm模块总数为增长指标,2011年末至2016年年中,JavaScript的增长超过了50倍,且增长速度没有放缓的迹象。JavaScript几乎无处不在,为众多平台、设备和终端用途提供支持。
  • 工具链的变革 :Traceur等JavaScript转译器和npm、browserify、webpack等工具的出现,使转译变得更加普遍和容易。在需要使用自定义语法(如React和JSX)的框架中,使用转译器变得必不可少。这为同构JavaScript创造了友好的环境,即使库不完全适合应用环境,转译和打包工具链也通常能使其可用。
  • WebAssembly的潜力 :WebAssembly是一种新的可移植、高效的格式,适合编译到Web。它为许多语言提供了可行的编译目标,对于同构代码的发展具有无限可能,超越了同构JavaScript的范畴。
11. 术语与理解
  • 术语的由来 :有人会问,为什么要为在客户端和服务器上运行相同代码创造一个新术语。原因是当时没有合适的术语来描述这一现象。而将其与数学中的“同构”概念联系起来,是因为前端构建系统和转译器在某种程度上体现了“同构”的含义。
  • 同构与通用JavaScript的区别 :经常会有关于“同构JavaScript”和“通用JavaScript”的争论。实际上,同构JavaScript可以看作一个频谱,不同类型的同构JavaScript有不同的复杂度和环境适应性:
  • 环境无关的JavaScript可以“通用”运行,无需修改。
  • 为每个环境进行填充的JavaScript只有在修改环境后才能在不同环境中运行。
  • 使用填充语义的JavaScript可能需要修改环境或在不同环境中表现略有不同。
  • 因此,如果“通用JavaScript”指的是无需修改代码或环境即可实现同构的代码,那么环境无关的JavaScript显然是通用的。随着对代码或运行环境的修改需求增加,代码就更倾向于同构。

以下是一个mermaid流程图,展示同构与通用JavaScript的关系:

graph LR
    A[环境无关JavaScript] --> B[通用JavaScript]
    C[需填充环境的JavaScript] --> D[同构JavaScript]
    E[使用填充语义JavaScript] --> D
12. 总结与展望

对于开发者来说,技术术语的争论并不重要,关键是利用已有的理解去做有趣或创新的事情。同构应用的发展表明,同构原则可以应用于任何技术栈,无需依赖可能在一年内过时的前端框架,也无需从头重建应用。希望其他开发团队能从这些经验中获得启发,未来能在更多技术领域看到同构应用的发展。

综上所述,同构应用开发结合了模块管理、视图模型转译、布局设计等多种技术手段,同时受益于JavaScript的发展和新兴技术的出现。随着技术的不断进步,同构应用有望在更多场景中得到应用,为开发者带来更多便利和创新机会。

第三方支付功能的技术人员;尤其适合从事电商、在线教育、SaaS类项目开发的工程师。; 使用场景及目标:① 实现微信支付宝的Native、网页/APP等主流支付方式接入;② 掌握支付过程中关键的安全机制如签名验签、证书管理敏感信息保护;③ 构建完整的支付闭环,包括下单、支付、异步通知、订单状态更新、退款对账功能;④ 通过定时任务处理内容支付超时概要状态不一致问题:本文详细讲解了Java,提升系统健壮性。; 阅读应用接入支付宝和建议:建议结合官方文档沙微信支付的全流程,涵盖支付产品介绍、开发环境搭建箱环境边学边练,重点关注、安全机制、配置管理、签名核心API调用及验签逻辑、异步通知的幂等处理实际代码实现。重点异常边界情况;包括商户号AppID获取、API注意生产环境中的密密钥证书配置钥安全接口调用频率控制、使用官方SDK进行支付。下单、异步通知处理、订单查询、退款、账单下载等功能,并深入解析签名验签、加密解密、内网穿透等关键技术环节,帮助开发者构建安全可靠的支付系统。; 适合人群:具备一定Java开发基础,熟悉Spring框架和HTTP协议,有1-3年工作经验的后端研发人员或希望快速掌握第三方支付集成的开发者。; 使用场景及目标:① 实现微信支付Native模式支付宝PC网页支付的接入;② 掌握支付过程中核心的安全机制如签名验签、证书管理、敏感数据加密;③ 处理支付结果异步通知、订单状态核对、定时任务补偿、退款及对账等生产级功能; 阅读建议:建议结合文档中的代码示例官方API文档同步实践,重点关注支付流程的状态一致性控制、幂等性处理和异常边界情况,建议在沙箱环境中完成全流程测试后再上线。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值