react + mobx:管理多个store的最佳实践

·  阅读 2030

对于store的概念相信大家都不陌生,在前端领域store的运用必不可少,mobx的多个store运用在大型项目中尤其重要,关键在于如何去组织多个store,在大型项目可能store的运用耦合性太高,导致越到后期维护越难以维护,代码编写极其困难,用文字表达不太直观,直接上代码:

实践一:

现在有一个Home主界面,里面包含里一个列表List组件和一个Task任务组件,以下是组件代码

let homeStore = new HomeStore()

class App extends Component {
    render() {
        return (
            <div>
                <Home homeStore={homeStore}/>
            </div>
        );
    }
}

复制代码
interface HomeProps {
    homeStore: HomeStore
}

@observer
class Home extends Component<HomeProps> {
    // 假设List组件需要调用homeStore某些方法,或者引用homeStore的某些属性,
    // 此时要把homeStore作为参数传进listStore,给listStore去调用homeStore的方法,去触发或者改变Home组件某些交互
    // 同时还有一个Task组件,糟糕的是产品设计Task组件不仅仅需要可以对Home组件做出交互同时也对List组件也有交互,
    // 此时taskStore需要传入homeStore的同时还要传入listStore      
    constructor(props: HomeProps) {
        super(props)
        this.listStore = new ListStore(this.props.homeStore)
        this.taskStore = new TaskStore(this.props.homeStore, this.listStore)
        // 这里还有一种情况就是有可能觉得listStore里面已经有homeStore了,taskStore就不需要传homeStore,
        // this.taskStore = new TaskStore(this.listStore)
    }
    listStore: ListStore
    taskStore: TaskStore

    render() {
        return (
            <Fragment>
                <List listStore={this.listStore} />
                <Task taskStore={this.taskStore} />
            </Fragment>
        );
    }
}

export default Home;

复制代码
interface TaskProps {
    taskStore: TaskStore
}

@observer
class Task extends Component<TaskProps> {
    constructor(props: TaskProps) {
        super(props)
    }    

    handleCount() {
        // 假设此处我需要调用homeStore的某些方法,对home组件做出一些交互,
        // 又或者需要取homeStore某些属性值的时候,可能会出现以下情况:
        this.props.taskStore.homeStore.xxx()
        
        // 若taskStore里面只有listStore,可能会出现以下情况:
        this.props.taskStore.listStore.homeStore.xxx()
    }

    render() {
        return (
            <div onClick={this.handleCount}></div>
        );
    }
}

export default Task;

复制代码

对应的HomeStore,ListStore和TaskStore的代码如下:

export class HomeStore {    
    xxx() {
        //do something here
    }
}
复制代码
export class ListStore {    
    constructor(homeStore: HomeStore) {
        this.homeStore = homeStore
    }

    homeStore: HomeStore 

    zzz() {

    }   
}
复制代码
//TaskStore根据上面代码有以下两种情况
export class TaskStore {    
    constructor(homeStore: HomeStore, listStore: ListStore) {
        this.homeStore = homeStore
        this.listStore = listStore
    }

    homeStore: HomeStore    
    listStore: ListStore

    yyy() {
        // 若此时需要调用homeStore的方法,会以下这样调用:
        this.homeStore.xxx()
    }
}

export class TaskStore {    
    constructor(listStore: ListStore) {
        this.listStore = listStore
    }  

    listStore: ListStore

    yyy() {
        // 若此时需要调用homeStore的方法,会以下这样调用:
        this.listStore.homeStore.xxx()
    }    
}
复制代码

以上代码是实战一方案,到此时,也许你会可能注意到一个很严重的问题Task组件调用homeStore的方法的时候需要在taskStore一层一层往上走,才能找到homeStore,这里只是两层Dom结构,如果Task组件里面还有子组件,子组件也需要一个单独管理的store的话,层级会越来越多,导致代码非常难维护。


针对越来越多的store,管理越来越复杂的情况,结合了mobx官方文档及建议,通过创建RootStore来管理即将要用到的stores,使用一个RootStore,保持对每个子 Store的引用。如有必要,可以在子Store中传入RootStore,让子Store也可以访问到RootStore以及RootStore引用的其他store,这个RootStore可以通过Provider的方式插入到组件数当中,下面我们上实践二的代码:

实践二:

import { Provider } from "mobx-react";
import rootStore from '../xxx/xxx/xxx';

class App extends Component {
    //使用Provider组件将stores传递,注意了,Home组件的homeStore并没有通过props传入
    render() {
        return (
            <Provider {...rootStore}>
                <div>                  
                    <Home/>
                </div>
            </Provider>
        );
    }
}
复制代码
export class RootStore {
    homeStore: HomeStore
    listStore: ListStore
    taskStore: TaskStore
    constructor() {      
        //this并非一定要传入子store的,当你有需要的时候才传入  
        this.homeStore = new HomeStore(this)
        this.listStore = new ListStore(this)
        this.taskStore = new TaskStore(this)
    }
}
export default new RootStore();
复制代码

此时,可能会产生疑问Home组件以及Home组件下面的子组件怎么引用相应store

import { observer, inject } from "mobx-react";

interface HomeProps {
    //这里大家会以为为什么要写个问号,因为此处若不带问号,Home组件入口会报错,显示你并没有传入homeStore,所以这里要带问号
    homeStore?: HomeStore 
}

//组件通过inject装饰器注入了homeStore,组件里面就可以通过props访问homeStore了
@inject("homeStore")
@observer
class Home extends Component<HomeProps> {  
    constructor(props: HomeProps) {
        super(props)
    }

