1.1.5 • Published 4 years ago

mobx-provide-pro v1.1.5

Weekly downloads
3
License
ISC
Repository
github
Last release
4 years ago

安装

npm i mobx-provide-pro --save

API参考

  • provide
  • connect
  • getStoreById
  • provide属性

provide

provide(provideName, provideClass, storeId)
  • provideName
    声明一个name,在同一组嵌套关系中此 name 唯一,重复的 name 会覆盖最近父节点的同名 store

  • provideClass store 构造方法,必须继承自IStoreBase

  • storeId 声明一个全局storeId ,此 id 全局唯一,同名的 storeid 会显示抛出异常,通过使用getStoreById 可以在任何地方获取到这个 store ,前提是 providestore 的组件必须存在。

provide向当前组件提供一个 store, 基于 reactcontext 特性,在该组件和其所有的后代组件中都可以通过connect方法获取到这个 store

通过 provide 可以无视组件嵌套层级而共享一组状态,此状态在该组件和其所有的后代组件中都可见,默认在组件层级之外无法访问,除非提供 storeId

store 具有生命周期,在组件之前创建并且在组件之后销毁,这意味着在组件的所有生命周期中都可以访问store,包括constructorcomponentWillUnmount。可以在 storeonDestory方法中做一些清理工作,例如:轮询任务,定时器,mobx reaction等。

加入provide后的组件生命周期
st=>start: store constructor
op2=>operation: ...组件生命周期...
end=>end: store onDestory

st->op2->end

提供storeId将会在一个全局的 HashMap 中注册该 store ,在组件卸载后会自动从 HashMap 中移除。通过getStoreById可以在组件嵌套层级之外获取到这个状态,例如:在页面中与顶部栏或者侧边栏进行交互,而页面可能与其并不具有父子关系。解决此问题还可以使用 状态提升

connect

connect(...storeNames)
  • storeNames storename序列,所有的name都必须在此次connect的组件的上层组件中被provide过,否则会抛出异常。可以嵌套provide只需保证命名不同,

组件之中使用this.props.name的方式获取 store , 在mobx非严格模式下可以修改状态并刷新视图。

getStoreById

getStoreById(id)
  • id 通过provide方法或者provide属性指定的全局ID,此ID全局唯一,重复则会抛出异常。

provide属性

<App provide={storeInstance} />
<App provide={[storeInstance]} />
<App provide={[storeIntance, storeId]} />
  • storeInstance store 实例,通过provide提供的 store 会在每次组件创建时创建,此参数会阻止其默认行为,storeInstance会替换默认生成的 store 实例,这意味着可以在组件外部去管理状态,可以参考状态提升章节。但即使传入全局实例也会在组件卸载时执行onDestory方法,而此实例却只会创建一次,这在数据持久化存储中发挥作用。

  • storeId 全局的storeId,作用同provide方法的第三个参数,一旦在属性中提供则会覆盖provide中的storeId值。及时使用全局实例,在 provide 组件卸载时其对应的 store 依然从全局 HashMap 中移除,所以通过id获取状态之前请确认组件是否还在挂载中,否则会返回undefined

如何使用mobx-provide在组件之间通信

  • 父子组件共享状态
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>
})

AppInner共享同一组状态,由于connect的内部已经实现了 observer,而不需要再次声明,任何时候只要改变message都会同时刷新这两个组件。注意到Inner并非App的直接子组件。

对于需要在AppInner之间交互的状态,我们都应该将其放在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>;
    }
}

Tablestore 中声明了三个变量分别表示数据总数、当前页、表格数据,在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 提升至其父组件中,以便对父组件完全受控,状态提升也可以跨越层级。

这里的AppTable使用了相同的provideName, 这意味着Table组件会覆盖App提供的 store,从而无法访问该状态。

Appprovide属性会覆盖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组件。

1.1.5

4 years ago

1.1.4

4 years ago

1.1.3

4 years ago

1.1.2

4 years ago

1.1.1

4 years ago

1.1.0

4 years ago

1.0.4

4 years ago

1.0.3

4 years ago

1.0.2

4 years ago

1.0.1

4 years ago

0.1.1

4 years ago

0.1.0

4 years ago