自定义hook+mobx按需加载数据, 实时刷新公用组件

782 阅读3分钟

解决问题:
1.多个页面都要使用的公用数据,会在项目初始化进来时就全部获取,并缓存store。当公用数据越很多时,项目打开时就会很卡。
需要: 实时按需加载数据,并且整个项目只发起一次http请求获取数据,获取后存store。当其他页面也用到这些数据时,不要再重复发起http请求。
2.当数据刷新时,各页面使用该数据的组件都要实时刷新。因此每个使用这些数据的页面组件都要用@observer。观察后会发现,这些使用同样数据页面,实现的功能也类似,比如都是做商店列表下拉框。代码重复量很多。
需要: 将不同页面使用这些数据的相同功能,整合在一个组件。这样不同的页面就可以复用组件的state,不用去observe很多的页面组件。

项目使用时间比较久。react的class组件,和升级后的function函数组件都有。管理本地缓存数据,用的是 mobx-react 的provider&inject的方式。下面是大概的用法示例。

mobx-react

// 首先父组件
import { inject, Provider } from 'mobx-react';
import stores from 'SRC/store';

@observer
class App extends Component {
  render() {
    return (
      <Provider {...stores}>
          <Routes /> 
      </Provider>
    )
  }
}
// 然后子组件(很多子页面需要使用shopList)
// 函数式组件不接受@修饰符的语法,参考官网使用inject('store')(observer(Component)) 这种语法方式。
import { inject, observer } from 'mobx-react';

function  TestChild(prop) {
  const { shopStore } = prop;
  ...
}

export default inject('shopStore')(observer(TestChild))

自定义hook, 复用useState,实时刷新公用组件

在线demo codesandbox.io/s/mobx-and-…

思路步骤:

  1. 数据shopList在不同页面都使用了,因为不希望每次用到shopList,都发起http重新获取。先定义mobx store缓存获取的shopList。
  2. 因为不想在项目进来时就发起http,需要按需加载数据。定义一个公用的hook,在hook中判断store是否已经缓存了数据或者已经在请求。都没有情况下,发起http请求。
  3. hook拿到store中的shopList。 并且observe shopList这个值,根据这个值的变化,实时刷新state。
  4. 公用组件ShopSelect,把不同页面的商店列表选择框的功能整合。这个公用的组件需要的数据shopList是一样的,并且页面要根据数据实时刷新。所以直接引用hook中的state就可以。const [shopList] = useShop();
  5. 各页面使用公用组件ShopSelect

mobx store data

class Shop {
  loading = false;
  shopList = [];

  constructor() {
    makeObservable(this, {
      loading: observable,
      shopList: observable,
      getShopList: computed,
      getDataSource: action
    });
  }

  get getShopList() {
    return toJS(this.shopList);
  }

  async getDataSource() {
    this.loading = true;
    try {
      const data = await httpApi.selectShopList();
      if (data.length > 0) this.loading = false;
      this.setshopList(data);
    } catch (error) {
      return Promise.reject(error);
    }
  }

  setshopList(data) {
    this.shopList = data;
  }
}

export default new Shop();

自定义hook, 监听mobx 存储的data,数据变化时set state, 刷新公用组件

import { useState } from "react";
import shopStore from "./store/shop";
import { observe } from "mobx";

// 自定义hook复用state
export default function useShop() {
  const [shopList, setShopList] = useState(shopStore.getShopList);

  // 只http 请求一次
  if (shopStore.getShopList.length === 0 && !shopStore.loading) {
    shopStore.getDataSource();
  }
  // 监听getShopList值,实时修改state
  observe(shopStore, "getShopList", ({ newValue, oldValue }) => {
    if (newValue !== oldValue) {
      setShopList(shopStore.getShopList);
    }
  });

  return [shopList];
}

公用组件ShopSelect,使用自定义hook useShop

import { Select } from "antd";
import useShop from "../useShop";

const ShopSelect = (props) => {
  const { children, showShopList, ...other } = props;
  // 此处使用自定义hook
  const [shopList] = useShop();
  return (
    <Select {...other} virtual={false}>
      {children}
      {showShopList(shopList)}
    </Select>
  );
};

ShopSelect.defaultProps = {
  children: null,
  //showShopList,自定义Select.Option的方式,默认如下
  showShopList: (list) =>
    list.map((item) => (
      <Select.Option key={item.id} value={item.id}>
        {item.name}
      </Select.Option>
    ))
};

export { ShopSelect };

各页面使用公用组件ShopSelect

import { Select } from "antd";
import { ShopSelect } from "./component/shopSelect";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <ShopSelect style={{ width: 200 }} placeholder="请选择商铺">
        <Select.Option value={null}>全部</Select.Option>
      </ShopSelect>
    </div>
  );
}