日常使用mobx的小技巧

日常使用mobx的小技巧

由于自己开发的项目都是中小型项目,所以在技术选型上使用了mobx。但是使用过程中发现关于mobx的技术文章并不多。于是萌发出写这篇文章的想法。请轻喷。

  • 更新控制store渲染的方法
mobx一些有关资料

中文文档
github仓库
npm地址

添加mobx
npm i mobx
npm i mobx-react

mobx用来操作store(也就是数据操作层,model层),而mobx-react则是用来操作view(也就是视图层,Component层)。

mobx常用操作符
  1. observable,将JS基本数据类型、引用类型、普通对象、类实例、数组和映射,转换为可观察数据。
  2. action,用来修改observable的数据的动作,只有actionrunInAction才能修改observable
  3. runInAction,用来在异步的时候执行修改observable的数据的动作。例如网络请求后修改数据。
  4. computed,根据现有的observable的值或其它计算值衍生出的值。只有在view使用了computed的值,computed才会执行计算
mobx-react常用操作符
  1. observer,将 React组件 转变成响应式组件。
  2. inject,将组件连接到提供的stores 。一般是用来连接到上层组件提供的store或者全局store
  3. Provider,它是一个react组件,用来向下传递 stores。任意子组件可以使用inject来获取Providerstore
代码

下面会贴点自己的代码。希望能给大家带来一些帮助。

全局store

// 文件 index.jsx
import { Provider } from "mobx-react";
import * as stores from "../../stores";
class MainView extends Component {
  render() {
    return (
      <React.Fragment>
        <Provider {...stores}>  // 这里是全局stores的配置
          <Switch>
            {routers.map((item, index) => {
              return (
                <Route
                  exact
                  key={item.path}
                  path={item.path}
                  component={item.component}
                />
              );
            })}
          </Switch>
        </Provider>
      </React.Fragment>
    );
  }
}


// 文件 ../../stores/index.js
import aStore from "./aStore";
import bStore from "./bStore";
export { aStore, bStore};

// 文件 aStore.js
class AStore {
  @observable info = {};
  @observable line = {};
  @action
  onInfo = data => {
    this.info = data;
  };
  @action
  onLine = data => {
    this.line = data;
  };
}
const aStore = new AStore();
export default aStore;

// 组件使用aStore
@inject(all => ({
  aStore: all.aStore // 连接到aStore
}))
@observer
class Detail extends Component {
		updateInfo = () => {
			const aStore = this.props.aStore;
			aStore.onInfo(info) // 改变store属性
		}
	  render() {
	  	const aStore = this.props.aStore; // 获取到全局store
      return (
        <div>
        	{aStore.info.name} // 使用里面的属性
        	<Button onClick={this.updateInfo}>改变info</Button>
        </div>
      );
  	}
}

组件内可观察数据。

@observer
class Detail extends Component {
  @observable info = {};
  @observable index = 1;
  
  @computed get sum() {
  	return index * 4
  }
  
  @action
  onInfo = data => {
    this.info = data;
  };
  updateInfo = () => {
		this.onInfo(info) // 改变store属性
	}
	render() {
    return (
      <div>
       {this.info.name} // 使用里面的属性
       <Button onClick={this.updateInfo}>改变info</Button>
      </div>
    );
  }
}

最后一种是我自己项目使用的,个人觉得不错。可以分离逻辑层和视图层。如果想使用全局stores,直接用inject导入第一种方式注入的stores。能较好划分全局stores单业务store的职责,而不是无脑的以树的方式全挂在index.js上面。子组件最好跟父组件用同一个store,方便沟通的同时层级不多也不会导致store太过复杂。

// 逻辑层 用来处理业务逻辑
import { observable, runInAction } from "mobx";
import { aService, bService } from "../../../services/dispatch";
import { T } from "react-toast-mobile";
class ListStore {
  @observable list = [];

  serInitData = async () => {
    try {
      T.loading();
      const data = await aService()
      runInAction("serInitData", () => {
        this.list = data || [];
      });
    } catch (error) {
      T.notify(error.message);
      console.error(error);
    } finally {
      T.loaded();
    }
  };

