Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于在mobx中如何observe观察深层的数据变动 #97

Open
ckinmind opened this issue Mar 14, 2017 · 13 comments
Open

关于在mobx中如何observe观察深层的数据变动 #97

ckinmind opened this issue Mar 14, 2017 · 13 comments
Labels

Comments

@ckinmind
Copy link
Owner

ckinmind commented Mar 14, 2017

背景:

发现在mobx中似乎深层次的数据变动有时候不会触发新的render, 例子如下:

// 在store中
class SpaceStore {
    @observable treeData = {
              id: 100,
              name: 'test',
              children: []
      };
}


//在组件中
const store = new SpcaceStore();
class Space extends Component {


     componentDidMount(){
        console.log('componentDidMount')
        store.treeData.name = 'hehe';
     }
  ...
  render(){
    console.log('this is render');

    //第一种情况,打印整个treeData(需要注释第二种情况)
    console.log(store.treeData)

   //第二种情况,打印treeData中的name(需要注释注释第一中情况)
     console.log(store.treeData.name)

  //第三种,解除前两条的注释

    return (
        <div>test</div>
    );
  }
}
  1. 第一种情况(第二种情况是注释的),打印出的内容以及顺序如下
this is render
Object
componentDidMount

可以奇怪的地方在于,打印的Object代表是treeData,这是在name改变前打印的,可是奇怪的是其name值已经变成了'hehe'

  1. 第二种情况(注释第一种情况),打印的内容以及顺序如下:
this is render
test
componentDidMount
this is render
hehe

仅仅是打印store.treeData.name, 居然能触发第二次render, 并且打印的name值前后变化都是正确的

  1. 解除前两条的注释,即如下
...
 console.log('this is render');
 console.log(store.treeData)
 console.log(store.treeData.name)
...

打印的结果以及顺序如下:

this is render
test
Object(其中name指为hehe)
componentDidMount
this is render
Object(其中name值为hehe)
hehe

奇怪的地方在于两次打印的treeData值中的name值都是改变后,这好奇怪

@ckinmind
Copy link
Owner Author

@wwayne
Copy link

wwayne commented Mar 24, 2017

@ckinmind Hi, 我搜索mobx深层变动时无意中看到你这里。首先谢谢你的这些链接,我顺着搜索到很多有用的信息。

1.我认为你上面所说的情况应该是正确的,比如第一种情况

可以奇怪的地方在于,打印的Object代表是treeData,这是在name改变前打印的,可是奇怪的是其name值已经变成了'hehe'

打印的object只是引用:当你在console里面看(读取)这个object,虽然是先打印出来的,但其实它只是个引用,指向的是被改动了的object,所以你看到的是新的name,这其实和mobx没关系,而是因为js里面object是ref(引用)

2.而我在Mobx深层数据变动遇到的问题是添加新的attribute进object,这个新的attribute却不被观察,比如

@observable treeData = {}

@action addNewAttr () {
 this.treeData['name'] = 'new added'
}

我的react component却没有因为这个addNewAttr的action触发任何的render事件...也就是说这一行为并没有被观察

最后我用了 map 这个数据结构 https://mobx.js.org/refguide/map.html , 这是我其中一个store

import { observable, action, map } from 'mobx';
import catalogAPI from '../api/catalog';

class Store {
  @observable data = map({}) // {catalogId: catalogObject}

  @action findById(id) {
    return catalogAPI.findById(id).then(catalog => {
      // 使用merge添加新的数据,会触发react里面的render
      this.data.merge({ [catalog.id]: catalog });
      return catalog;
    })
  }
}

export default new Store();

3.最后还有个问题,我看到你store export出去的都是个store class,而我的store都是export 这个store的instance出去,我的目的在于,这样在我的SPA里面,我的store状态就都是共享的了。虽然之前看别人文档也大多数export 这个store class出去,但我现在用下来还是觉得export store的instance比较适合SPA,我也是第一次尝试mobx,不知道你有没有什么想法在这方面?

@ckinmind
Copy link
Owner Author

ckinmind commented Apr 4, 2017

