使用Mobx支持React Class&Hook混用

1,965 阅读2分钟

最近写业务项目,已有代码是mobx+react class,正好想用react hook,于是做了如下修改下实现支持

代码职责

  • store 负责业务数据定义存储
  • action 负责操作store中数据
  • component 通过action完成数据操作,使用store数据进行内容展示,state负责ui交互状态

已有代码

Provider

import { Provider } from 'mobx-react';
export class ContainerProvider extends React.Component<any, any> {
    private injects: {
        [injectName: string]: any
    } = {};

    UNSAFE_componentWillMount() {
        const instances = injectInstance(
            assign({}, this.props.inject)
        );
        // 复杂app可以此做些初始化工作
        this.injects = {};
        instances.forEach((value, key) => {
            this.injects[key] = value;
        });
    }

    render() {
        return (
            <Provider {...this.injects}>
                {this.props.children}
            </Provider>
        );
    }
}

inject

export const injectDecorator = (injectName: string): any => (target: any, propertyKey: string, descriptor: PropertyDescriptor): any => {
    target[propertyKey] = injectName;
    // 加入一个标注变量
    if (!target['_injectVariables_']) {
        target['_injectVariables_'] = [propertyKey];
    } else {
        target['_injectVariables_'].push(propertyKey);
    }
    return descriptor;
};


export const injectInstance = (classes: {}) => {
    const classMap = new Map<string, any>();
    const instanceMap = new Map<string, any>();

    // 实例化
    Object.keys(classes).forEach((eachClassName) => {
        if (classMap.has(eachClassName)) {
            throw new Error(`duplicate className: ${eachClassName}`);
        }
        classMap.set(eachClassName, classes[eachClassName]);
    });
    classMap.forEach((eachClass: any, keyName) => {
        instanceMap.set(keyName, new eachClass());
    });
    // 注入
    instanceMap.forEach((eachInstance: any, key: string) => {
        // 遍历对象中注入类名
        eachInstance['_injectVariables_'] && eachInstance['_injectVariables_'].forEach((injectVariableKey: string) => {
            if (!instanceMap.get(eachInstance[injectVariableKey])) {
                throw new Error('injectName: ' + eachInstance[injectVariableKey] + ' not found!');
            }
            // 注入名改成实际对象
            eachInstance[injectVariableKey] = instanceMap.get(eachInstance[injectVariableKey]);
        });
        delete eachInstance['_injectVariables_'];
    });
    return instanceMap;
};

Store

export class DemoStore {
    @observable data={} 
}

Action

export class DemoAction {
	@inject('DemoStore') store: DemoStore;
    async getData() {
        this.store.data = await fetch(...);
    }
}

Component

interface Props {
    DemoStore?: DemoStore
    DemoAction?: DemoAction
}
@inject('DemoStore', 'DemoAction') 
@observer
export class Demo extends Component<Props, any> {
	render() {
        const { data } = this.props.DemoStore;
        return 
        <div onClick=()=>{this.props.DemoAction.getData()}>
        	{data}
        </div>
    }
}

新增代码

Provider

export const StoreContext = createContext(null);
export const ContextProvider = observer((props: any) => {
    const instances = injectInstance(
        assign({}, props.inject)
    );
    const injects = {};
    instances.forEach((value, key) => {
        injects[key] = value;
    });
    return (
        <StoreContext.Provider value={injects}>
            {props.children}
        </StoreContext.Provider>
    );
});

useInject

export const useInject = (name) => {
    const context = useContext(StoreContext);
    if (name && context) {
        return context[name];
    }
    return context;
};

Store

// 保持不变

Action

// 保持不变
@inject('DemoStore') store: DemoStore;
// 注意可能获取不到的情况,可放到init方法中专门调用进行初始化
// store: UCenterStore = useInject('DemoStore');

Component

import { useObserver } from 'mobx-react-lite';
export const Demo = memo((props: PropsDefine) => {
    const action: DemoAction = useInject('DemoAction');
    const store: DemoStore = useInject('DemoStore');

	useEffect(() => {
        action.getData();
    }, []);
    
    const handleClick = useCallback((item, index) => {
        // action.listItemClick(item, index);
        // action.log({
        //     k: 'h5_click',
        //     v: 'hot_words'
        // });
    }, []);
    
    const { data } = store;
    return useObserver(() => (
    	<div className='creater-page'>
        	...
        </div>
    ))
}

总结

新Provider仍然通过inject实例化及赋值对象,action&store写码方式保持不变,component中使用hook,通过usrInject获得action及store实例,通过useObserver实现ui对store的响应