mtor
Version:
react 模块化 依赖注入 数据流处理
515 lines (384 loc) • 12 kB
Markdown
1. [MTOR简介](
2. [安装](
3. [基本使用](
4. [核心概念](
5. [高级特性](
6. [API参考](
7. [最佳实践](
8. [常见问题](
9. [版本迁移](
MTOR是一个基于React的响应式数据流状态管理库,它基于原生React Hooks进行了二次封装,提供了更加简洁、高效的状态管理方案。
MTOR具有以下四大特点:
1. **面向对象**:将功能模块中的所有方法、属性和数据封装成一个模型类,由MTOR自动初始化并管理类实例。
2. **依赖注入**:参考了Java Spring框架的DI(依赖注入)概念,所有模块类都是单实例的,由框架创建并维护,模块之间可以相互依赖。
3. **异步操作**:对异步方法做了大量开发体验方面的优化,异步操作之间的数据修改可以实时反馈到页面中。
4. **其他优势**:
- 通用性强,兼容Taro、React Native等React生态
- 模块跟随页面加载自动注册,无需单独写注入逻辑
- 热更新数据不丢失,包括类属性和静态属性
| 问题 | React Hooks | MTOR |
|-----|-------------|------|
| 属性保存与修改 | 一个useState只能管理一个属性 | 使用setData方法可以同时对多个属性进行修改 |
| 可读性 | 随着组件规模增大,展示与业务逻辑混在一起,可读性降低 | UI展示与业务逻辑分离,结构更清晰,可读性更好 |
| 复用性 | 组件里面的业务逻辑不可复用 | 整个模块都是可复用的 |
| 数据共享 | 必须使用useContext或属性传值,增加大量非业务代码 | 使用依赖注入,多模块轻松实现共享/内部通信 |
| 开发体验 | 热更新时所有state丢失,useMemo、useCallback等依赖项容易出错 | 热更新数据保留,不用关心依赖项更新问题 |
```bash
npm install --save mtor
yarn add mtor
```
以下是一个简单的示例,实现从后端获取随机数并在页面中展示,点击按钮可以给随机数加1:
```javascript
// HomeModel.js
import { service, Model } from 'mtor';
function ajax() { // 模拟ajax请求
return new Promise((resolve) => {
setTimeout(() => {
resolve(parseInt(Math.random() * 10, 10));
}, 16.7);
});
}
@service('home') // 或使用 @service(module.id) 或 @define(module)
class HomeModel extends Model {
num = 0;
async init() {
this.num = await ajax();
}
add() { // 普通方法
this.num++;
}
}
export default HomeModel;
```
```jsx
// HomePage.jsx
import React, { useEffect } from 'react';
import { useModel } from 'mtor';
import HomeModel from './HomeModel';
export default () => {
const model = useModel(HomeModel);
const { num } = model;
useEffect(() => {
model.init();
}, []);
return (
<div>
<div onClick={model.add}>+1</div>
<div>{num}</div>
</div>
);
};
```
```jsx
// HomePage.jsx
import React from 'react';
import { useInitModel } from 'mtor';
import HomeModel from './HomeModel';
export default () => {
const model = useInitModel(HomeModel, (m) => m.init(), true);
const { num } = model;
return (
<div>
<div onClick={model.add}>+1</div>
<div>{num}</div>
</div>
);
};
```
模块是MTOR的核心概念,通过`@service`装饰器定义:
```javascript
@service('moduleName') // 或使用 @service(module.id) 或 @define(module)
class MyModel extends Model {
// 属性定义
count = 0;
// 方法定义
increment() {
this.count++;
}
async fetchData() {
// 异步操作
}
}
```
MTOR支持模块间的依赖注入,使用`@inject`装饰器:
```javascript
import UserModel from './UserModel';
@service(module.id)
class HomeModel extends Model {
// 注入UserModel实例
@inject(UserModel)
user;
async init() {
// 可以直接使用注入的实例
console.log(this.user.name);
}
}
```
也可以使用`@resource`按名称注入:
```javascript
@service(module.id)
class HomeModel extends Model {
// 按名称注入
@resource('usermodel')
user;
}
```
在模块方法中可以直接修改属性:
```javascript
@service(module.id)
class CounterModel extends Model {
count = 0;
increment() {
this.count++; // 直接修改属性
}
}
```
可以使用`setData`方法同时修改多个属性:
```javascript
@service(module.id)
class UserModel extends Model {
name = '';
age = 0;
updateUser(name, age) {
this.setData({
name,
age
});
}
}
```
在组件中也可以直接调用`setData`:
```jsx
const model = useModel(UserModel);
// 在事件处理函数中
const handleClick = () => {
model.setData({ name: 'John', age: 30 });
};
```
MTOR对异步操作做了特别优化,可以在异步方法中直接修改属性:
```javascript
@service(module.id)
class DataModel extends Model {
data = null;
loading = false;
error = null;
async fetchData() {
try {
this.loading = true; // 修改会立即反映到UI
this.data = await api.getData();
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
}
}
```
使用`reset`方法可以将模块状态重置为初始值:
```jsx
useEffect(() => {
model.init();
return model.reset; // 组件卸载时重置模块
}, []);
```
- `@service(name: string)`:定义一个模块
- `@inject(ModelClass)`:按类型注入依赖
- `@resource(name: string)`:按名称注入依赖
基础模型类,提供以下方法:
- `setData(data: Object)`:批量设置属性
- `reset()`:重置模块状态
- `onBeforeReset(callback: Function)`:注册重置前回调
- `useModel(ModelClass)`:获取模块实例
- `useInitModel(ModelClass, initFn?, clean?)`:获取模块实例并处理初始化和清理
- `define(module)`:基于webpack模块定义模块
- `getModels()`:获取所有模型实例
- `evtBus`:事件总线实例
推荐的目录结构:
```
src/
├── models/
│ ├── HomeModel.js
│ └── UserModel.js
├── pages/
│ ├── Home/
│ │ ├── index.js
│ │ └── style.less
│ └── User/
│ ├── components/
│ │ └── UserCard/
│ │ ├── index.js
│ │ └── style.less
│ ├── index.js
│ └── style.less
└── index.js
```
1. 使用`@service(module.id)`确保模块名称全局唯一
2. 类名与文件名保持一致
3. 继承`Model`基类
4. 使用TypeScript或JSDoc提供类型信息
5. 添加热更新支持:`module.hot?.accept()`
6. 不要随意添加未声明的属性
7. 异步方法封装为Promise
8. 一个页面组件对应一个模型
1. 使用`useInitModel`代替`useModel`+`useEffect`组合
2. 页面组件中的业务逻辑放入模型中
3. 列表项组件通过属性传值,不直接使用模型
修改对象类型属性时,需要更新引用:
```javascript
// 错误方式
updateObj() {
this.obj.a = 2; // 不会触发更新
}
// 正确方式
updateObj() {
this.obj = { ...this.obj, a: 2 };
}
```
在模型文件末尾添加:
```javascript
// webpack
module.hot?.accept();
// vite
import.meta.hot?.accept();
```
对于可控组件,推荐使用`setData`方法:
```jsx
<Input
value={name}
onChange={({target: {value}}) => model.setData({name: value})}
/>
```
可能的原因:
1. 修改了对象属性但没有更新引用
2. 使用了未在类中声明的属性
3. 在构造函数中调用了异步方法
避免模块间形成循环依赖,可以使用事件总线或共享服务模式解决。
主要变化:
1. 使用`async/await`替代`generator`方法
2. 对TypeScript支持更友好
3. 底层优化,性能更高效
4. 普通异步回调可以直接通过`this`修改数据
迁移步骤:
1. 将所有`generator`方法改为`async/await`
2. 检查并更新依赖注入方式
3. 更新异步操作处理方式
```typescript
// UserModel.ts
import { service, Model } from 'mtor';
@service('usermodel')
class UserModel extends Model {
name = 'Guest';
isLoggedIn = false;
login(username: string) {
this.name = username;
this.isLoggedIn = true;
}
logout() {
this.name = 'Guest';
this.isLoggedIn = false;
}
}
export default UserModel;
// HomeModel.ts
import { service, Model, inject } from 'mtor';
import UserModel from './UserModel';
@service(module.id)
class HomeModel extends Model {
count = 0;
messages = [];
@inject(UserModel)
user;
async init() {
this.messages = await this.fetchMessages();
}
increment() {
this.count++;
}
async fetchMessages() {
// 模拟API调用
return new Promise(resolve => {
setTimeout(() => {
resolve(['Message 1', 'Message 2', 'Message 3']);
}, 1000);
});
}
}
export default HomeModel;
```
```tsx
// HomePage.tsx
import React from 'react';
import { useInitModel } from 'mtor';
import HomeModel from '../models/HomeModel';
const HomePage: React.FC = () => {
const model = useInitModel(HomeModel, m => m.init(), true);
const { count, messages, user } = model;
return (
<div>
<h1>Welcome, {user.name}</h1>
<div>
<h2>Counter: {count}</h2>
<button onClick={() => model.increment()}>Increment</button>
</div>
<div>
<h2>Messages</h2>
<ul>
{messages.map((msg, index) => (
<li key={index}>{msg}</li>
))}
</ul>
</div>
{user.isLoggedIn ? (
<button onClick={() => user.logout()}>Logout</button>
) : (
<button onClick={() => user.login('John')}>Login</button>
)}
</div>
);
};
export default HomePage;
```
---
更多信息和示例,请访问[GitHub仓库](https://github.com/sampsonli/mtor)。