本系列一共七章,Github 地址请查阅这里。原文地址请查阅这里。javascript
虽然有一些通用的项目结构指南,可是没有适合全部项目的结构。有兴趣的童鞋能够看看来自Node Hero系列的 Nodejs 项目结构指南。java
NX 旨在成为一个开源社区驱动的工程,易于扩展和可伸缩性强。node
最终的依赖关系图以下:git
这个结构为一些经典的框架的相关痛点提供了一个解决方案。github
易扩展性是开源驱动工程的一个必备条件。为了实现它,工程必须具有小型的内核和预约义的依赖处理系统。前者保证工程是易读的,然后者则确保它将保持这种状态。后端
在本节中,我将专一于设计一个小型内核。设计模式
现代框架的主要特性是可以建立自定义组件并在 DOM 中使用它们。NX 核心内置了 component
函数具有这一功能。它容许用户配置和注册一个新的组件类型。bash
component(config)
.register('comp-name')
复制代码
注册的 compname
是一个空白的组件类型,能够按预期在 DOM 中实例化。框架
<comp-name></compname>
函数
下一步是要保证组件能够用新的功能进行扩展。为了保持简单性和可扩展性,这些新功能不该该污染内核。这就是依赖注入的方便之处。
若是你不熟悉依赖注入,我建议您阅读这篇文章。
依赖注入是一种设计模式,其中一个或者多个依赖(或服务)被注入或者经过引用传递到依赖对象中。
DI 删除了硬编码引入依赖,但却产生了一个新的问题。用户不得不去了解如何配置和注入全部的依赖。大多数的客户端框架都会有 DI 容器代替用户来作这件事。
一个依赖注入容器是一个知道如何实例化和配置对象的对象。
另外一项技术是中间件依赖注入模式(middleware DI pattern),它被普遍应用于后端(Express, Koa)。这里的窍门在于全部的可注入依赖(中间件)拥有一致的接口,而且能够以一样的方式被注入。在这种状况下,是不须要 DI 容器的。
我采用这个解决方案是为了保持简单。若是你用过 Express,下面的代码会很是熟悉。
component()
.use(paint) // 注入画图中间件
.use(resize) // 注入重调大小中间件
.register('comp-name')
function paint(elem, state, next) {
elem.style.color = 'red'
next()
}
function resize(elem, state, next) {
elem.style.width = '100px'
next()
}
复制代码
当新的组件实例挂载到 DOM 的时候,运行中间件而后为组件实例扩展新的功能。用其它不一样的库来扩展相同的对象会致使命名冲突。私有变量的暴露会让这个问题复杂化,而且可能会致使被其它人的不经意间所引用而致使事故。
解决这个问题的办法是利用一个公共的 API 来暴露公共变量而后隐藏掉其它的变量是一个好的实践。
在 JavaScript 中以函数做用域来处理私有变量的。当引入跨函数做用域的私有变量的时候,人们会试图为私有变量添加 _
前缀以表示它们的私有性,而后暴露为公有变量。这个能够防止意外的引用,可是仍然没法避免命名冲突。一个更好的替代方案是使用 ES6 的 Symbol
数据类型。
Symbol 是指的一个惟一和固化的数据类型,能够被用来做为对象的属性。
如下为一个 symbol
示例
const color = Symbol()
function colorize(elem, state, next) {
elem[color] = 'red'
next()
}
复制代码
如今 red
只能被 color
标记的引用来读取。'red'
的私有性能够经过暴露 color
标记为不一样的值来控制。当有了必定量的私有变量的时候,使用一个集中标记存储系统是一种优雅的解决方案。
exports.private = {
color: Symbol('color from colorize')
}
exports.public = {}
复制代码
添加 index.js
,内容以下:
const symbols = require('./symbols')
exports.symbols = symbols.public
复制代码
存储系统能够被工程内部的全部模块所访问可是私有的部分不会暴露出去。公有部分能够被用来暴露底层的功能给外部开发人员。这样能够防止意外引用错误,由于开发人员不得不经过显式地引用相应的标记来使用变量。另外,标记引用不会像字符串那样产生冲突,因此就能够避免命名冲突。
如下总结了不一样场景下的模式的使用。
正常使用。
function (elem, state, next) {
elem.publicText = 'Hello World'
next()
}
复制代码
跨做用域变量,即项目的私有变量,应该有一个被添加到私有标记库的标记键值。
exports.private = {
text: Symbol('private text')
}
exports.pubic = {}
复制代码
当须要的时候引用。
const private = require('symbols').private
function (elem, state, next) {
elem[private.text] = 'Hello World'
next()
}
复制代码
须在公共标记表中添加底层 API 的变量的标记键值。
exports.private = {
text: Symbol('private text')
}
exports.public = {
text: Symbol('exposed text')
}
复制代码
须要的时候加载。
const exposed = require('symbols').public
function (elem, state, next) {
elem[exposed.text] = 'Hello World'
next()
}
复制代码