@wwayne 你好,首先很抱歉,其实我很早就看到了你的回复,因为之前太忙,加上有些问题我自己还没有思考清楚,所以没有及时回复,现在乘着清明节放假,我谈一些我的思考

  1. 确实是引用类型的原因,我原来以为先打印的话会打印改变前的状态,这并非遵循时间顺序上变化

  2. 关于深层次数据变动无法触发更新的问题,原因应该是出在原数据是observable的,但是其深层次的子数据不是observable的,拿map类型举例,可以参考我这个issue 关于mobx中两种定义observable map的方式的差别 #107

    对于这种问题,我个人有一个比较取巧的解决方案,你应该发现了如果在一个action中改变了多个observable的变量,但是最终只会触发一次render,在这一次render中将改变全部反映到视图上,所以,对于复杂类型的数据,我们可以额外定义一个updateKey(observable),当每次需要更新视图的时候,改变这个updateKey的值就行,使用了这种方式之后,其他的变量我都不需要定义observable了,代码如下

@observer
 class Test extends Component {

data = [];   // data内存储的是嵌套很深的数据
@observable updateKey = '';  //定义一个用于触发更新的变量

/** 视图触发器,里面什么也不用做 */
@action renderTrigger = () => {
 };

@action changeData = () => {
     // 异步或者别的什么操作,改变了data

    this.updateKey = Math.random();  // 随机改变updateKey的值,只要和之前不一样就行
    // 因为这里改变了,所以触发了renderTrigger的依赖变动(即使里面什么也不做),
   // 从而触发了视图更新, 从而将改变都反应到视图中
};

   render(){

    this.renderTrigger(this.updateKey);  // 这里必须传入updateKey,效果类似autorun

    return (
      .......
    )
  } 
}
  1. 任何需要改变data然后视图更新的地方,都可以通过改变updateaKey来触发视图更新
  2. 极端情况是,只定义一个updateKey是observable,其他所有的变量都可以不用是observable,任何数据改变后需要触发更新,只要直接this.updateKey = Math.random(); 就行
  3. 还有一个额外的好处是,可以让data的数据结构变得清晰,否则使用observable之后结构看起来就不太一样,加上可能还需要转换状态比如toJS,slice之类的,使用了我这种方式就可以直接使用原数据
  1. 确实是export 出去实例的好,我之前export store,然后在最顶级组件中实例化,然后如果子组件需要就通过prop传过去, 后来我看到这篇文章使用Mobx更好地处理React数据, 发现可以直接export 一个实例出去,然后哪里需要就import进去就行,这也引发了我一个新的疑问就是export出去的是共享一个实例还是每次import 都是一个新的store,自己测试的结果是共享一个

希望我的回复能对你有价值

@ckinmind ckinmind removed the 待处理 label Apr 4, 2017
@wwayne
Copy link

wwayne commented Apr 4, 2017

@ckinmind 感谢 :)
确实第二点如你所说,你这样的话data的数据结构会清楚很多,确实不失为一个有用的小技巧。也许封装下会更好,否则我觉得可能会坑队友,哈哈哈。
再次感谢你的认真回复,收获良多 :)

@huyansheng3
Copy link

上面的讨论很实用。 updateKey 这种方式曾在项目中实践过,但控制更新的视图范围更大,如果 observe 所有 data 的话,如果视图更零散,控制更新的视图会更精细。

@shopshow
Copy link

非常棒的讨论,感谢!

@leeseean
Copy link

问下现在有更好的解决方案了吗

@ckinmind
Copy link
Owner Author

@leeseean 过了这么久了,还是觉得updateKey这总方式最好

@zxiaohong
Copy link

学习了,感谢!

@lvSally
Copy link

lvSally commented Nov 1, 2019

发现这种方式可以触发更新Object.assign({}, this.loading, { [key]: status }),下面是我的代码片段

import { observable, action } from 'mobx'

class FetchLoadingStore {
  @observable loading = {}

  @action setLoading(key, status) {
    this.loading = Object.assign({}, this.loading, { [key]: status })
  }
}

const fetchLoadingStore = new FetchLoadingStore()
export { fetchLoadingStore }

@haanamomo
Copy link

@ckinmind ,学习了,我封装了一下

import { observable, action } from "mobx";

class RenderTrigger {
  @observable
  key: number;

  @action
  reRender() {
    this.key = Math.random();
  }

  watch(key: number) {}
}

export const renderTrigger = new RenderTrigger();

在数据结构处:

udpate() {
    renderTrigger.reRender();
}

在组件里:

render() {
    renderTrigger.watch(renderTrigger.key);
    return (
        <div>...
    )
}

讨论已经过去两年多,不知道有没有更好的办法。

@linshuizhaoying
Copy link

每次更新用lodash的cloneDeep重新赋值

@ckinmind
Copy link
Owner Author

@linshuizhaoying 可以考虑对于复杂的数据结构,用更新另外一个observable变量的方式触发更新的方式来降低复杂度,代码可能会更加简单清爽

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants