Closed John0King closed 3 years ago
这示例代码怎么一堆问题,引号、this.filter
、this.set
......
我试了下没这问题,san 没有用 checked 做什么内部跟踪机制。我怀疑是你代码哪里有问题。下面是我的测试代码,有问题留言
var MyApp = san.defineComponent({
template: `<div>
<div s-for="item,index in list"> <input type="checkbox" checked="{{item.checked}}" /></div>
</div>`,
attached() {
setInterval(() => {
var list = this.data.get('list');
var isAllChecked= this.data.get('isAllChecked');
for (var i =0; i<list.length; i++) {
this.data.set('list['+i+'].checked', !isAllChecked)
}
}, 1000)
},
computed:{
isAllChecked() {
var list = this.data.get('list');
var len = list.length;
while (len--) {
if (!list[len].checked) {
return false;
}
}
return true;
}
}
})
var myApp = new MyApp({
data: {
list: [
{checked: true},
{checked: true}
]
}
});
myApp.attach(document.body)
这个问题可能是其他领域(change detector)的问题导致的, 我目前没有完全重现这个问题 根据我的测试, san 的变更检测 在视图层和 逻辑层使用的变更检测不一致 我简单的理解为
然后问题导致了 在 computed 等地方(或者 视图绑定的方法上) 业务逻辑的数据是正确的, 但是视图的状态跟它脱节了
https://github.com/John0King/san-demo-check 这是我的 demo, 里面app.js 的第一行代码 控制了两种 对 toggleAll 方法 变更数据的方式, 两种方法, 逻辑层的 data总是正确的, 但是 视图层却不会更新
我这个还是没有把我遇到的问题测全, 实际上我之前提交过一个 bug 关于双向绑定和数组的, 不过因为网络问题,好像提交没有成功, 在这个测试里面没有测出来, 因为测试那个需要至少 父级和子级两个组件。
推测的问题:
null | undefined
, 而不是数组ref
值 没有更新 @errorrik 我简略的看了下文件结构(和非常小的一部分代码),
我觉得这个 changeDetector 的设计问题, 设计之初可能你们想要跟 vue一样 尽可能的局部更新,所以你们利用值是否相等来判断两个值是否相等, 但是与 vue不同的是, vue 会把 所有 data 里面描述的字段,全部变更为 Object Proxy 属性, 比如:
list[3].name = 3
, 首先 list 本身是继承数组的 子类数组,它模拟了几乎数组的所有操作,比如 pop, push, shift 等等, 但是无法模拟 list[99] = { }
这样的添加( 当然vue3 利用了 ObjectProxy 已经 不存在此限制) 他的这种方式保证了 变更通知和发起变更事件总是同步的, 但是 san采用的另外一种 proxy, 所有的操作都采用 data 这个代理来委托变更, 但是这造成了, 它不知道变更的来源是 下级还是父级 , 比如 data.set('list',list)
, 他必须去检测每个元素有没有变更(子集变更) 同时要向 watch
提交 父级事件 (但是问题出现在 这个 相等性比较上,或者没有向子集去变更检测), 所以我感觉 他还不如使用 this.detectChange()
这种批处理 统一去管理变更 (视图更新和 watch事件), 而且也不用再去处理字符串命名 和 变量名的双重变量名问题。(当然这一点得看你们的主体设计方向了)
考虑下这样的操作
san.defineComponent({
initState(){ //为了兼容老版本,data本身重命名为 state, initData 仍然是 变更委托 , initData 跟他一样,
//这里是觉得如果 state 变更是推荐的,所以这里也跟着改了名
return {
list:[]
}
},
attached(){
this.state.list.push({ name:'1' });
this.detectChange();
},
someClick(){
//老方法理论上可以兼容理论上可以兼容
this.data.push('list', { name: 'old-name' }); // 给 state.list push 并且执行 detectChange 方法
}
})
@errorrik 能否解答下,是不是如我所想, 除了 后端代码 有 变更检测, 视图又有自己独立的变更检测? 如果是的话,我觉得应该学习一下 angular 的方式由统一的 变更检测 发起对 watch 和 视图的更新, 至少不能让状态跟视图完全脱节
这里的原因没有那么复杂。通过方法操作数据变化,数据的变化肯定是不会丢的,不会出现老 vue 的问题。
但是为什么会出现你说的这种情况?因为我们认为,当 数据没有发生变化的时候,视图就不应该更新。那什么叫 数据没有发生变化 ?我们认为 immutable 是个好的想法,大家也比较好理解,毕竟社区一直在谈论,也这么多年了。data 的操作就是基于这个想法去封装的,所以你例子里的现象,是 by design 的。
我觉得,这个文章可能对你有所帮助 https://efe.baidu.com/blog/san-perf/#immutable
@errorrik 完全用 immutable 来当然是没有问题的, 问题是在没有类型辅助的情况下,immutable很难做到, 不过 typescript 有个 Readonly helper , type 应该使用这个来帮助阻止非 immutable 更新 eg.
export class Data<TData=any>
{
// 本来想 Readonly<Parital<TData>> 来着,不过exp会多层
get<T=any>(exp:string,option?:{ force?:boolean,slience?:boolean }): Readonly<T> {
//... do somthing
}
}
immutable 的变更检测是自下而上的(child->root),子更新导致从子更新到root完全更新,而“下”不够彻底就是这个问题的根源(变更发生在开始这个“下”的更下面), 可以考虑增加一个 this.detechChange()
方法, 强制 变更检测 重新 自上而下 检测变更,同步视图状态。
immutable 作用是在变更时,不是检测时。变更时自下而上的引用全变,从而检测只需要 ===。
和类型辅助没啥关系。也无需增加方法强制触发,强制触发已经有 force
参数了。
另外,immutable 还有另外的好处。
// data: {person: {name: 'foo'}}
let person = this.data.get('person');
person.name; // foo
this.data.set('person.name', 'bar');
person.name; // 如果不是 immutable,就会莫名其妙影响到 person 对象,就会是 bar。期望应该不被影响,是foo
@errorrik force 参数只会触发自己,而不会触发 子级, Vue 就有个 $forceupdate()
来帮助解决 视图与状态不对应的问题。
另外来说下 我上面提到的 Readonly<T>
这个 helper type, 这个是 typescript 内置的帮助类型之一, 用来将一个 可变对象 当成不可变对象来出来(他没有实现真正的不可变,而是在typescript 类型基础上 不能变更)
比如: type Foo = { a:string; b:number }
而 type ImmutableFoo = Readonly<Foo>
意思是
type ImmutableFoo = {
readonly a:string;
readonly b:number
}
可以看到任何对成员的赋值都是不允许的,
@John0King 我觉得,我们在讨论的点可能有些偏差
你可以从 repo 里看到新的(还没 release)的 .d.ts ,里面增加了对 data 的类型推断,我们希望用 ts 的开发项目能更便捷。
但我之前直接回避了这个东西。原因有2:
至于 Readonly 要不要应用于 data.get ,如果想讨论可以单开一个 issue
为什么说不需要一个方法,只要 force 参数就行?这是整个机制决定的。
force 参数确实只会发出当前变更数据项的事件,不包含子项,但是视图认为 什么变更数据项影响了我 ,这个实现才是视图更新的决定因素。下面这个简单的例子可以更清楚的展示效果。
var MyComponent = san.defineComponent({
template: '<div>{{person.name}}</div>',
});
var myComponent = new MyComponent({
data: {
person: {name: 'one'}
}
});
myComponent.attach(document.body);
setTimeout(function () {
var p = myComponent.data.get('person');
p.name = '1';
// 看看下面两行效果有什么不同
myComponent.data.set('person', p, {force: true});
// myComponent.data.set('person', p);
}, 1000);
可以看到,person 的 force,让 person.name 引用的视图发生了变更, 即使没有 immutable。
当然,当次变更没有问题,但在复杂的业务流中,可能会玩脱,造成意料之外的影响。所以,还是建议细粒度走数据方法来更新数据
预期的结果
循环中的复选框跟随 列表项的
checked
选中和补选中实际结果
列表中的项 不会跟随
item.checked
,其他的说明
将
item.checked
改为item.isChecked
, 该问题得到修复, 所以这应该属于bug, 推测可能跟 san内部的跟踪机制有一定关系, 建议内部的跟踪属性修改成____$checked__