  serBService = async vehicleNum => {
    try {
      T.loading();
      await bService(vehicleNum);
      runInAction("serBService", () => {
        this.list = this.list.filter(params => {
          return params.vehicleNum != vehicleNum;
        });
      });
    } catch (error) {
      T.notify(error.message);
      // eslint-disable-next-line no-console
      console.error(error);
    } finally {
      T.loaded();
    }
  };
}
export default ListStore;


// 视图层 纯粹的视图展示和操作触发
@observer
class Detail extends Component {
  store = new ListStore();
  
  componentDidMount() {
  	// 初始化数据
    this.store.serInitData();
  }
  
  updateInfo = () => {
  	this.store.serBService(); // 调用bService
	}
	render() {
    return (
      <div>
       {this.store.list.map((item)=>{
       		return 
       		<Provider myStore={this.store} >
       			<Item key={item.guid} />
       		</Provider>
       })} // 轮询列表
       <Button onClick={this.updateInfo}>改变info</Button>
      </div>
    );
  }
}

const Item = inject(allStores => ({
   aStore: all.aStore, // 连接到aStore
   myStore: allStores.myStore, // 连接到父组件的myStore
}))(
observer(function(props) { // 使用function方式在发送action等操作时,会带上Item这个组件名字,能变相的看到发送的来源。使用箭头函数则不会。而且react推荐的无状态组件也是使用的function
  
  return (
    <div>
    	{this.props.aStore.xxxxx} // 使用全局store
    	{this.props.myStore.xxxxx} // 使用父组件的store
    </div>
  );
}));

一些备注
  1. 使用@inject后,无法通过组件的refs属性调用其对应的方法?
if (this.tabIndex == "0") {
    // 加了@inject+@observer
    this.biddingRef.current.wrappedInstance.openFilter();
} else if (this.tabIndex == "1") {
    // 只有@observer
    this.zdRef.current.openFilter();
}
参考链接
// https://stackoverflow.com/questions/43847401/reactnative-mobx-how-to-access-component-refs-from-mobx
  1. mobx如何自动保存数据
import { observable, action, autorun, toJS, set } from "mobx";

function autoSave(store, save) {
  let firstRun = true;
  autorun(() => {
    // 此代码将在每次运行任何可观察属性时运行
    // 对store进行更新。
    const json = JSON.stringify(toJS(store));
    if (!firstRun) {
      save(json);
    }
    firstRun = false;
  });
}
class RouteState {
  @observable state = {};
  constructor() {
    this.load();
    autoSave(this, this.save.bind(this));
  }
  load() {
    const storeTemp = sessionStorage.getItem("route_state");
    if (storeTemp) {
      const data = JSON.parse(storeTemp);
      set(this, data);
    }
  }
  save(json) {
    sessionStorage.setItem("route_state", json);
  }
  @action.bound
  actionState(_state) {
    this.state = _state;
  }
}

// 参考链接
https://stackoverflow.com/questions/40292677/how-to-save-mobx-state-in-sessionstorage
场景模拟

列表A中有一个倒计时,这个倒计时会改变dataListitem里面的时间戳.

列表所在的页面有个筛选浮层,筛选浮层中有DatePicker组件

遇到的问题

DatePicker选择其他时间1s后,时间会重置为初始设置的时间.

原因

列表A中的倒计时每秒都在改变dataList,这导致该页面的子组件每秒都会执行render函数.导致DatePicker中的时间重置为初始时间.

解决办法

github iss

// 新建一个包含组件.注意,这里不能用@observer将其包裹起来,
// 包含组件应该是个干净的,由你控制是否重新渲染的组件
class FilterPopContains extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 在包含组件的shouldComponentUpdate方法中自己判断是否要执行render函数
    if (JSON.stringify(nextProps) != JSON.stringify(this.props)) return true;
    return false;
  }
  render() {
    // 将props原封不动传递给逻辑组件
    return <FilterPop {...this.props} />;
  }
}
// 这个是具体的内容组件,里面有你的逻辑
@observer
class BiddingFilterPop extends Component {
  render() {
    return <div>
    	<DatePicker/>
    </div>
  }
}
  1. mobx的使用非常灵活。可以多种使用方式在同一个项目使用。并不冲突。
  2. 推荐所有的组件都observer化,这并不会造成性能损耗,反而会优化组件。
  3. 想不到了。想起来再更新吧。
posted @ 2019-06-10 10:35  被咯苏州  阅读(1846)  评论(0编辑  收藏  举报