别人都在用Redux/MobX,只有我在用"小众"的easy-model

4 阅读2分钟

事情是这样的

上周技术评审会上,leader问:"这个模块用什么做状态管理?"

我说:"easy-model。"

然后全场安静了5秒。

"这是什么?""没听过。""靠谱吗?""有Redux好吗?"

我深吸一口气,准备了一波反向输出。

我为什么"叛变"了

1. Redux:爱过,但真的太累了

// Redux版本
// 1. 定义action types
const INCREMENT = "counter/INCREMENT";
const DECREMENT = "counter/DECREMENT";

// 2. 定义actions
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });

// 3. 定义reducer
const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;
    default:
      return state;
  }
};

// 4. 配置store
const store = createStore(counterReducer);

// 5. 在组件中使用
const Counter = () => {
  const count = useSelector((state) => state);
  const dispatch = useDispatch();
  return (
    <div>
      <div>{count}</div>
      <button onClick={() => dispatch(increment())}>+</button>
    </div>
  );
};

一个计数器,整整60行代码。

而且随着项目变大:

  • action文件爆炸
  • reducer文件爆炸
  • selector文件爆炸
  • 中间件配置爆炸
  • 最后没人看得懂

2. MobX:类型推来推去,心累

// MobX版本
class CounterStore {
  @observable count = 0;

  @action
  increment() {
    this.count += 1;
  }
}

const store = new CounterStore();

// 组件
const Counter = observer(() => {
  return <div>{store.count}</div>;
});

看起来简单?但实际项目中:

// 稍微复杂点的场景
class OrderStore {
  @observable orders: Order[] = [];
  @observable loading = false;

  @computed
  get totalAmount() {
    return this.orders.reduce((sum, o) => sum + o.amount, 0);
  }

  @action
  async fetchOrders() {
    this.loading = true;
    try {
      const res = await api.getOrders();
      runInAction(() => {
        this.orders = res.data;
      });
    } finally {
      runInAction(() => {
        this.loading = false;
      });
    }
  }
}

然后TypeScript开始报错:

  • "Type 'Order[]' is not assignable to type 'Order'"
  • "Cannot read property 'reduce' of undefined"

光修类型问题就能耗费一下午。

easy-model:真香

// easy-model版本
class CounterModel {
  count = 0;

  increment() {
    this.count += 1;
  }

  decrement() {
    this.count -= 1;
  }
}

function Counter() {
  const counter = useModel(CounterModel, []);

  return (
    <div>
      <div>{counter.count}</div>
      <button onClick={() => counter.increment()}>+</button>
    </div>
  );
}

不到30行,包含类型定义。

同事问我:它有什么亮点?

我掰着指头数:

1. 类模型 + IoC

// 依赖注入,spring既视感
const apiSchema = object({ baseUrl: string() });

class UserApi {
  @inject(apiSchema)
  config?: { baseUrl: string };

  async getUser(id: string) {
    return fetch(`${this.config?.baseUrl}/users/${id}`);
  }
}

2. 深度监听

// 嵌套对象变化也能监听
watch(order, (keys, prev, next) => {
  // keys: ['items', 0, 'price']
});

3. History内置

const history = useModelHistory(model);
history.back(); // 撤销
history.forward(); // 重做
history.reset(); // 重置

4. Loader加载状态

class UserModel {
  @loader.load()
  async fetchUser() {
    // ...
  }
}

function UserPage() {
  const { isLoading } = useLoader();
  // isLoading(userModel.fetchUser) 直接获取单个方法状态
}

性能实测

方案10万元素批量更新
Zustand0.6ms
easy-model3.1ms
MobX16.9ms
Redux51.5ms

有IoC能力的状态管理方案里,easy-model没有对手。

现在

评审结束了,leader说:

"这个库确实有点东西,下个项目可以试试。"


Github: github.com/ZYF93/easy-…

不是Redux用不起,而是easy-model更有性价比。

点个⭐️,支持下这个"小众"但真好用的库 🙏