# mobx-react@7.x hooks

参考文章

React integration (opens new window)

Using MobX with React Hooks and TypeScript (opens new window)

# 前言

mobx-react@7.x相较于mobx-react@6.x有很大改变, api 也有所不同. 因此写法有改变. 另外本例子是基于react@17.0.1+react hooks+mobx@6.x + mobx-react@7.x+ typescript创建的.

# 如何定义子模块

因为使用mobx, 大多都是用来做全局状态管理的. 因此会拆分多个子模块.

mobx-react@7.x提供了makeAutoObservable (opens new window)方法, 用来将普通对象转化为可观察对象. 官方文档的例子中是在constructor中进行转化的.






 






// /src/store/timer.ts

class Timer {
  secondsPassed = 0;
  constructor() {
    makeAutoObservable(this);
  }
  increaseTimer() {
    this.secondsPassed += 1;
  }
}

但是经过测试, 只需要最终引用的实例对象被转化即可








 

class Timer {
  secondsPassed = 0;
  increaseTimer() {
    this.secondsPassed += 1;
  }
}

const timer = makeAutoObservable(new Timer());

另外, 例子中的子模块类的属性方法并没有使用装饰器, 也可以正常运行. 但是会存在引用方法时, this指向错误的问题.

<button onClick={timer.increaseTimer}>increaseTimer</button>

// 报错: 'this.secondsPassed' is not defind

所以延续旧的装饰器写法


 


 







class Timer {
  @observable
  secondsPassed = 0;

  @action
  increaseTimer = () => {
    this.secondsPassed += 1;
  };
}

const timer = makeAutoObservable(new Timer());

# 定义 stroe 总模块

mobx-react@7.x中, 推荐使用React Context来存储状态. Using external state in observer components (opens new window)

先来看总览的代码

// /src/store/index.tsx

import { FC, createContext, useContext } from "react";

import timer from "./timer";

const createStore = () => ({ timer });

const storeValue = createStore();

type TStore = ReturnType<typeof createStore>;

const StoreContext = createContext<TStore | null>(null);

export const StoreProvider: FC = ({ children }) => (
  <StoreContext.Provider value={storeValue}>{children}</StoreContext.Provider>
);

export const useStores = () => {
  const store = useContext(StoreContext);
  if (!store) {
    throw new Error("no store");
  }
  return store!;
};

先讲讲createContextuseContext. use context (opens new window)中提到, 创建出来的context类似一个容器, 用来缓存数据,Provider为提供数据的容器组件,其中的value属性则用来配置数据. 而useContext则会往上找到指定的context中保存的数据.

逐一分析

# 创建context

Version 1

const store = { timer }; // 其中'timer'是上文定义的子模块
const StoreContext = createContext(null);

<StoreContext.Provider value={store} />;

这样写是没有问题的. 但是会出现一点小问题. 当逻辑组件引用store的时候, 1.会提示store可能是null(即初始值), 2.不能明确store中包含的内容.





 




function App() {
  const store = useContext(StoreContext);

  // 'store' 可能为空, 由于没有定义类型接口, 'timer' 是不明确的
  const secondsPassed = store?.timer?.secondsPassed;

  return "";
}

Version 2 先给 stroe 定义子模块的类型



 


const createStore = () => ({ timer });
const storeValue = createStore();
type TStore = ReturnType<typeof createStore>;
const StoreContext = createContext<TStore | null>(null);

其中ReturnType<typeof createStore>是根据这篇文章 (opens new window) 来使用的, 具体的原理还不是很明白, 先这样写. createContext接收泛型参数TStore后就能在引用时获得提示. 然后在导出useStores时, 使用!明确store一定存在.






 


export const useStores = () => {
  const store = useContext(StoreContext);
  if (!store) {
    throw new Error("no store");
  }
  return store!;
};

# 创建关联 contextProvider

export const StoreProvider: FC = ({ children }) => (
  <StoreContext.Provider value={storeValue}>{children}</StoreContext.Provider>
);

该组件注入storeValue, 然后组件内的所有子组件children都可以获取到context的数据.

# 提供获取 store 的自定义 hooks

export const useStores = () => {
  const store = useContext(StoreContext);
  if (!store) {
    throw new Error("no store");
  }
  return store!;
};

其中store就是上文创建的storeValue

# 在根组件中使用 Provider

一般在/src/index.tsx

import { StoreProvider } from "@/store";

ReactDOM.render(
  <StoreProvider>
    <App />
  </StoreProvider>,
  document.getElementById("root")
);

# 在子孙组件中引用

















 

// /src/pages/MyComponent.tsx

import { observer } from "mobx-react";
import { useStores } from "@/store";

function MyComponent() {
  const { timer } = useStores();

  return (
    <>
      <p>Seconds passed: {timer.secondsPassed}</p>
      <button onClick={timer.increaseTimer}>increaseTimer</button>
    </>
  );
}

export default observer(MyComponent);

最后不要忘了用observer来将组件转为观察者.

Last Updated: 1/7/2021, 5:49:30 PM