vir.js
TypeScript icon, indicating that this package has built-in type declarations

1.2.5 • Public • Published

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了;
  • 分析:
    1. src 满足基本的流接口: write, read, pipe
    2. src.write后数据同过pipe流到了iframe的src里;
  • 注意:
    1. 为了稳定 VirStream 只允许用于绑定dom的某个属性,
    2. 其他的高级功能, 可以结合函数节点和接下来的对象节点实现,vir.js懒得实现了
    3. 其实实现了也不想发布, 下个版本再说;

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. 其他使用: 例如

  1. vir.js 使用的webpack打包的(umd)
  2. 浏览器导入 <script src="node_models/vir.js/dist/vir.js"></script>
  3. 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 的优势

  1. 定位清晰: 就是用js创建dom的.
  2. 无其他依赖: 目前发布的vir.js 才800来行, 源码都可以一口气看完..
  3. 有潜力: 这是一个极简先驱版本, 我还开发了许多vir.js的工具, vir.js不久将会上升到framework的高度
  4. 完全用js 开发的: 实现在前后端同时跑也是可以的;
  5. 使用方便: 有强大的数组使用方法;

四. 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, 但也可以是数组.

  1. on 绑定事件的地方. on为函数时自定义把绑定, 'div .test':{ on(ele){ /* do what you want */ } } , ele 为HTMLELement;
  2. style 绑定样式的地方
  3. args 绑定生成的HTMLELement的属性如className, 或自定义: 如key; (index 被Vir.js用了!)
  4. data 绑定到dataSet上, 原生dom的安全数据接口, 优点是css里可以访问到(绝对黑科技!),
  5. $ 显式的绑定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 对象, 使用这个对象管理事件的触发和监听;

  1. 超简单而直接的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)
  1. 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!')
 
  1. Promise的优点

普通的事件处理有时会有意外

例如: 先触发了事件, listen晚了, 导致最后一次的触发没监听到.

// 这里只是一种简洁的写法, 实际可能更复杂!
ev.emit('done', 'ok')
 
ev.listenAll('done',(ok)=>{
  alert(ok)
})

Promise不会有这种问题, Promise 有状态记录, 无论在哪then 都可以.

  1. done, after 的事件模型(内存的消耗多了点, 不要用太多就ok)

    Promise 是一个稳定的开关, 状态只会改变一次.

    一个Promise后的then 里的函数只会执行一次; 不利于事件常常触发的场景(也可以使用, 只是有点蹩脚)

done, after继承了Promise 的优点, 无论何时done , after总是可以触发;

ev.done('done','ok');
ev.after('done', (ok)=>{
  console.log(ok)
})
// after 成功的接受到了done事件; after 在done 后立即触发, after 保证了顺序.
// done 的事件也可以被listen监听;
  1. 意外的监听过多

初学者极其容易犯这个错, 旧的回调忘记及时删除;

// 这里只是一种简洁的写法, 实际可能更复杂!
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 为空字符串)

  1. EventPool只是个小工具, 力推一波: Sage; 集中管理异步

...

2. ApiManager: 集中的控制ajax的, vir.js 的第二个核心, 大约 1月30日推出

Package Sidebar

Install

npm i vir.js

Weekly Downloads

0

Version

1.2.5

License

ISC

Unpacked Size

194 kB

Total Files

40

Last publish

Collaborators

  • eoyo