    doSomething() {
        this.props.homeStore.xxx()
    }

    render() {
        // 同理,List组件和Task组件并没有通过props传入相应的store
        return (
            <Fragment>
                <List />
                <Task />
                <div onClick={this.doSomething}></div>
            </Fragment>
        );
    }
}

export default Home;
复制代码

通过这种方式组件确实可以访问注入进来的store了,但是这里有个小问题,或者说是一个可以优化的点,也是在各论坛比较多人问的问题,就是homeStore的类型是否一定要写到HomeProps里面并且带个问号来解决Home组件props没有传入的报错问题,个人认为并非最优办法,下面有一种办法,比较容易区分哪些对象是通过props传入哪些对象(store)是通过inject注入,以下是Home组件的更优写法:

import { observer, inject } from "mobx-react";

interface HomeProps {

}

//此处的InjectedProps接口继承了HomeProps
interface InjectedProps extends HomeProps {
    homeStore: HomeStore;
}

@inject("homeStore")
@observer
class Home extends Component<HomeProps> {  
    constructor(props: HomeProps) {
        super(props)
    }
    
    get injected() {
        return this.props as InjectedProps;
    }    

    doSomething() {
        // 此时可以通过get访问属性来读取inject注入的homeStore,这样的好处是可以明确
        // 区分props传入的对象和inject注入的对象的同时也解决了Home不传入homeStore的报
        // 错问题,若此时,想通过props来访问homeStore,显然是不行的,个人认为这种方式
        // 更优,若有更好的办法也可以推荐上来
        this.injected.homeStore.xxx()        
    }

    render() {
        return (
            <Fragment>
                <List />
                <Task />
                <div onClick={this.doSomething}></div>
            </Fragment>
        );
    }
}

export default Home;
复制代码

至于后面的List组件和Task组件也是一样方式注入

interface TaskProps {
    
}

//需要用到什么store,就注入什么store
interface InjectedProps extends HomeProps {
    homeStore: HomeStore;
    taskStore: TaskStore;
    listStore: ListStore;
}
@inject("homeStore", "taskStore", "listStore")
@observer
class Task extends Component<TaskProps> {
    constructor(props: TaskProps) {
        super(props)
    }    

    get injected() {
        return this.props as InjectedProps;
    }     

    handleCount() {
        //当然组件的逻辑可以在taskStore里面操作,并非一定要在组件上去操作
        this.injected.homeStore.xxx()
        this.injected.listStore.zzz()
    }

    render() {
        return (
            <div onClick={this.handleCount}></div>
        );
    }
}

export default Task;

复制代码

通过inject方式更好地解决了props一层一层传递store的问题,避免了父级的store一定要通过子组件的props一级一级地往下传的问题,此时可能会产生一个疑惑taskStore现在要怎么读取其他store的东西呢,以下是taskStore的代码:

export class TaskStore {    
    constructor(rootStore: RootStore) {
        this.rootStore = rootStore
    }  

    rootStore: RootStore

    yyy() {
        //若此时要调用homeStore或者listStore的方法,可以通过rootStore去
        访问相应的方法就可以了,这样就避免了很多层级的问题
        this.rootStore.homeStore.xxx()
        this.rootStore.listStore.zzz()
    }    
}
复制代码

通过实践二,可以发现通过inject的方式更加合理的管理stores,这样会减低了store与store之间的耦合性,代码更加简单易懂,但是此时读者可能会产生很多疑问,比方说,所有的store是不是一定要通过inject方式注入?Provider是不是全局只能有一个?多个Provider又怎么去配合使用

假设:我们Task组件并非一开始就加载或者写死的,是动态添加挂载的,而且每个Task要对应自己的一个store,并且Task组件下面可能还存在一些子组件,子组件里面可能会有一些局部唯一的store的情况

此时的taskStore就不能在rootStore里面new 一个实例然后通过inject方式注入,这样显然是不行的,通常全局唯一或者局部唯一的store才放到相对的根store里面,所以根据上述假设,Task组件的taskStore要通过props传递进入,然后我会建议在Task组件里再创建一个Provider来传递子组件的局部唯一store

这里先定义一个相当于Task组件的局部根Store

export class TaskRootStore {
    taskStore: TaskStore
    storeA: StoreA
    storeB: StoreB
    constructor() {      
        this.taskStore = new TaskStore(this)
        this.storeA = new StoreA(this)
        this.storeB = new StoreB(this)
    }
}
export default new TaskRootStore();
复制代码
// 这里TaskProps定义了一个taskRootStore是因为每个Task都会对应单独的store,
// 所以注册Task组件的时候,就要<Task taskRootStore={new taskRootStore()} />
interface TaskProps {
    taskRootStore: TaskRootStore;
}

interface InjectedProps extends HomeProps {
    homeStore: HomeStore;
    listStore: ListStore;
}
@inject("homeStore", "listStore")
@observer
class Task extends Component<TaskProps> {
    constructor(props: TaskProps) {
        super(props)
    }    

    get injected() {
        return this.props as InjectedProps;
    }     

    //Children组件就可以通过inject方式注入StoreA,StoreB,当然了也可以更上一级的homeStore
    render() {
        return (
            <Provider {...this.props.taskRootStore}>                
                <Children/>
            </Provider>
        );
    }
}

export default Task;

复制代码

以上就是对于局部动态加载一个组件的实践,通过增加Provider去分发局部不变的store,如此改造,可以更加方便管理store维护store,以上就是对react + mobx管理多个store的实践见解

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改