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日:
- 确认了数据处理方案: 支持流对象节点用来控制Dom的各种属性,关键字: VirStream
- 统一了Vir中的类型命名, 函数节点的类型名字为 VirFunction
- 新: 添加了对面向对象的支持. 对象中只要实现getRender,高阶方法就可以了, 关键字为VirClass;
- node 端可以跑vir.js了, 目前可以用vir.js 做html模板的生成.
- 提供了工具函数,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 // 绑定到dom // 使用流;
- 结果: 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
二. 简单的使用例子:
className = "box"
), 每个盒子一个big apple:
1. 显示四个盒子(写法1:
写法2: 使用vir.js'原生的'数组自适应, 让你可以控制单个的内容
// 可能有点特别, 反直觉. 有可能你认为这表示一个盒子里四个apple. 注意vir.js的数组节点是分述语法. 详见语法四.8 // 为了不被糊弄写个注释也可以
写法3: 搭配ramda 等类似的库, 让你有望使用函数式开发.
强行安利一波函数式编程: ( 2018-01-15)
案例1: 根据URL数组,生成一组图片:
const data = '''''' { return args: src: v } // 如果又不是img了 // 逻辑逻辑完美的复用
案例2: 还是根据URL数组,生成一组图片, 但是每个图片的样式一样(假设你就是想用js设置样式)
2. 模块开发, 一个模块说我是'model one', 另一个说我是: 'model two'
核心是利用 函数节点
// use typescript// modelOne;; { const sayWords = 'I\'am model one' }//model two { const sayWords = 'I\'am model two' // 想用jquery就用, 自由如此 } // usage 1: // usage 1 等价于如下: usage 2 // 说白了就是有个函数节点, 看看如下, 详细见语法篇
三. vir.js 的优势
- 定位清晰: 就是用js创建dom的.
- 无其他依赖: 目前发布的vir.js 才800来行, 源码都可以一口气看完..
- 有潜力: 这是一个极简先驱版本, 我还开发了许多vir.js的工具, vir.js不久将会上升到framework的高度
- 完全用js 开发的: 实现在前后端同时跑也是可以的;
- 使用方便: 有强大的数组使用方法;
四. vir.js 的语法
"#id .class .classtwo"
1.基本的 id 与class 解析 : #
后紧跟id名, .
后跟class名,class是按顺序的,可以多个;id 与和class 不分顺序,class之间有顺序的;
标签名默认为div;
[key = 'value']
2.属性解析:和html 中写属性值一样;
".parent > .son"
3.子元素解析:在js中:
".parent > .son": $:"son" ///绑定在最后生成的元素上;即为.son上;
创建的是:
son
"3* div"
4.多个元素 : 数字和*黏在一起;不可以!!!!:"3 *div"
。
但是可以:"3*div"
。最好是放在开头,其他地方也可以,但容易出错;
"div ::oneDiv"
5.定义变量 : 创建的div存在oneDiv里。可以多次出现::oneDiv在不同的标签属性里,结果是oneDiv变成了数组,按创建次序记录各个element;还可以与point 4中的乘法搭用,生成数组(不是“反常坑”的HTMLcollection)。
"div ::parentDiv > 3*div ::threeChild"
6.混搭 :js代码:
var test =
生成html如下:
son son son
使用变量threeChild;
// 使用变量threeChild;testargsthreeChild
改动效果如下html:
use use use
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 数组中合法的类型:
注意:
- 不支持Symbol类型用在数组里
- null 和 undefined: null不显示, undefined会被渲染成字符串'undefined';
- 尾部的
.item
没有声明数量和声明的数量为1, 则.item
的数量为其后数组的长度. 若是声明的数量大于1, 则溢出的会忽略, 不足的为'undefined'
8.2 分述、复述
js代码为:
// 分述 // 复述
分述在节点值为数组时触发;否则为复述
生成html为:
<!-- 分述 --> son1 son2 son3 <!-- 复述 --> son son son
ps: 复述, 我觉的就生成个棋盘啥的有用了...
8.3 在特殊的属性$中使用数组
其实和直接写在节点尾部是一样的, 只是使用$写, 可以写额外的style, on 等等
实现点击blue弹出blue, 点击red弹出red
9. 属性的注释
js 对象里同级的属性名是唯一的, 虽然8.* 中可以通过数组批量的生成节点, 但是数组不适用所有的情况. (写文章的话, 不要用vir.js , vir.js 是用来开发应用的, 推荐: MarkDown;)
使用';' + ' '
, 即分号 + 空格
, 放在属性的开头, 表示注释;
// 属性名只能唯一, 使用带注释的属性名. 我觉的这个不仅能解决问题还解决的不错.
五. 来自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'
;const ev = ; ev { ev} // 延时
- EventPool实现的是基于事件名的异步解耦合, 来个高级的例子:
场景: 有一个任务流程: A -> B -> C, 运行到B时需要满足一定的条件才可以继续 运行到C. 如何在外部去控制B 的状态呢?? 通俗的说: 问题是指如何传个开关给函数, 外边的人拿着这个开关控制这个函数的运行? 如何搞出这个开关?
;// A -> B -> C; { // do A B}const ev = ; // set B, B is a Promise object // ev.listen('EveCode') , 单参数,返回的Promise 对象. ev
- Promise的优点
普通的事件处理有时会有意外
例如: 先触发了事件, listen晚了, 导致最后一次的触发没监听到.
// 这里只是一种简洁的写法, 实际可能更复杂!ev ev
Promise不会有这种问题, Promise 有状态记录, 无论在哪then 都可以.
-
done, after 的事件模型(内存的消耗多了点, 不要用太多就ok)
Promise 是一个稳定的开关, 状态只会改变一次.
一个Promise后的then 里的函数只会执行一次; 不利于事件常常触发的场景(也可以使用, 只是有点蹩脚)
done, after继承了Promise 的优点, 无论何时done , after总是可以触发;
ev;ev// after 成功的接受到了done事件; after 在done 后立即触发, after 保证了顺序.// done 的事件也可以被listen监听;
- 意外的监听过多
初学者极其容易犯这个错, 旧的回调忘记及时删除;
// 这里只是一种简洁的写法, 实际可能更复杂!ev { ev}; // 改变主意了, 想让'done' 后alert'B'了, ; // 这是错误的, 之前监听'A'的没删掉;
其实名字就已经强调了,
listenAll
会监听所有的回调, 要想自动的删除之前的可以用listen
, 如下:
ev { ev}; // 改变主意了, 想让'done' 后alert'B'了, ; // ok, 之前监听'A'的删掉了;
注意啦: ev.listen
是根据function.name
来判断的. 如果function.name
是listen过的, 就用新的替代旧的. 所以ev.listen 是不喜欢只用匿名的函数的.(匿名函数的name 为空字符串)
- EventPool只是个小工具, 力推一波: Sage; 集中管理异步
...