baidu / san Goto Github PK
View Code? Open in Web Editor NEWA fast, portable, flexible JavaScript component framework
Home Page: https://baidu.github.io/san/
License: MIT License
A fast, portable, flexible JavaScript component framework
Home Page: https://baidu.github.io/san/
License: MIT License
如题:
1, checkbox表单元素点击后只有html层面的选中/取消选中效果, 双绑的数据值无更新(但默认选中态可判定)
2, radio 元素如果在传递的直接data值中数据类型为number(data: 1), 然后直接转换为string类型(data: '1'), 无法判定默认选中态. 但如果双绑的值不为直接的data值, 比如为转换过的 新设置的newData: "1", 则可以判定默认选中态
https://github.com/ecomfe/san/blob/91dc49af96fba09644c27508f676d9bd21e2753c/src/main.js#L3336
/* eslint-disable operator-linebreak */
/**
* 清空从已有的el进行初始化的行为
*
* @param {Object} options 初始化参数
*/
Component.prototype._initFromEl =
/* eslint-enable operator-linebreak */
另外,全部逻辑都在一个4k+的单文件?
点击 'Add Item' 按钮时,向 cols[0].list
push 了一个对象,然后在控制台里打印 cols
。
一个 <li>
标签被插入视图,并且 cols
字段打印结果为 [{list: [{title: 'title'}]}]
。
一个 <li>
标签被插入视图,cols
字段打印结果为 [{list: []}]
。
推测可能是 this.data.get
获取模型的引用有偏差。
true // <- log(true)
false // <- log(false)
You can never see me if san-if="true"
.
You can see me although san-if="false"
.
undefined <- log(true)
It's a variable named 'false' <- log(false)
在 san-if
指令中使用匿名变量没有任何意义,这里只是用于列举一种情况。
const Children = san.defineComponent({
template: `<ul>
<li>Boolean Prop: {{booleanProp}}</li>
<li>Named Variable Prop: {{namedVariableProp}}</li>
<li>Number Prop: {{numberProp}}</li>
</ul>`
})
const Parent = san.defineComponent({
components: {
'child-comp': Children
},
template: `<div>
<child-comp
booleanProp="{{true}}"
namedVariableProp="{{false}}"
numberProp="{{666}}">
</child-comp>
<p san-if="!true">You can never see me if \`san-if="true"\`.</p>
<p san-if="false">You can see me although \`san-if="false"\`.</p>
<button on-click="log(true)">console.log(true)</button>
<button on-click="log(false)">console.log(false)</button>
</div>`,
initData () {
return {
false: `It's a variable named 'false'`
}
},
log (msg) {
console.log(msg)
}
})
const comp = new Parent()
comp.attach(document.body)
定义 Grandpa
Father
Son
三个组件,假设每个组件都是上一个的父组件,此时如果在 Son
中注册 Grandpa
组件会失效。目前看来 san 组件似乎只有自递归的能力,但是如果一个需要递归的组件过大,逻辑过于复杂,为可维护性考虑就避免不了拆分问题,于是就产生了隔代递归的需求。
https://codepen.io/Dafrok/pen/qjmWdg?editors=1010
这是一个简单的 JSON 可视化组件,注册了 Leaf
Branch
JsonNode
三个组件,每一个 JSON 的节点对应一个 JsonNode
组件实例。
JsonNode
中注册了 Leaf
和 Branch
,两个子组件,如果当前属性是对象或数组,则渲染 Branch
组件,如果是其它值类型则渲染 Leaf
组件。
Branch
组件上会注册一个 JsonNode
组件用于渲染当前 JSON 节点的所有成员。
根据这个逻辑,JsonNode
到 Branch
再回到 JsonNode
形成了一个隔代递归,san 无法处理。
// pug template
.item
.leaf foo: 233
.leaf bar: bar
.branch baz:
.item
.leaf a: 1
.leaf b: 2
.branch qux:
.item
.leaf 0: 6
.leaf 1: 6
.leaf 2: 6
// pug template
.item
.leaf foo: 233
.leaf bar: bar
.item baz:
json-node
.item qux:
json-node
var Foo = san.defineComponent({
template: '<template><div>value is: {{value}}</div><input type="{{boxType}}" value="bj" checked="{=value=}" /></template>',
initData: function() {
return {
boxType: 'radio',
value: ''
};
}
});
var Bar = san.defineComponent({
template: '<template><div>value is: {{value}}</div><input type="radio" value="bj" checked="{=value=}" /></template>',
initData: function() {
return {
value: ''
};
}
});
var App = san.defineComponent({
components: {
'ui-foo': Foo,
'ui-bar': Bar
},
template: '<template><ui-foo/><ui-bar/></template>'
});
var app = new App();
app.attach(document.body)
对于 Foo
这个组件,bindInfo.raw
是 {{boxType}}
,不是 radio
或者 checkbox
当组件存在父子关系时,父组件的 created 钩子函数被执行了两遍,且子组件的 created 钩子函数获取实例的 el 属性为 undefined,父子组件在相同生命周期中的表现不一致。
https://codepen.io/Dafrok/pen/oWEZYE?editors=1010
创建有父子关系的两个组件并挂载,分别在 create 周期时在控制台输出当前实例的 el 属性。
parent el: <div id="_san_1">…</div>
children el: <div id="_san_2">…</div>
parent el: <div id="_san_1">…</div>
children el: undefined
parent el: <div id="_san_1">…</div>
san 在某些场景(通常是使用 s-if / s-for 指令时)下会在父节点的第一个节点或最后一个节点中插入一个 script 标签占位,使 CSS selector 误判。
ul
li
color green
li:first-child
color blue
li:last-child
color red
const App = san.defineComponent({
template: `<ul>
<li s-for="item in list">{{item}}</li>
</ul>`,
initData () {
return {
list: [1, 2, 3]
}
}
})
const app = new App()
app.attach(document.body)
<ul>
<li>1</li><!-- 蓝色 -->
<li>2</li><!-- 绿色 -->
<li>3</li><!-- 红色 -->
</ul>
<ul>
<li>1</li><!-- 蓝色 -->
<li>2</li><!-- 绿色 -->
<li>3</li><!-- 绿色 -->
<script type="text/san"></script>
</ul>
https://codepen.io/Dafrok/pen/LLQexv
使用特殊 comment tag 代替 script 标签做占位符。
和 @jinzhubaofu 讨论后,确定:
然后是形式:
太大了,而且只能支持json定义的类型(我没好好读spec,不知道对date这种常用的复合类型支持的怎样)
https://github.com/facebook/prop-types
这种方式,除了支持基本的类型外,还:
PropTypes.arrayOf(PropTypes.shape({id, age}))
?dataType= {
name: String,
age: Number,
addr: {
provider: String,
city: String
}
}
这种方式,除了支持基本的类型外,最多能支持深层次结构,但是很多值相关的就做不了了。当然,可以开放支持function来定制校验规则。如果内置校验规则,和 react propType 也没毛区别。
@otakustay @jinzhubaofu 你俩觉得选哪种好
Element.prototype._created
和 Element.prototype.attach
里面都有调用 bindEvents
的逻辑
除去使用slot等场景,自定义的组件都不会有内容,此时如果还要写闭合标签太累,希望在所有标签上都能支持自闭合,即<ui-calendar />
可以,<input />
可以,<div />
也可以
var Foo = san.defineComponent({
template: ''
+ '<template>'
+ '<input type="text" value="{=value=}" />'
+ '<div san-if="{{isFoo}}">is foo</div>'
+ '<div san-if="{{!isFoo}}">is not foo</div>'
+ '</template>',
computed: {
isFoo() {
const value = this.data.get('value');
return value === 'foo';
}
},
initData() {
return {
value: 'xxoo'
}
}
});
var foo = new Foo();
foo.attach(document.body);
如果把 <div san-if="{{!isFoo}}">is not foo</div>
改成 <div san-else>is not foo</div>
,那么是符合预期的。
或者改成 <div san-if="{{value !== 'foo'}}">is not foo</div>
也是符合预期的。
考虑以下两种情况:
<san-panel></san-panel>
<san-panel>
</san-panel>
目前 san 认识第一种情况无 default slot 内容,而第二种有填充,填充内容是一个空白字符\n;
这会影响到这种情况:
<template>
<div>
<slot>{{tilte}}</slot>
<slot name="icon" />
</div>
</template>
此时使用者换不换行就会造成两种不同的结果:
<sm-panel title="test"><sm-icon slot="icon>more</sm-icon></sm-panel>
有 title 输出
<sm-panel title="test">
<sm-icon slot="icon>more</sm-icon>
</sm-panel>
无 title 输出,因为有两个 \n 填充给了 default slot
解析到 text-node 可以理解,但是这对使用容易造成困惑。是否有更好的解决方案?
it("dynamic expr long path", function (done) {
var wrap = document.createElement('div');
document.body.appendChild(wrap);
var MyComponent = san.defineComponent({
template: ''
+ '<div>'
+ '<div san-for="item, index in config">'
+ '<div>{{item.name}}</div>'
+ '<div san-for="child, ci in item.children">'
+ '{{index}} - {{ci}} : <input value="{=result[child.name]=}"/>'
+ '</div>'
+ '</div>'
+ '</div>',
initData: function() {
return {
result: {},
config: [
{
name: 'erik',
children: [
{
name: 'erik-child-0'
},
{
name: 'erik-child-1'
}
]
}
]
}
},
attached: function() {
var input = wrap.getElementsByTagName('input')[0];
triggerEvent('#' + input.id, 'input', '666');
setTimeout(this.doneSpec.bind(this), 600);
},
doneSpec: function() {
var result = this.data.get('result');
expect(result.erik).toBe('666');
this.dispose();
document.body.removeChild(wrap);
done();
},
});
var myComponent = new MyComponent();
myComponent.attach(wrap);
});
var MyApp = san.defineComponent({
template: '<select value="{=v=}">'+
' <option></option>'+
' <option san-for="item in list" value="{{item}}" san-if="item==2">{{item}}</option>'+
'</select>',
initData: function () {
var data ={
list:[],
v:20
};
for(var i=0;i<21;i++){
data.list.push(i);
}
return data;
}
});
var myApp = new MyApp();
myApp.attach(document.body);
在vuejs里是可以这么使用的
<a title="{{1 + list.length}}" san-for="item in list">{{item}}</a>
initData: function () {
return {
list: [1,2],
this.data.push('list', 3)
RT
传递自定义事件的参数使用的判断是 event || window.event,这样在真正想传递 false 或者0或者空串的时候,传递到回调中的却是 window.event
san.defineComponent({
components: {
'ui-user': UserInfo
},
template: '<div><ui-user info="{{md.user}}"></ui-user></div>'
});
当 data.set('md.user.name')
时,更新user组件内部数据项的info.name,不要灌入整个info
最近在使用san的时候发现,san的component的created方法会调用两遍
于是clone了最新版的san, 使用example里的todos_esnext在list.san里进行验证 同样发现created执行了两遍 生命周期的顺序是正常的
下面是控制台打印的内容
List.san?7f39:67 compiled
List.san?7f39:63 inited
List.san?7f39:59 created
List.san?7f39:59 created
List.san?7f39:55 attached
由于在一个项目中使用了由 store 控制的无限嵌套组件,所以在操作 store 的时候只能在一个计算属性中使用递归的方式来寻找某一个层级的组件所对应 store 中的 key string。而这样的操作使表单元素的绑定产生了三个问题:
https://codepen.io/Dafrok/pen/LyqQOo?editors=0110
递归生成的 input 元素没有办法顺利地更新 store。虽然我也可以在单向绑定的情况下记录光标的位置,更新视图后重置光标,但是显然这么做是非常没有效率的。如果这些问题是 feature 所致,还望指教正确的使用姿势。
模板声明事件时,如果实例方法传入的参数是一个匿名对象的引用,san 会在实例化时抛出一个 TypeError。
.san 组件的 scoped css 不生效
san-loader 生成的 scoped id 的格式是 _s-[HASH]
,san 在处理 attribute 的时候会忽略下划线,于是属性变成 s-[HASH]
,以 s-
开头的 attribute 会被当做指令来处理,最后被 parser 干掉。
我暂时想到以下两个解决方案,具体使用哪一个可能需要推敲一下:
将生成的属性 _s-[HASH]
改为 data-san-[HASH]
或其它合法属性名。
我在本地暂时用的是这个解决方案,改动和副作用较小,但会占用一个合法的属性名,留下命名冲突的隐患。如果选用这个方案,我可以把本地的改动直接发 pr 给 san-loader。
这个方案需要同时修改 san-loader 和 san 的源码,改动量较大。san-loader 生成一个特殊的属性名,如_s_scoped_[HASH]"
,san 再去优先匹配 \_s_scoped.+\
规则,符合则当做正常 html attribute 进行处理,好处是在用户按照规范声明 prop 或 html attribute 时不存在撞名的隐患。
ESNext 是无法声明 prototype property 的。所以,对于 template / filters / components 等属性,San 提供了 static property 的支持。
实际上不嫌难看的话,可以用ESNext这么搞:
class HelloComponent {
get template() {
return '<p>Hello {{name}}!</p>';
}
initData() {
return {name: 'San'}
}
}
// test
HelloComponent.prototype.template && console.log(HelloComponent.prototype.template)
目前如果 san template 编写错误,会得到很笼统的错误提示。
比如在 template 很深的位置漏写属性的双引号 <xxx style={{xxx}} />
,san 给出的错误提示是:template 需要一个根元素。
这个错误提示很难帮忙开发者快速定位到问题所在的位置。
建议能够优化。
3.0.3-rc.6
模板
<span class="page-num" san-if="current - 2 < totalPage">{{ current - 2 }}</span>
<span class="page-num" san-if="current - 1 < totalPage">{{ current - 1 }}</span>
<span class="page-num" san-if="current < totalPage">{{ current }}</span>
<button on-click="changeVal()">change</button>
初始值
{
totalPage: 5,
current: 5
}
事件处理
{
changeVal() {
this.data.set('current', 19);
this.data.set('totalPage', 19);
}
}
操作
点击 button
空
<span>17</span><span>18</span>
在第一个 set 产生了两个 dispose child task,来移除第 1 和2 个 span;
而第二个 set 产生了两个 update child task,来更新第 1 和 2 个 span;
根本原因是 tasks 是异步执行的,view 在 tasks 执行完成前与 model 不一致。两次 set 都对基础状态的 view 做了 diff。第二次 set 应该基于上一次 set 后的 view 来计算 diff。
如果每次 set 都是同步做全量 diff 的话,将 task queue 清空即可。
一个文件阅读起来好累……
目前san的浏览器兼容性如何,是否兼容ie8
先上测试代码:
var MyApp = san.defineComponent({
template: ''
+ '<div>'
+ '<input value="{= name =}" placeholder="please input">'
+ 'Hello {{name}}!'
+ '</div>'
});
var myApp = new MyApp();
myApp.attach(document.body);
3.0.0-rc.3
在双绑text
类型的input
元素时,输入中文会出问题:
翻了下代码,应该是因为直接绑定了input
事件,但没处理compositionstart
与compositionend
的状态导致的。
<input san-if="checked" type="checkbox" checked >
<input san-else type="checkbox" >
RT
var BoxGroup = san.defineComponent({
template: '<template><div class="box-group">'
+ '<div san-for="item in datasource">'
+ '<label><input type="checkbox" value="{{item.value}}" checked="{=value=}" /><span>{{item.text}}</span></label>'
+ '</div>'
+ '</div></template>',
initData: function() {
return {
datasource: [],
value: []
};
}
});
var Issue = san.defineComponent({
template: '<div><slot/></div>'
});
var App = san.defineComponent({
components: {
'ui-boxgroup': BoxGroup,
'x-issue': Issue
},
template: '<template>'
+ '<button on-click="onClick">Clear</button><hr/>'
+ '<x-issue san-for="p in groups">'
+ '<div>value: {{p.value}}</div>'
+ '<ui-boxgroup datasource="{{p.datasource}}" value="{=p.value=}" /><hr/>'
+ '</x-issue>'
+ '</template>',
initData: function() {
return {
groups: [
{
datasource: [
{text: 'foo', value: 'foo'},
{text: 'bar', value: 'bar'}
],
value: []
},
{
datasource: [
{text: 'abc', value: 'abc'},
{text: '123', value: '123'}
],
value: []
}
]
}
},
onClick: function() {
this.data.set('groups[0].value', []);
this.data.set('groups[1].value', []);
}
});
var app = new App();
app.attach(document.body)
如果把 <x-issue
换成 <div
就一切正常了
我这边整理了一些,和现在的实现有差异,帮我指出一下应该怎么修改吧:https://github.com/ecomfe/san-view/tree/lifetime/meta
一个比较意外的情况是,if
和for
等信息现在是放在directives
下的,我原来以为会是一个Node,而整个ANode的树里是没有directives
这个东西的
http://codepen.io/Dafrok/pen/OmrqgZ?editors=1010
<select>
元素值为 'bar'
<select>
元素值为 'foo'
描述:由于 san 的 Element
是在 genHTML
过程中初始化子组件的,而在子组件的初始完成时后可以通过 dispatch 来修改父组件的数据。这导致整个过程中的数据不一致问题,表现在 template
中位于子组件之前的 dom 渲染使用的是 dispatch 前的数据,而位于子组件之后的 dom 渲染为新数据。
san 单文件组件目前不支持 less 解析,单文件样式代码如下:
<style lang="less">
@brand-color: blue;
.hello {
color: @brand-color;
}
</style>
webpack 2 配置如下:
module: {
rules: [
{
test: /\.san$/,
use: 'san-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
include: [appPath]
},
{
test: /\.less$/,
use: 'css-loader!less-loader'
},
{
test: /\.css$/,
use: 'css-loader'
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: 'url-loader?limit=10000'
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: 'url-loader?limit=10000'
}
]
}
是配置有误,还是目前不支持呢?后续会提供支持吗?
var Foo = san.defineComponent({
template: ''
+ '<template>'
+ '<div san-for="item in items">'
+ '<div class="row">'
+ '<label>{{item.label}} - {{item.values|join}}</label>'
+ '<div class="boxes">'
+ '<span san-for="box in item.datasource">'
+ '<input type="checkbox" value="{{box.value}}" checked="{=item.values=}"/> {{box.title}}'
+ '</span>'
+ '</div>' // end of boxes
+ '</div>' // end of row
+ '</div>'
+ '</template>',
filters: {
join(value) {
return value.join(',')
}
},
initData() {
return {
items: [
{
label: 'A',
datasource: [
{
title: 'foo',
value: 'foo'
},
{
title: 'bar',
value: 'bar'
}
],
values: ['foo']
},
{
label: 'B',
datasource: [
{
title: 'foo',
value: 'foo'
},
{
title: 'bar',
value: 'bar'
}
],
values: ['bar', 'foo']
}
]
}
}
});
var foo = new Foo();
foo.attach(document.body);
这个地方 checked="{=item.values=}"
是我用错了吗?
在实现 san 的过渡动画 HOC 的过程中,进入阶段可以在 created 和 attached 生命周期中给实例的 el 添加 css hook 来实现淡入效果。但当实例的 san-if 指令变为 false 时,将会自动进行 detach 和 dispose,此时 el 已同步销毁,无法通过插入 css hook 的方法执行淡出动画。
此段代码仅作抛砖引玉,如果能有更合理的设计或直接将过度动画集成到 core 是缀吼的。
class SanComponent extends Component {
async beforeDetach () {
this.stopLifeCyle()
await doSomethingAsync()
this.nextLifeCycle() // 或 this.detach()
}
}
引入了异步的生命周期,可能引发 model 层和视图层不同步的问题。如 san-if 值改变两次后,第一个生命周期还没有结束,导致生命周期混乱。
比如在todo-amd
的基础上,声明一个App
组件:
define(function (require) {
var san = require('san');
return san.defineComponent({
template: '<ui-list></ui-list>',
components: {
'ui-list': require('./List')
}
});
});
这个组件仅渲染一个List
组件,是无法使用的,如果声明为<div><ui-list></ui-list></div>
就能使用
我觉得这是一个问题,主要原因是:
MoneyInput
可能就是<ui-text-box type="number" postfix="$">
,这个组件的唯一作用就是指定一堆属性的固定值,作为组件系统中模板的复用方案很常见AuthFail
可能是<ui-dialog on-ok="backToLogin" on-cancel="backToLogin">您登录超时,请重新登录</ui-dialog>
,这些组件是基于容器对业务的封装,使得模板看上去能更简洁地表达业务,也具备更好的复用性如果在这些场景下,都要求再多套一层DOM Element会造成开发的不便和最终DOM结构的复杂,特别对于使用Dialog
这样的场景,多套一层可能整个定位、拖动都会被破坏
因此我们在基于aNode
渲染的时候,对根节点最好也采取和子节点一样的策略,随着Element和Component的不同进行不同的渲染(其实把Element和Component归一化后就不会有这问题了),然后利用递归不断创建子元素会好些
var Issue = san.defineComponent({
template: '<template><div><slot/></div></template>'
});
var Foo = san.defineComponent({
components: {
'x-issue': Issue
},
template: ''
+ '<template>'
+ '<div san-for="item, index in items">'
+ '<div class="row">'
+ '<label>{{item.label}}</label>'
+ '<x-issue>'
+ '<button on-click="addText(index)">Add</button>'
+ '<table border="1px"><tr san-for="o in item.datasource"><td>{{o.label}}</td></tr></table>'
+ '</x-issue>' // end of x-issue
+ '</div>' // end of row
+ '</div>'
+ '</template>',
addText(index) {
this.data.push('items[' + index + '].datasource', {label: Date.now()});
},
initData() {
return {
items: [
{
label: 'A',
datasource: []
}
]
}
}
});
var foo = new Foo();
foo.attach(document.body);
如果把 <x-issue>
换成 <div>
就没有问题了,所以有点儿奇怪。
https://codepen.io/Dafrok/pen/LyqdxG
更改任何一个 input 值时,其余的标签都会同步成当前值。
更改有红色标识的 input 值时,其余的标签并没有被同步,model 层也没有发生变化。
var Block = san.defineComponent({
template: '<template><div class="x-block"><slot/></div></template>'
});
var Foo = san.defineComponent({
components: {
'x-block': Block
},
template: ''
+ '<template>'
+ '<x-block san-if="f">{{foo}}</x-block>'
+ '<x-block san-else>{{bar}}</x-block>'
+ '</template>',
initData() {
return {
f: false,
foo: 'foo',
bar: 'bar'
}
}
});
var foo = new Foo();
foo.attach(document.body);
页面打开之后,如果在控制台里面执行 foo.data.set('bar', '123')
,页面应该更新,但是没有。
请看一下下面这种栗子的情况:
<template>
<div class="container">
<div class="items" san-for="item,index in list"></div>
</div>
</template>
<script>
// 假设引了jq
export default {
attached() {
let doms = $(this.el).find('.items');
// doms啥也没有
}
}
</script>
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.