vir.js v1.2.5
Vir.js 就用js开发web, 哼!
I' am sorry for that there is no English Version, because this is a stupid project.
这次我将核心的功能抽离出来了, 核心的内容比旧版的少了许多, 也更灵活了.
旧版的可以看看这里: https://github.com/Eoyo/express/tree/master/public/Vir
更新日志:
3月1日: 1. 确认了数据处理方案: 支持流对象节点用来控制Dom的各种属性,关键字: VirStream 2. 统一了Vir中的类型命名, 函数节点的类型名字为 VirFunction 3. 新: 添加了对面向对象的支持. 对象中只要实现getRender,高阶方法就可以了, 关键字为VirClass; 4. node 端可以跑vir.js了, 目前可以用vir.js 做html模板的生成. 5. 提供了工具函数,Vir.html(obj); 解析js dom生成html的字符串的;
2018-03-01 至 --
Vir.js 的VirStream,VirClass 版本更新介绍:
一. VirStream, 数据流对象
1. 数据绑定的历史介绍
在Vir.js 的最先版本中(一个古老版本), 使用Vir中内置的Data类重写数据, 通过将普通的数据变成用对象封装的数据, 可以实现双向绑定.
在Vir的内部将数据重构的做法主要是学习了Vue. 曾经用Vue做过开发, 开发体验不是很好.Vue 改变了数据的get, set 方法, 使得数据不纯, 而最早的Vir中也犯了这个错误; 数据不纯, 使得组件与组件的沟通困难; dom操作太依赖数据绑定, 让人感觉多绕了弯子了, 而且还会污染数据,比如使得数据中还得有许多状态数据.
Redux 在我使用React 开发时使用过, Redux 最大的缺陷是没有类型申明,这个好解决. 于是我用typescript 写了个类似的东西叫:Spi.ts;
但是Spi.ts有点太'大型了', 不适用于一个简单的数据绑定. 后来温习node.js的文件操作时恍然大悟, Redux 其实就是一个高级的流操作呀. 要想将数据单向的绑定到dom上,就是从数据流上牵根管道到某个dom操作上! 数据绑定完全满足单方向,点对点;于是VirStream就诞生了, 简单快速, 套用早就有了的流的概念.
2. vir.js 中使用VirStream 绑定数据
Vir 中提供了VirStream, Vir.io()
是一个VirStream
的工厂函数, 返回一个new VirStream()
;
// 创建一个流
const src = Vir.io('./index.html')
// 绑定到dom
Vir({
'iframe' :{
args: { src } // ES6 同名略写
}
})
// 使用流
setTimeout(() =>{
src.write('./404.html');
}, 1000);
- 结果: 1s 后iframe 的内容被定向到404.html了;
- 分析:
- src 满足基本的流接口: write, read, pipe
- src.write后数据同过pipe流到了iframe的src里;
- 注意:
- 为了稳定 VirStream 只允许用于绑定dom的某个属性,
- 其他的高级功能, 可以结合函数节点和接下来的对象节点实现,vir.js懒得实现了
- 其实实现了也不想发布, 下个版本再说;
3. VirClass , 在Vir.js中的支持原理
vir.js 中解析VirClass 的对象的方式很简单, 只是看这个对象有没有getRender方法. 有就调用 => getRender 返回一个函数 => 转成函数节点处理.
vir.js 中不内置VirClass 的实现, 因为我也不知道到底那个VirClass 才符合大家的口味, 索性不发布, VirClass 实现的唯一要求就是要有一个getRender高阶函数.
2017-12-00 至 2018-02-28
vir.js 的核心: js Dom 树的解析
一. 用法
1. 安装:
npm install vir.js
使用typescript编写的, 自带类型声明的
2. 导入 (typescript/ ES6)
import { Vir } from 'vir.js'
注意啦: 要使用{} , 我是export { Vir } 导出的
3. 其他使用: 例如
- vir.js 使用的webpack打包的(umd)
- 浏览器导入
<script src="node_models/vir.js/dist/vir.js"></script>
- node.js 的 require,
const Vir = require('vir.js').Vir
二. 简单的使用例子:
1. 显示四个盒子(className = "box"
), 每个盒子一个big apple:
写法1:
Vir({
'4* .box': {
'.apple': 'Big Apple'
}
})
写法2: 使用vir.js'原生的'数组自适应, 让你可以控制单个的内容
Vir({
'.box' : [
{ '.apple': 'Big Apple' },
{ '.apple': 'Big Apple' },
{ '.apple': 'Big Apple' },
{ '.apple': 'Big Apple' }
]
})
// 可能有点特别, 反直觉. 有可能你认为这表示一个盒子里四个apple. 注意vir.js的数组节点是分述语法. 详见语法四.8
// 为了不被糊弄写个注释也可以
Vir({
'4; .box' : [
{ '.apple': 'Big Apple' },
{ '.apple': 'Big Apple' },
{ '.apple': 'Big Apple' },
{ '.apple': 'Big Apple' }
]
})
写法3: 搭配ramda 等类似的库, 让你有望使用函数式开发.
import R = require('ramda')
Vir({
'.box' : R.map(
(e)=>({ '.apple': e }),
R.repeat('Big Apple', 4)
)
})
强行安利一波函数式编程: ( 2018-01-15)
案例1: 根据URL数组,生成一组图片:
const data = ['','','']
function toArgsSrc(v) {
return {
args: { src: v }
}
}
Vir({
"img .bigImage": data.map(toArgsSrc)
})
// 如果又不是img了
Vir({
"video .playingBackground": data.map(toArgsSrc)
}) // 逻辑逻辑完美的复用
案例2: 还是根据URL数组,生成一组图片, 但是每个图片的样式一样(假设你就是想用js设置样式)
Vir(this.ele, {
"img ::pics": {
$: data.map(toArgsSrc) // 把之前直接写在尾部的,用$接起来
, style: { // 这个style 会被复用到每一个img上;
border: '1px solid #aaa'
}
}
})
2. 模块开发, 一个模块说我是'model one', 另一个说我是: 'model two'
核心是利用 函数节点
// use typescript
// modelOne
import { Vir } from 'vir.js';
import * as $ from 'jquery';
function One (ele : HTMLElement) {
const sayWords = 'I\'am model one'
Vir(ele, {
'.say': sayWords
, on: { // 绑定个事件也可以
click() {
alert(sayWords)
}
}
})
}
//model two
function Two (ele : HTMLElement) {
const sayWords = 'I\'am model two'
Vir(ele, {
'.say': sayWords
})
// 想用jquery就用, 自由如此
$(ele).on('click',()=> {
alert(sayWords)
})
}
// usage 1:
Vir({
'.model': One
, '2; .model': Two
})
// usage 1 等价于如下: usage 2
Vir({
'.model': [
One, Two
]
})
// 说白了就是有个函数节点, 看看如下, 详细见语法篇
Vir({
'.model'(ele) {
ele.innerHTML = 'abc'
}
})
三. vir.js 的优势
- 定位清晰: 就是用js创建dom的.
- 无其他依赖: 目前发布的vir.js 才800来行, 源码都可以一口气看完..
- 有潜力: 这是一个极简先驱版本, 我还开发了许多vir.js的工具, vir.js不久将会上升到framework的高度
- 完全用js 开发的: 实现在前后端同时跑也是可以的;
- 使用方便: 有强大的数组使用方法;
四. vir.js 的语法
1.基本的 id 与class 解析 : "#id .class .classtwo"
#
后紧跟id名, .
后跟class名,class是按顺序的,可以多个;id 与和class 不分顺序,class之间有顺序的;
标签名默认为div;
2.属性解析:[key = 'value']
和html 中写属性值一样;
3.子元素解析:".parent > .son"
在js中:
".parent > .son":{
$:"son"
///绑定在最后生成的元素上;即为.son上;
}
创建的是:
<div class = "parent"> <div class = "son">son</div> </div>
4.多个元素 : "3* div"
数字和黏在一起;不可以!!!!:`"3 div"。
但是可以:
"3*div"`。最好是放在开头,其他地方也可以,但容易出错;
5.定义变量 : "div ::oneDiv"
创建的div存在oneDiv里。可以多次出现::oneDiv在不同的标签属性里,结果是oneDiv变成了数组,按创建次序记录各个element;还可以与point 4中的乘法搭用,生成数组(不是“反常坑”的HTMLcollection)。
6.混搭 :"div ::parentDiv > 3*div ::threeChild"
js代码:
var test = Vir({
"div ::parentDiv > 3*div ::threeChild":{
$:"son"
///绑定在最后生成的元素上;即为.son上;
}
})
生成html如下:
<div>
<div>son</div>
<div>son</div>
<div>son</div>
</div>
使用变量threeChild;
// 使用变量threeChild;
test.args.threeChild.forEach((v)=>{
v.innerHTML = "use";
})
改动效果如下html:
<div>
<div>use</div>
<div>use</div>
<div>use</div>
</div>
7. 特殊的属性: $, on, style, args, data
其实上面有许多的例子使用了特殊的属性了, 特殊属性主要是为了方便操作dom的. 其他的都很简单的, 就$牛逼点: $ 是innerHTML, 但也可以是数组.
- on 绑定事件的地方. on为函数时自定义把绑定,
'div .test':{ on(ele){ /* do what you want */ } }
, ele 为HTMLELement; - style 绑定样式的地方
- args 绑定生成的HTMLELement的属性如className, 或自定义: 如key; (index 被Vir.js用了!)
- data 绑定到dataSet上, 原生dom的安全数据接口, 优点是css里可以访问到(绝对黑科技!),
- $ 显式的绑定innerHTML 或者使用数组, 就这两种情况
8. 分述、复述和自适应数组
数组的自动解析是本框架的一大特色, 很多框架都是要用类似于for于语句去声明. 但是我这里不需要的. 为了使用时使得结果符合预期, 请大家注意一下几点:
8.1 数组中合法的类型:
Vir({
'.array > .item' : [
'string'
, true
, 123 //primitive转字符串显示
, { // object 当做新的vir节点解析
'#obj' : 'object'
}
, (ele) => { // 调用函数处理, 唯一入口是框架生成的HTMLElememt
ele.innerHTML = 'function'
}
, null // 结果不显示
, undefined // 显示出undefined
]
})
注意: 1. 不支持Symbol类型用在数组里 2. null 和 undefined: null不显示, undefined会被渲染成字符串'undefined'; 3. 尾部的
.item
没有声明数量和声明的数量为1, 则.item
的数量为其后数组的长度. 若是声明的数量大于1, 则溢出的会忽略, 不足的为'undefined'
8.2 分述、复述
js代码为:
// 分述
Vir({
".parent > 3* .son":["son1","son2","son3"]
})
// 复述
Vir({
".parent > 3* .son": "son"
})
分述在节点值为数组时触发;否则为复述
生成html为:
<!-- 分述 -->
<div>
<div title = "son">son1</div>
<div title = "son">son2</div>
<div title = "son">son3</div>
</div>
<!-- 复述 -->
<div>
<div title = "son">son</div>
<div title = "son">son</div>
<div title = "son">son</div>
</div>
ps: 复述, 我觉的就生成个棋盘啥的有用了...
8.3 在特殊的属性$中使用数组
其实和直接写在节点尾部是一样的, 只是使用$写, 可以写额外的style, on 等等
实现点击blue弹出blue, 点击red弹出red
Vir ({
'span .color': {
$: ['red', 'blue']
, on: {
click() {
alert(this.innerHTML)
}
}
}
})
9. 属性的注释
js 对象里同级的属性名是唯一的, 虽然8.* 中可以通过数组批量的生成节点, 但是数组不适用所有的情况. (写文章的话, 不要用vir.js , vir.js 是用来开发应用的, 推荐: MarkDown;)
使用';' + ' '
, 即分号 + 空格
, 放在属性的开头, 表示注释;
Vir({
'h2': "头"
, "english; h2" : "head"
})
// 属性名只能唯一, 使用带注释的属性名. 我觉的这个不仅能解决问题还解决的不错.
五. 来自Vir.js 开发的其他东西, (可以在vir.js 的 1.0.7 及以上使用)
主要有: 1. EventPool, 事件的处理池; 接下来:(before 2018 1-15): 2. State 3. ApiManager
1. EventPool 事件处理池
生成的是一个eventpool 对象, 使用这个对象管理事件的触发和监听;
- 超简单而直接的example: 触发'say', 1秒后alert 一个 'hello'
import { EventPool } from 'vir.js';
const ev = new EventPool();
ev.listen('say', (data)=> {
alert(data)
})
function say () {
ev.emit('say', 'hello')
}
// 延时
setTimeout(say, 1000)
- EventPool实现的是基于事件名的异步解耦合, 来个高级的例子:
场景: 有一个任务流程: A -> B -> C, 运行到B时需要满足一定的条件才可以继续 运行到C. 如何在外部去控制B 的状态呢?? 通俗的说: 问题是指如何传个开关给函数, 外边的人拿着这个开关控制这个函数的运行? 如何搞出这个开关?
import { EventPool } from 'vir.js';
// A -> B -> C;
function job(B: Promise) {
// do A
B.then((data)=>{
// do C
})
}
const ev = new EventPool();
// set B, B is a Promise object
job(ev.listen('done')) // ev.listen('EveCode') , 单参数,返回的Promise 对象.
ev.emit('done', 'B is ok!')
- Promise的优点
普通的事件处理有时会有意外
例如: 先触发了事件, listen晚了, 导致最后一次的触发没监听到.
// 这里只是一种简洁的写法, 实际可能更复杂! ev.emit('done', 'ok')
ev.listenAll('done',(ok)=>{ alert(ok) })
Promise不会有这种问题, Promise 有状态记录, 无论在哪then 都可以.
4. done, after 的事件模型(内存的消耗多了点, 不要用太多就ok)
Promise 是一个稳定的开关, 状态只会改变一次.
一个Promise后的then 里的函数只会执行一次; 不利于事件常常触发的场景(也可以使用, 只是有点蹩脚)
> done, after继承了Promise 的优点, 无论何时done , after总是可以触发;
```js
ev.done('done','ok');
ev.after('done', (ok)=>{
console.log(ok)
})
// after 成功的接受到了done事件; after 在done 后立即触发, after 保证了顺序.
// done 的事件也可以被listen监听;
- 意外的监听过多
初学者极其容易犯这个错, 旧的回调忘记及时删除;
// 这里只是一种简洁的写法, 实际可能更复杂!
ev.emit('done', 'ok')
function listen(str){
ev.listenAll('done',(ok)=>{
alert(str)
})
}
listen('A');
// 改变主意了, 想让'done' 后alert'B'了,
listen('B'); // 这是错误的, 之前监听'A'的没删掉;
其实名字就已经强调了,
listenAll
会监听所有的回调, 要想自动的删除之前的可以用listen
, 如下:
ev.emit('done', 'ok')
function listen(str){
ev.listen('done',(ok)=>{ // 使用listen
alert(str)
})
}
listen('A');
// 改变主意了, 想让'done' 后alert'B'了,
listen('B'); // ok, 之前监听'A'的删掉了;
注意啦: ev.listen
是根据function.name
来判断的. 如果function.name
是listen过的, 就用新的替代旧的. 所以ev.listen 是不喜欢只用匿名的函数的.(匿名函数的name 为空字符串)
- EventPool只是个小工具, 力推一波: Sage; 集中管理异步
...
2. ApiManager: 集中的控制ajax的, vir.js 的第二个核心, 大约 1月30日推出
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago