Mobx

全局状态管理插件设计的核心思想都可以概括成:不能随意的去修改状态(state)。因此我们通常需要一个 action 来统一 modify 我们的 state。
MobX 也是基于这种思想设计的,它具有:

  • 定义状态并使其可观察 (observable)
  • 创建视图以响应状态的变化(observer、computed)
  • 更改状态(action)

image.png

Function 组件里使用

由于 React Hooks 的出现,以及注解的不稳定性。在 6.x 中,MobX 升级了它的使用方法。
这里我们以 makeAutoObservable 的 Store 设计为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export class Store6_makeAutoObservable {
 readonly base = 5;
 MCount = 0;

 constructor() {
   makeAutoObservable(this);
}

 setMCount() {
   this.MCount++;
}

 get total() {
   return this.MCount * this.base;
}
}

export default new Store6;

然后在函数组件里,我们需要使用 observer 函数来替代注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { observer } from "mobx-react";
import store6 from "@/store";

export const MobXFunc: React.FC = observer(() => {
return (
<div>
     
<button
onClick={() => {
store6.setMCount();
}}
>
       count++      
</button>
     <span> Count {store6.MCount} </span>     
<span> Base {store6.base} </span>     <span> Total {store6.total} </span> 
 
</div>
);
});

MobX + Hooks 写法

为了更好的结合 Hooks 语法,Mobx 在 6.x 中也提供了 2 个新的 API:

useLocalStore(Hooks 环境下的 observable)

1
const store = useLocalStore(() => ({ key: "value" }));

等价于

1
const [store] = useState(() => observable({ key: "value" }));

为 Hooks 解决了 依赖传递 和 缓存雪崩 的问题。

useObserver

Mobx 使组件响应数据状态的变化主要有以下三种方式:

  • @observer
    • 给类组件提供 pure component 的能力,将组件的 props 和 state 转换为 observable 态,响应数据变化
    • 不推荐在 Hooks 中使用
  • observer 方法
  • Component:Observer(Mobx 6 中已经基于 useObserver 来实现了)
  • Hooks:useObserver

还是以 makeAutoObservable 的 Store 设计为例,这里就不展示了。

我们在函数组件里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React from "react";
import { Observer, useLocalObservable, useObserver } from "mobx-react";
import { store6_auto as store6 } from "@/store";

export const MobXHook: React.FC = () => {
const store = useLocalObservable(() => store6);
return useObserver(() => (
<div>
     
<button
onClick={() => {
store.setMCount();
}}
>
       count++      
</button>
     <span> Count {store.MCount} </span>     
<span> Base {store.base} </span>     <span> Total {store.total} </span>   
</div>
));
};

// 或者
export const MobXHook: React.FC = () => {
const store = useLocalObservable(() => store6);
return (
<Observer>
   {" "}
{() => (
<div>
         
<button
onClick={() => {
store.setMCount();
}}
>
           count++          
</button>
         <span> Count {store.MCount} </span>         
<span> Base {store.base} </span>         <span>
{" "}
Total {store.total}{" "}
</span>       
</div>
)}
   
</Observer>
);
};

同时,useLocalObservable 也可以用来创建一个新的 observable,并在组件的整个生命周期内将其保留在组件中(可以理解为组件级别的 observer)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const MouseEventListenerMobx: React.FC = (props) => {
const state = useLocalStore(
(target) => ({
x: 0,
y: 0,
handler(e) {
const nx = e.xxx;
const ny = e.xxx;
if (
Math.abs(nx - state.x) >= target.size ||
Math.abs(ny - state.y) >= target.size
) {
state.x = nx;
state.y = ny;
}
},
}),
props
);

useEffect(() => {
document.addEventListener("mousemove", state.handler);
return () => document.removeEventListener("mousemove", state.handler);
}, []);

return useObserver(() => props.children(state.x, state.y));
};

最终推荐方案

综上所述,在结尾给出最终推荐方案。
在 store 设计上,建议采用 makeAutoObservable 的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export class Store6_makeAutoObservable {
readonly base = 5;
MCount = 0;

constructor() {
makeAutoObservable(this);
}

setMCount() {
this.MCount++;
}

get total() {
return this.MCount * this.base;
}
}

export default new Store6();

在 store 使用上,建议采用 observer 函数包裹组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";
import { observer } from "mobx-react";
import { store6_auto as store6 } from "@/store";

const MobXHook: React.FC = () => {
const store = store6;
return (
<div>
     
<button
onClick={() => {
store.setMCount();
}}
>
       count++      
</button>
     <span> Count {store.MCount} </span>     
<span> Base {store.base} </span>     <span> Total {store.total} </span>   
</div>
);
};

export default observer(MobXHook);