专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

react 最新数据流管理库mtor正式上线

ins518 2024-09-11 09:35:14 技术文章 28 ℃ 0 评论

mtor 是什么?

mtor 是一个基于react 类响应式数据流状态管理库, 本身是基于原生react hooks 进行了二次封装,对比原生react hooks:

问题

react hooks

mtor

属性保存与修改

一个useState 只能管理一个属性

是用setData方法可以同时对多个属性进行修改

可读性

随着组件规模增大,展示与业务逻辑混在一起,可读性直线降低, 也更容易出错

ui展示与业务了逻辑分离,结构更清晰,可读性更好

复用性

组件里面的业务逻辑不可复用

整个模块都是可复用

数据共享

必须使用useContext,或属性传值,增加大量非业务代码

使用依赖注入, 多模块轻松实现共享/内部通信

开发体验

热更新,所有state丢失,useMemo,useCallback,等依赖项很容易出错

热更新数据保留,不用关心依赖项更新问题



从一个简单demo开始

实现一个简单小需求, 从后端接口获取一个随机数,展示在页面中, 页面有一个按钮,点击给获取的随机数+1

1. 定义模块类

import {service, Model} from 'mtor';
function ajax() { // 模拟ajax请求
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(parseInt(Math.random() * 10, 10));
        }, 16.7);
    });
}
@service('home')
class HomeModel extends Model {
    num = 0;

    async init() {
        this.num = await ajax();
    }

    add() { // 普通方法
        this.num ++;
    }
}
export default HomeModel;

-说明

  1. @service('home') 定义一个模块, 每个模块必须添加此注解, 其中home 是自己给模块取的名称, 如果不想取名,也可直接用module.id, 比如@service(module.id);
  2. mtor 大量依赖最新注解语法, 需要配置相应babel插件(@babel/plugin-proposal-decorators);
  3. Model 是个类接口, 主要是给model实例和类提供接口api和属性;
  4. init() 是一个异步方法, 调用接口返回给num属性;
  5. add() 是定义的普通类方法, 此方法给类属性num 加1;
  6. num 是一个类属性, 页面中可以直接使用;

2. 在页面中使用 model (useModel)

页面引入model目前仅支持方法组件(使用hooks语法),使用方法如下:

import React, {useEffect} from 'react';
import {useModel} from 'mtor';
import style from './style.less';
import HomeModel from '../../models/HomeModel';

export default () => {
    const model = useModel(HomeModel);
    const {
        num,
    } = model;
    useEffect(() => {
        model.init();
    }, []);
    return (
        <div className={style.container}>
            <div className={style.content}>
                <div className={style.addOne} onClick={model.add}>
                    +1
                </div>
                <div className={style.txt}>
                    {num}
                </div>
            </div>
        </div>
    );
};
  • 说明
  1. model 是HomeModel的一个实力, 通过useModel 方法获取, 传一个类型类;
  2. 页面加载后调用model中定义的init方法,获取随机数;
  3. (小技巧) model中所有方法已经绑定过this了, 可以单独拿出来直接使用;

3. 在页面中使用 model (useModel 二次封装版 useInitModel)

在上面案例中使用useModel写法 每次页面加载都会重新执行init方法, 如果开发情况下, 修改了页面, 会导致页面热跟新,其实并不需要再次执行init方法, 可以用useInitModel 取代 useModel, 方法如下:

export default () => {
    const model = useInitModel(HomeModel, () => model.init(), true);
    const {
        num,
    } = model;
    return (
        <div>
            {/* // 业务代码   */}
        </div>
    );
};
  • 说明
  1. useInitModel 底层基于useModel, useEffect 的二次封装方法;
  2. 第二个参数是第一次加载初始化方法,第三个参数是退出是否调用reset方法;
  3. useInitModel开发模式做了优化, 页面热更新的时候不会再次调用reset 与初始化方法;

三、深入理解 mtor

1. 依赖注入(DI)

以上案例基本上可以满足绝大部分业务需求, 但是有时候我们定义了多个model, model之间需要有数据共享, 在mtor 引入了依赖注入(Dependency Inject), 模块之间可以相互依赖, 框架会根据配置类型自动注入进来。举个例子,还是在上面的案例中, HomeModel 依赖另外 一个UserModel, UserModel 中定义了name 属性, HomeModel 初始化后拿到UserModel中的name,并展示在页面中;

1. 定义需要依赖的model

import {Model, service} from 'mtor';

@service('usermodel')
class UserModel extends Model {
  name = 'hello user';
}
export default UserModel;
  • 此处定义了UserModel, 里面有name 属性

2. 定义准备依赖的model

import UserModel from './UserModel';

@service(module.id) // 也可以直接使用模块标识
class HomeModel extends Model {
    num = 0;
    username;
    
    /**
     * 声明user类型
     * @type {UserModel}
     */
    @inject(UserModel) 
    user;

    async init() {
        this.num = await ajax();
        this.username = this.user.name; // 可以在model方法中直接使用UserModel 中name 属性
        // this.user.name = 'sampsonli' // ***不可以***直接修改被注入属性中的值, 应该调用被注入属性中的方法修改其值
        // this.user.setData({name: 'sampsonli'} // 可以调用UserModel 中setData 为name 赋值
    }

    add() {
        this.num ++;
    }
}
export default HomeModel;
  • 说明
  1. @inject(UserModel),给属性注入UserModel 的实例;
  2. 注入的实例,类方法中可以获取实例属性, 也可以调用注入实例的方法, 但是不能直接修改实例的属性, 只能通过setData方法或者UserModel 中单独定义方法去设置;
  3. 如果使用es语法,被注入的属性前面建议加上jsDoc注释,表明属性类型,方便后续使用实例属性和方法;

