Open YuhangGe opened 9 years ago
jing.js
jing.js
是构想中的全新的Web App开发脚手架。构想
是指,这个项目还处于萌芽阶段;但拒绝嘴炮和吹逼,拒绝拖沓。全新
强调的是这是一个和己有的东西在核心上有本质区别的玩意;它可能会借鉴包括jquery,angular,react等优秀产品的思想或功能。Web App
是指,在浏览器上的以单页为主的富交互、多逻辑、强数据的html5应用;不关注以信息展示为主的诸如传统门户网站。开发脚手架
是指,该项目将提供全新的Web App
驱动核心以及围绕该核心的工具产品;在前期jing.js还没有实体成型前,我们尽可能拒绝使用“框架”一类的词汇;但jing.js的目标和野心,确实是期望能做出一个全新的Web App
开发框架。
欢迎有如下特质的同学加入共同思考和探索:
debug
模式和release
模式在一开始就考虑到jing.js中。其中一点是,将seajs
的webpack
的思想(或是代码)直接集成到jing.js中。期待的目标是,debug
模式下,代码仍然是清晰拆分化的,修改后可立即生效而不需要等待编译或打包。这儿是设计概要,每一块之后都会进一步详细补充设计。
双向绑定是跟angular的双向绑定概念和基本用法一致,同时也会吸收PC软件和手机APP开发中的双向绑定思想。目前来看,jing.js
的双向绑定核心将会使用javascript的defineProperty。
逻辑属性是angular的directive的概念抽取。它基本等价于不带template和directive。逻辑属性是写在dom元素上的会被jing.js解析的attribute。逻辑属性的定位是非常轻量的逻辑控制,在之后的设计中,它不会拥有优先级等概念。不大准确的例子可以理解为,逻辑属性是诸如ng-click, ng-model
这一类的angular-directive,但ng-repeat
等在jing.js中将会以泛化组件的概念存在。
泛化组件是指将一切组件化。其思想来源于react,整个页面都是由一个层次一个层次的组件组合和拼凑而成。但和react会有本质不同的是,jing.js仍然是基于html而不是jsx。
泛化组件包括两大形式。一是独立的组件,它可以像npm/seajs一样地独立开发,有独立版本,组件之间能够相互依赖;但和npm/seajs不一样的是,jing.js的组件只能服务于使用jing.js的项目,并且组件不只是包含js,还有html/css/less/resources。二是页面区块组件,可能就只是简单的container,或是angular的ng-controller用于隔离逻辑。区块组件和独立组件拥有相同的生命周期和状态转化,可以相互依赖,无缝链接。
(泛化组件的英文应该是generalize component,简写gc容易和垃圾回收Garbage collection混淆。日后看看如何更好的命名这个概念)
数据源是jing.js期待解决的目前还没有框架解决好了的核心问题。数据源思想来源于PC软件和手机APP开发中已经广泛采纳的DataSource概念,其价值在于希望简化数据在服务器和App之间的传递和处理。在jing.js
中,数据源将会和泛化组件的设计进行深度结合。
路由基本借鉴angular、react以及服务器端框架(nodejs/php/java)的路由,可以进一步通过在jing.js
核心中融入路由的设计来尝试增强。
泛化两字完全是为了取名而取名。其实就是组件,只不过将组件概念广泛化了。泛化组件在表现形式上参考了angular的很多设计,但内部完全不同。
<env key="somEnv">
<div>
<repeat source="item in somelist | someFilter" >
<div>{{item.name}}</div>
<div>{{item.value}}</div>
<div><button click=""></div>
</repeat>
</div>
<form>
<input t-bing="someVarInEnv" />
<canlendar t-bind="someVarInEnv" />
</form>
</env>
其中<evn><repeat><canlendar>
都是泛化组件,而env
和repeat
是页面组件,canlendar
是独立组件。
页面组件可以理解成angular的directive的一种,比如ng-controller和ng-repeat,不一样的是在jing.js中一定是以Element(而不是attribute)的形式。这样的做法是为了更严谨而直观地处理像优先级一类的问题,同时也为了更好的处理泛化组件的状态变化。
独立组件其实就好比npm或seajs里的模块概念。jing.js框架在解析到
此外,除非主动指定属性dom="div"
,泛化组件的DOM元素本身不会存在于最后的浏览器DOM树里,这一点跟Angular是不同的,可以具有更好的可控性和灵活性。比如<repeat>
组件就可以实现每一次循环插入多个div。
不论是页面级的只带逻辑的组件(如env, repeat)还是自带html/css的独立组件,它们都会有统一的状态,统一的生命周期,统一的事件。
如果组件不存在,则需要先下载。对于页面组件,这个状态会被直接跳过。
这个状态对于泛化组件非常重要。在该状态中,泛化组件会去加载其依赖的数据源。比如从一个或多个URL接口获取数据,处理和拼接数据。也可能是建立基于socket的双向数据通道并取得初始数据。如果一个泛化组件没有依赖需要异步获取的数据,这个状态会被直接跳过。
这个状态会真正地处理DOM元素,进行绑定数据等操作。并且,对于其内部的子组件,则会开始子组件的状态生命周期。也就是说,一个复杂的APP页面可能是由许多的泛化组件构成,这些组件的生命周期的开始并不是完全并行的,而是从父亲到儿子这样的顺序关系。(更新:但也存在某些场景下,需要在子组件也全部Ready后再一次性展示出来。因此考虑可以配置到底在什么时候真正地把DOM显示出来)
渲染完成后,该泛化组件会将DOM元素真正地插入到浏览器DOM里面。
泛化组件需要更新数据源时,进入到这个状态。这个状态结束后,又会跳到rending状态。
泛化组件在不需要活动时进入睡眠状态(比如路由变化导致页面切换时)。睡眠状态下如果是socket的双向通信数据源将不会刷新数据。当然双向通信数据源可能会在更日后的版本里设计,初期这个状态主要是给业务层代码手动进行处理(类比于在angular的$scope的$destroy事件里回收资源,但不一样的是jing.js里泛化组件只是sleep了)。
上面的事件基本对应于不同状态时的切换,对内和对外通知。
泛化组件的状态变化时,往往UI界面需要有的相应反馈。为了更好的抽象这些大量重复的逻辑,引入特殊的一个泛化组件。暂时用<com>
来命名。
<env key="someEnv" dom="div">
<com role="splash" dom="div" class="splash">
<i icon="loading"></i><span>loading...</span>
</com>
<com role="content">
<p>{{someObj.someProp}}</p>
</com>
</env>
当泛化组件还处理非Ready状态时,它会显示role="splash"
的com
。同时,com
组件还有更灵活的设计。
可以全局配置com
组件在某个role
时的模板。比如role='splash'
这种loading的提示层,统一用一个模板。这样可以实现轻量级的组件复用并且修改起来更简单。
还有一个更大的想法是用com
组件来实现独立组件的灵活自定义。是否有这样一种情景,我有一个写好的组件可以使用,但我发现即便覆盖它默认的css,也很难得到我想要的UI视觉效果。我想要能够自定义html模板,但使用组件的逻辑。
在jing.js中,独立组件和页面是统一的,因此在设计和实现独立组件时,可以把关键的和逻辑相关的DOM元素指定为com
并为其分配一个role
。在使用独立组件时,可以通过com
来进行映射,从而实现高度的自定义化组件。以下代码是一些非常原始的想法。
<!-- 组件,名称假设为 InputName -->
<div>
<p>这里是组件默认的文案</p>
<com-def role="name">
<input t-bind="name" />
</com-def>
<div>
<!-- 简单使用组件 -->
<InputName />
<!-- 定制化使用组件 -->
<InputName>
<h1>这里文案连html结构都变啦</h1>
<div>
<com role="name" />
</div>
</InputName>
这里记录对泛化组件和数据源之间关联和使用的初步想法。至于数据源本身如何设计还在思考中。
function SomeComponent(env) {
var Account = require('AccountModel'); //Model是一种数据源
var Page = require('PageModel');
env.props = {
account: Account.get({userId: 888}),
pages: Page.getAll(),
numA: 34,
strA: 'dsdsdsd'
}
env.addState(function() {
//这里可以处理更复杂的情况,主动干预组件的状态。
// 比如某些情况下要处理一些多消耗大量时间(web worker)的计算?
});
}
module.exports = function() {
return SomeComponent;
}
<env key="SomeComponent">
<h1>Hello, {{account.userName}}</h1>
<repeat source="page in pages">
{{page.detail}}
</repeat>
</env>
每一个泛化组件都会有一个逻辑环境,也可以理解成就是该组件的实例。env.props
定义了该逻辑也就是该组件的属性(类似于React的props)。如果组件发现属性是一种数据源,那么则会监听该数据源的事件。只有所有数据源的状态都是数据已经加载时,该组件才会把自己的状态更新为rendering.
路由的设计有两种思路。一种是采用angular-ui-router的方式,基于状态树。一种是采用服务器端路由的思路(比如php的yii框架),直接将路由做为整个框架的骨架。
目前还在琢磨两种方式的优劣,以及是否可以把二者进行结合。
在实际业务中有如下一种场景:
一个list页面,点击里面的某个链接进入detail页面。然后再返回到list页面。期待如果detail页面没有对list页面有影响的情况下,返回时可以保持list之前的状态,包括dom结构、数据和浏览器滚动位置等。一方面可以避免多余的网络访问和计算(重新获取数据并渲染),另一方面也可以让用户查看之前的页面位置。
因此,路由在切换时,对应于Environment会进入Sleeping状态。当从Sleeping状态恢复(也就是用户重新切换回来时),可以从上一个路由状态里读取信息,从而决定是否重新加载数据。其实要不要重新加载数据,这些都是业务代码自己控制的。而路由模块,需要提供的只是简单的可以在路由状态切换时进行数据的传递。
由此考虑到的Environment在设计时,可以配置其对应的DOM块,在Sleeping的时候是只是'display:none'来隐藏,还是'remove'后保留在内存,还是测底的'remove'掉。这三种情况可能各有使用场景,比如路由切换时,相关联页面(如父状态到子状态,list页面到detail页面)使用'display:none'。非关联页面切换时,测底'remove'掉。
这个issue用于记录jing.js初期进行功能和架构等相关设计和思考的点滴,用于日后整理