地图的MobX地图

MobX map of map

如何制作 observable.map() 的 map/transform?

例如,如果我有一个 observable.map()Todos,他们的 id 是关键:

var Todos = observable.map({
    'rf8r4': {id: 'rf8r4', description: 'Get milk'},
    '543w4': {id: '543w4', description: 'Code in MobX'},
    '099i0': {id: '099i0', description: 'Sleep'}
})

如何自动生成 UI 状态 observable.map(),例如包含 visible 属性 默认为 true(注意键对应上面 Todos 的那些):

ui_state = observable.map({
    'rf8r4': {visible: true}, // defaults
    '543w4': {visible: false}, // changed to false after some user interaction
    '099i0': {visible: true}
})

但要确保每个 Todo 的状态在更改原始 Todos 地图后保持一致,例如:

Todos.set('grtg6', {id: 'grtg6', description: 'Read Whosebug'})

应该导致(注意 543w4visible 属性 仍然是 false):

ui_state = observable.map({
    'rf8r4': {visible: true},
    '543w4': {visible: false},
    '099i0': {visible: true},
    'grtg6': {visible: true} // this is the new one
})

我尝试了 autorun/createTransformer,但它最后更新了,就好像这是一个副作用,所以我不能在 React 组件中使用 ui_state

我也尝试了不同版本的 computed,但它每次都会创建一个新的 ui_state,因为如果我在 computed 函数中使用 createTransformer,我会得到一个错误...

谢谢!

当您在您所在的州添加新条目时,可能很难将条目添加到您的 ui 地图,但如果您将不在 ui 地图中的条目视为可见,您可以这样做 like this:

const state = observable.map({
  'rf8r4': {id: 'rf8r4', description: 'Get milk'},
  '543w4': {id: '543w4', description: 'Code in MobX'},
  '099i0': {id: '099i0', description: 'Sleep'}
});

@observer
class App extends React.Component {
  @observable visible = asMap({
    'rf8r4': {visible: false}
  });
  @computed get visibleState() {
    const res = [];
    state.forEach(val => {
      const item = this.visible.get(val.id);
      if (!item || item.visible) {
        res.push(val);
      }
    });
    return res;
  }
  render() {
    return <div>
      {this.visibleState.map(el => 
        <div key={el.id} onClick={this.onClick}> {el.description} </div>
      )}
    </div>;
  }
  onClick = () => {
    this.visible.set('rf8r4', {visible: true});
  };
}

实际上我找到了制作 map 的 constantly-updated 地图的最佳方法。参见MobX Documentation on createTransformer

我在问题中提到我尝试 createTransformer 并且 运行 遇到了问题。这是因为 autorun 用于 运行ning side-effects,因此在所有 MobX 数据更新后 运行s。这意味着如果某些 MobX 数据(比如 'X')同时依赖于 mapmap 的映射,那么当 map 被更新时,那么 'X' 已更新,但 X 的另一个依赖项,地图的地图,是陈旧的,因为 autorun 还没有 运行,并且 'X' 的结果值要么是错误或计算本身会产生错误。一旦 autorun 终于 运行 更新了 map 的地图,那么 'X' 必须再次更新。

解决方法是让'X'依赖map的map,简单的在原[=]中存储一个引用entry 15=] 在这张 map 的地图中。

index.js:

import Inferno from 'inferno'
import { Provider } from 'inferno-mobx'
import h from 'inferno-hyperscript';

// Components:
import App from './inferno_components/App.js';

// Stores:
import languages from './stores/LanguagesStore.js';
import ui_state from './stores/LanguageViewState.js';


Inferno.render(
    h(Provider, {languages, ui_state}, // the following argument can't be an Array:
        h(App)
        )
, document.getElementById('main'));



// Make random changes post-render to make sure it re-renders:
languages.set('french', {id: 'french', example: 'Bonjour tout le monde'}); // map already passes the second arg through observable, so we don't need to do it explicitly
languages.get('french').example = 'Merci boucoup';

languages.set('german', {id: 'german', example: 'Gutten tag'});
languages.set('swahili', {id: 'swahili', example: 'Rafiki'});
languages.delete('german');


ui_state.get('english').visible = false;

components/App.js:

// for any component:
import { connect } from 'inferno-mobx'
import h from 'inferno-hyperscript';

// components to be used in this component:
import LanguageView from './LanguageView.js';


// the App:
var App = connect(['ui_state'], function({ui_state}){
    return h('div', ui_state.keys().map((language_name) => {
            var state = ui_state.get(language_name);
            if(state.visible) 
                return h(LanguageView, {language: state.language}); 
            else
                return h('p', 'Sorry, invisible!');
            }))
    });

export default App;

components/LanguageView.js:

import { connect } from 'inferno-mobx'
import h from 'inferno-hyperscript';


var LanguageView = connect(function({language}){
    return h('div', [
        h('p', language.id),
        h('p', language.example)
        ]);
    });

export default LanguageView;

stores/LanguagesStore.js:

import { observable } from 'mobx'

const languages = observable.map({
    english: {
        id: 'english',
        example: 'Hello World'
        }
    });

export default languages;

stores/LanguageViewState.js:

import { observable, createTransformer, intercept } from 'mobx';

import languages from './LanguagesStore.js';


const makeUIStateFromLanguage = createTransformer((language) => {
    return {language, visible: true}
    });


var ui_state = observable.map({});

// initialize:
languages.forEach((language, language_name) => {
    ui_state.set(language_name, makeUIStateFromLanguage(language));
    });

// set up automatic updating:
intercept(languages, function(change_object){
    if(change_object.type === 'add'){
        ui_state.set(change_object.name, makeUIStateFromLanguage(change_object.newValue));
        }
    else if(change_object.type === 'delete'){
        ui_state.delete(change_object.name);
        }
    return change_object; // for intercept
    });

export default ui_state;