3. 最后在页面中展示数据

import React, {useEffect} from 'react';
import {useModel} from 'mtor';
import style from './style.less';
import HomeModel from '../../models/HomeModel';

export default () => {
    const model = useModel(HomeModel);
    const {
        num,username, user,
    } = model;
    useEffect(() => {
        model.init();
    }, []);
    return (
        <div className={style.container}>
            <div className={style.content}>
                <div className={style.addOne} onClick={model.add}>
                    +1
                </div>
                <div className={style.txt}>
                    {num}
                </div>
                <div className={style.txt}>
                    {username}-{user.name}
                </div>
            </div>
        </div>
    );
};
  • 说明
  • 也可以直接使用被注入属性中的属性值, 当user数据有更新时,会同步到页面中。

2. 定义model注意事项

  1. 不管是普通方法,还是异步方法, 尽量不要定义为箭头方法, 箭头方法在类中是以普通属性存在, 不能使用类中定义的属性;
  2. 保留字 setData, reset, ns,不能用于自定义方法、属性名; 自定义onCreated, onBeforeClean 会根据满足某些条件自动调用;
  3. 当修改模块中对象类型的属性时, 需要同时更新对象引用值。例如:@service(module.id) class DemoModel extends Model { obj = {a: 1}; updateObj() { this.obj.a = 2; // 错误的做法 } updateObj2() { this.obj = {...this.obj, a: 2}; // 正确的做法 } } export default DemoModel;
  4. 使用属性前,必须先在类中声明, 否则动态添加的属性不生效@service(module.id) class DemoModel extends Model { setA() { this.a = 111; // 此处修改不会反馈到页面中 } } export default DemoModel;

3. 初始化方法

有时候会遇到这种场景, 模块加载的时候进行一些初始化操作(注意不是初始化值), 初始化操作可以定义onCreated方法来实现

@service(module.id)
class CreatedModel extends Model {
    num = 0;
    constructor() { // 构造方法只能初始化变量
        this.num = 1;
        // this.ajaxGet()// 不能直接调用模块中的方法
    }
    ajaxGet() {
        // 方法逻辑
    }
    onCreated() { // 如果定义了onCreated方法,此方法在模块加载的时候会自动执行(注意,热跟新的时候也不会执行哦。。。)
        thia.ajaxGet() // 此方法中可以调用模块中的方法进行初始化
    }
}
export default CreatedModel;
  • 最佳实践, 尽量减少onCreated方法使用, 在模块类中定义init方法,然后放入组件的 React.useEffect方法中调用。

4. 模块生命周期方法

目前只提供了onCreated, onBeforeClean 钩子方法, onCreated 前面已经介绍过了, onBeforeClean 在调用 reset方法前自动调用, 可以用来进行一些数据清理工作, 比如取消事件注册,定时器任务等等。

4. setData 妙用

1. 便捷地操作model中的数据

有时候页面中需要修改model中的数据, 如果只是修改少量数据,新定义一个方法会大大增加业务代码量, 可以使用 model.setData(params)方法 params是一个普通对象, key是要修改的属性名, value是修改后的值。

export default () => {
    const model = useModel(HomeModel);
    const {
        num,username
    } = model;
    useEffect(() => {
        model.init();
    }, []);
    return (
        <div className={style.container}>
            <div className={style.content}>
                <div className={style.addOne} onClick={() => model.setData({num: num + 1})}>
                    +1
                </div>
                <div className={style.txt}>
                    {num}
                </div>
                <div className={style.txt}>
                    {username}
                </div>
            </div>
        </div>
    );
};
  • 用 model.setData({num: num + 1}) 取代 model.add 方法, 可以减少代码量, 但是缺点是每次页面渲染都会生成一个新方法, 可能对性能优化不是很友好, 具体取舍看业务场景吧! setData 所设置的属性名尽量是模块类中存在的属性, 比如上例 setData({num2: 33}) 设置一个新属性num2, 虽然运行没问题, 但是不提倡这样写。 ####2. 给model动态添加新属性
@service(module.id)
class SetDataModel extends Model {
    num = 0;
    addNum2() {
       this.num2 = 100; // 此方法不会在页面中获取num2
       this.setData({num3: 200}); // 可以在页面中获取num3
    }
}
export default SetDataModel;

-说明

  1. 正常情况下, 属性应该先声明再使用,尽量减少添加没有在类中声明的属性
  2. 也可以在页面中调用setData 动态添加属性

5. 重置model中的所有数据到初始值

组件销毁的时候, 我们要清空现有的数据, 我们可以调用 model.reset;

export default () => {
    const model = useModel(HomeModel);
    const {
        num,username
    } = model;
    useEffect(() => {
        model.init();
        return model.reset; // 当前组件销毁的时候会调用 model.reset() 方法
    }, []);
    return (
        <div className={style.container}>
            <div className={style.content}>
                <div className={style.addOne} onClick={() => model.setData({num: num + 1})}>
                    +1
                </div>
                <div className={style.txt}>
                    {num}
                </div>
                <div className={style.txt}>
                    {username}
                </div>
            </div>
        </div>
    );
};

-说明

  1. 可以用 useInitModel 简化以上逻辑。

总结:

以上包含mtor的基本用法。 欢迎大家在评论区讨论, 以及在github 上star,
详情可以查看https://github.com/sampsonli/mtor。您的意见是我持续不断优化的动力。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表