npm i mobx-provide-pro --save
- provide
- connect
- getStoreById
- provide属性
provide(provideName, provideClass, storeId)
-
provideName
声明一个name,在同一组嵌套关系中此 name 唯一,重复的 name 会覆盖最近父节点的同名 store -
provideClassstore 构造方法,必须继承自IStoreBase -
storeId声明一个全局storeId,此 id 全局唯一,同名的 storeid 会显示抛出异常,通过使用getStoreById可以在任何地方获取到这个 store ,前提是 provide 此 store 的组件必须存在。
provide向当前组件提供一个 store, 基于 react 的 context 特性,在该组件和其所有的后代组件中都可以通过connect方法获取到这个 store
通过 provide 可以无视组件嵌套层级而共享一组状态,此状态在该组件和其所有的后代组件中都可见,默认在组件层级之外无法访问,除非提供 storeId
store 具有生命周期,在组件之前创建并且在组件之后销毁,这意味着在组件的所有生命周期中都可以访问_store_,包括constructor和componentWillUnmount。可以在 store 的onDestory方法中做一些清理工作,例如:轮询任务,定时器,mobx reaction等。
st=>start: store constructor
op2=>operation: ...组件生命周期...
end=>end: store onDestory
st->op2->end
提供storeId将会在一个全局的 HashMap 中注册该 store ,在组件卸载后会自动从 HashMap 中移除。通过getStoreById可以在组件嵌套层级之外获取到这个状态,例如:在页面中与顶部栏或者侧边栏进行交互,而页面可能与其并不具有父子关系。解决此问题还可以使用 状态提升
connect(...storeNames)
storeNamesstore 的name序列,所有的name都必须在此次connect的组件的上层组件中被provide过,否则会抛出异常。可以嵌套provide只需保证命名不同,
组件之中使用this.props.name的方式获取 store , 在mobx非严格模式下可以修改状态并刷新视图。
getStoreById(id)
id通过provide方法或者provide属性指定的全局ID,此ID全局唯一,重复则会抛出异常。
<App provide={storeInstance} />
<App provide={[storeInstance]} />
<App provide={[storeIntance, storeId]} />
-
storeInstancestore 实例,通过provide提供的 store 会在每次组件创建时创建,此参数会阻止其默认行为,storeInstance会替换默认生成的 store 实例,这意味着可以在组件外部去管理状态,可以参考状态提升章节。但即使传入全局实例也会在组件卸载时执行onDestory方法,而此实例却只会创建一次,这在数据持久化存储中发挥作用。 -
storeId全局的storeId,作用同provide方法的第三个参数,一旦在属性中提供则会覆盖provide中的storeId值。及时使用全局实例,在 provide 组件卸载时其对应的 store 依然从全局 HashMap 中移除,所以通过id获取状态之前请确认组件是否还在挂载中,否则会返回undefined
- 父子组件共享状态
class AppStore extends IStoreBase{
@observable public message = "hello";
@action setMessage(message: string) {
this.message = message;
}
}
interface IProps extends IProvideProps {
db?: AppStore,
}
@provide("db", AppStore)
@connect("db")
export default class App extends React.Component<IProps> {
public render() {
return (
<div className="App">
App {this.props.db?.message}
<Wrap>
<Inner />
</Wrap>
</div>
);
}
}
const Wrap = (props: any) => <div>{props.children}</div>
const Inner = connect("db")((props: IProps) => {
const onClick = () => {
props.db?.setMessage("world");
}
return <button onClick={onClick}>{props.db?.message}</button>
})App和Inner共享同一组状态,由于connect的内部已经实现了 observer,而不需要再次声明,任何时候只要改变message都会同时刷新这两个组件。注意到Inner并非App的直接子组件。
对于需要在App和Inner之间交互的状态,我们都应该将其放在AppStore中。由于AppStore会在App组件卸载时销毁,因此其是状态安全的,我们也可以将非交互状态也放置其中,或者使用AppStore取代组件的state,任何时候组件只需要负责渲染而不需要关心数据来源。
- 子组件状态提升
// table.tsx
class TableStore extends IStoreBase{
@observable public count = 10;
@observable public page = 1;
@observable public data = [];
@action queryData() {
fetch("/data.action").then((res: Response) => res.json()).then((json) => {
this.count = json.count;
this.page = json.page;
this.data = json.data || [];
});
}
}
interface ITableProps extends IProvideProps {
db?: TableStore,
}
@provide("db", TableStore)
@connect("db")
class Table extends React.Component<ITableProps> {
componentDidMount() {
this.props.db?.queryData();
}
public render() {
const {count, page, data} = this.props.db || {};
return <div>
<div>{data}</div>
<p>第{page}页,共{count}条</p>
</div>;
}
}在Table的 store 中声明了三个变量分别表示数据总数、当前页、表格数据,在componentDidMount中进行数据加载。
// app.tsx
class AppStore extends IStoreBase{
public table = new TableStore();
}
interface IAppProps extends IProvideProps {
db?: AppStore,
}
@provide("db", AppStore)
@connect("db")
export default class App extends React.Component<IAppProps> {
private onClick = () => {
this.props.db?.table.queryData();
}
public render() {
return <div>
<Table provide={this.props.db?.table} />
<button onClick={this.onClick}>刷新</button>
</div>
}
}在App中实现对Table的数据刷新,这里使用了状态提升,即将子组件的 store 提升至其父组件中,以便对父组件完全受控,状态提升也可以跨越层级。
这里的App和Table使用了相同的provideName, 这意味着Table组件会覆盖App提供的 store,从而无法访问该状态。
App的provide属性会覆盖Table组件默认的provide,对Table来说没有任何变化,它与 store 交互而无需知道该 store 由谁提供。而App可以像操纵自己的状态一样随意操纵Table的状态,因此可以在App中复制多个Table,甚至实现它们之间的联动。
class AppStore extends IStoreBase{
public table1 = new TableStore();
public table2 = new TableStore();
}
interface IAppProps extends IProvideProps {
db?: AppStore,
}
@provide("db", AppStore)
@connect("db")
export default class App extends React.Component<IAppProps> {
private onClick = () => {
this.props.db?.table1.queryData();
this.props.db?.table2.queryData();
}
public render() {
return <div>
<Table provide={this.props.db?.table1} />
<Table provide={this.props.db?.table2} />
<button onClick={this.onClick}>刷新</button>
</div>
}
}也可以对两个Table提供相同的 store 使它们共用同一个状态,任意一个Table修改状态也会同时反映到另一个上。
- 全局状态
有时候我们需要在两个组件之间通信,但他们并不具备嵌套关系。可以使用状态提升将他们的状态提升至共同父组件、根组件,甚至可以提升至全局。
// loading.tsx
export class LoadingStore extends IStoreBase {
@observable public visible = false;
@action public setVisible(visible: boolean) {
this.visible = visible;
}
}
interface ILoadingProps extends IProvideProps {
db?: LoadingStore,
}
@provide("db", LoadingStore)
@connect("db")
class Loading extends React.Component<ILoadingProps> {
public render() {
const {visible} = this.props.db || {};
return visible ? <div>加载中...</div> : <i />;
}
}// app.tsx
export const loadingStore = new LoadingStore();
export const App = () => {
return <div>
<Loading provide={loadingStore}/>
<Wrap>
<Inner />
</Wrap>
</div>
}使用storeId将状态注册到全局 HashMap 中,在需要访问的地方使用getStoreById获取状态。
// loading.tsx
export class LoadingStore extends IStoreBase {
@observable public visible = false;
@action public setVisible(visible: boolean) {
this.visible = visible;
}
}
interface ILoadingProps extends IProvideProps {
db?: LoadingStore,
}
@provide("db", LoadingStore, "loading")
@connect("db")
export class Loading extends React.Component<ILoadingProps> {
public render() {
const {visible} = this.props.db || {};
return visible ? <div>加载中...</div> : <i />;
}
}// app.tsx
const App = () => {
return <div>
<Loading />
<Wrap>
<Inner />
</Wrap>
</div>
}
const Wrap = (props: any) => <div>{props.children}</div>
const Inner = observer(() => {
const onClick = () => {
const loading: LoadingStore = getStoreById("loading");
loading.setVisible(true);
}
return <button onClick={onClick}>显示Loading</button>
})
export default App;使用mobx-provide可以很容易进行视图重构,因为进行重构不需要关心数据,我们可以很容易复杂组件拆分成众多小组件,也可以将小组件合并成大组件,甚至重新写一套视图组件。
一般情况下,每个状态改变都会引起组件重新渲染,observer包裹的组件会自动浅层渲染,将组件拆分成更小的单元,可以避免某些视图不必要的渲染,这对于大数据渲染有很大的性能提升。
class AppStore extends IStoreBase{
@observable public list = [...Array(1000).keys()];
@observable public count = 0;
@action increase() {
this.count ++;
}
}
interface IProps extends IProvideProps {
db?: AppStore,
}
@provide("db", AppStore)
@connect("db")
export default class App extends React.Component<IProps> {
private click = () => {
this.props.db?.increase();
}
public render() {
const { list = [], count } = this.props.db || {};
return (
<div className="App">
<ul>
{list.map((e: number) => <li key={e}>{e}</li>)}
</ul>
<button onClick={this.click}>{count}</button>
</div>
);
}
}上面的App组件会在每次click按钮时递增,并且每次点击都会渲染一个长度为1000的列表,为了避免列表不必要的渲染,可以考虑将按钮放置到一个单独组件中。
class AppStore extends IStoreBase{
@observable public list = [...Array(1000).keys()];
@observable public count = 0;
@action increase() {
this.count ++;
}
}
interface IProps extends IProvideProps {
db?: AppStore,
}
@provide("db", AppStore)
@connect("db")
export default class App extends React.Component<IProps> {
public render() {
const { list = [] } = this.props.db || {};
return (
<div className="App">
<ul>
{list.map((e: number) => <li key={e}>{e}</li>)}
</ul>
<Button />
</div>
);
}
}
const Button = connect("db")((props: IProps) => {
const click = () => {
props.db?.increase();
}
const { count } = props.db || {};
return <button onClick={click}>{count}</button>
})对于同样的点击事件,后者只会渲染Button组件。