Skip to content

anthhub/matrox

Repository files navigation

GitHub license Build Status Coverage Status

特性

  • 两个 API, 符合直觉, 几乎无需学习成本
  • 支持类 redux 中间件, 方便拓展
  • 支持类 mobx 依赖收集, 类 react action 合并和批量更新, 优化性能
  • 支持预加载和持久化
  • 完美的 TypeScript 支持
  • 支持多数据源, matrox => matrix
  • 支持 function 和 class 组件

在线体验

Edit

安装

yarn add matrox
npm install --save matrox

快速上手

创建一个 store

matrox 的 store 用法和 mobx 比较类似, 使用 class 形式继承 StoreBase, 则 store 实例拥有的 setProps 方法来改变状态. 和 react 类似, 只有通过 setProps 才能触发更新, 即单向数据流, 直接修改实例属性是不允许的. setProps 也可以接受 plain onject,和 返回 plain objectcallback(就像 setState 那样), 并有完善的类型推断.

注意:setProps 返回 Promise 也是异步更新的, 会合并多个 action, 并且多个 store 的多个 setProps 也会合并在某个时机一起更新.

通过 createStore 可以把 store class 生成 hooks -- useStore 在 funcion 组件使用; injectStore 在 class 组件中使用

import { StoreBase, createStore } from 'matrox'

class CounterStore extends StoreBase<CounterStore> {
  count = 0
  increment = () => this.setProps({ count: this.count + 1 })
  decrement = () => this.setProps(({ count }) => ({ count: count - 1 }))
}

const { useStore, injectStore } = createStore(CounterStore)

使用 store

还记得刚刚的 useStore 的返回值吗?在 function 组件中调用这个 Hook , 就可以获取到 store 了. 当订阅的 store 状态发生改变的时候, 将会触发组件重渲染.

export default function App() {
  const { count, increment, decrement } = useStore()
  return (
    <div className="App">
      <h1>Hello Matrox</h1>
      <h1>{count}</h1>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
    </div>
  )
}

class 组件中使用 store

现在 injectStore 要大显生手了, 它其实是属性属性装饰器, 在 class 使用它, 就可以获取到 store 了. 当然了, 你可以同时 'inject' 多个 store. 是不是 很像 Spring 呢, 没错, matrox 内部实现其实就是一个 DI 容器, 由它来帮我们实例化 store 实例

export default class App extends React.Component {
  @injectStore()
  counterStore: Readonly<CounterStore>

  render() {
    const { count, increment, decrement } = this.counterStore

    return (
      <div className="App">
        <h1>Hello Matrox</h1>
        <h1>{count}</h1>
        <button onClick={increment}>increment</button>
        <button onClick={decrement}>decrement</button>
      </div>
    )
  }
}

注意: 无论是 useStore,injectStore 和后面会提到的 getStore, 返回的 store 实例都是只读的!

进阶用法

store 的多例模式

有的时候我们需要同时创建多个 store 的示例, 这个时候我们可以传入 identification 来索引其中之一.

如下方的例子一样, 我们可以在 useStore 或者 injectStore 传入参数, 例如在 function 组件中:

import { StoreBase, createStore } from 'matrox'

class CounterStore extends StoreBase<CounterStore> {
  count = 0
  increment = () => this.setProps({ count: this.count + 1 })
  decrement = () => this.setProps(({ count }) => ({ count: count - 1 }))
}

const { useStore, injectStore } = createStore(CounterStore)

export default function App(props) {
  const { count, increment, decrement } = useStore(props.id)
  return (
    <div className="App">
      <h1>Hello Matrox</h1>
      <h1>{count}</h1>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
    </div>
  )
}

store 之间的依赖

一般来说, 我们推荐组合多个 store 使用, 这样更方便防止命名冲突, 减轻心智负担, 便于查找. 但是 store 之间很可能会出现需要依赖其他 store 的情况.

matrox 中非常简单: 由于 store 和 class 组件都是 class 形态的, 所以在 store 中引用 store 和 class 组件中完全一样! 当被依赖的 store 更新, 会触发依赖它的 store 更新.

import { StoreBase, createStore } from 'matrox'

// store1
class CounterStore extends StoreBase<CounterStore> {
  count = 0
  increment = () => this.setProps({ count: this.count + 1 })
  decrement = () => this.setProps(({ count }) => ({ count: count - 1 }))
}

const { useStore, injectStore } = createStore(CounterStore)

//  store2
class DoubleCounterStore extends StoreBase<DoubleCounterStore> {
  @injectStore()
  counterStore: Readonly<CounterStore>

  get doubleCount() {
    return this.counterStore.count
  }
}

const { useStore: useDoubleCounterStore, injectStore: injectDoubleCounterStore } = createStore(
  DoubleCounterStore
)

提醒: 小心循环依赖!

忽略订阅

使用过 mobx 的同学可能知道, 有时候我们并不需要观察某个数据, 仅仅把它当做全局变量使用, 我们应该如何做呢?

如下面的例子一样,我们只需要加入 @ignore 装饰器就可以了:

import { StoreBase, createStore } from 'matrox'

class CounterStore extends StoreBase<CounterStore> {
  @ignore count = 0
}

组件外获取 store 的状态

有时候我们需要在组件外使用 store, 这时候我们可以使用 getStoregetState, 如下:

import { StoreBase, createStore } from 'matrox'

class CounterStore extends StoreBase<CounterStore> {
  count = 0
}

// getStore 获取 store 实例, getState 获取 store 序列化之后的 plain object
const { getStore, getState } = createStore(CounterStore)

应用中间件

matrox 可以使用中间件! 其中间件写法 redux 中间件非常相似!

import { StoreBase, createStore } from 'matrox'

class CounterStore extends StoreBase<CounterStore> {
  count = 0
}

// matrox 自带了 persist 中间件, 有兴趣可以参考源码
const { getStore, getState, injectStore } = createStore(CounterStore, { middleware: [persist] })

当 store 太多的时候, 一个一个配置是十分麻烦的事所以提供了 globalConfig api 来提供全局配置, 在特定的 store 你还可以忽略某个中间件.

import { StoreBase, createStore, globalConfig } from 'matrox'

globalConfig({ middleware: [persist] })

class CounterStore extends StoreBase<CounterStore> {
  count = 0
}

// matrox 自带了 persist 中间件, 有兴趣可以参考源码
const { getStore, getState, injectStore } = createStore(CounterStore, {
  ignoreMiddleware: [persist]
})

你完全可以自己定义中间件, 来加强你的 store, 以下是内置 persit 中间件示例:

import { MiddleWare, StoreAPI, Dispatch } from '../types/middleware'
import { Action } from '../types/StoreBase'

const persist: MiddleWare = (store: StoreAPI) => {
  const { key, isSessionStore } = store.meta

  const state = JSON.parse(localStorage.getItem(key) || '{}')
  store.updatePropsWithoutRender(state)

  return (dispatch: Dispatch) => async (action: Action<any, string>) => {
    await dispatch(action)

    if (!isSessionStore) {
      const state = store.getState()
      localStorage.setItem(key, JSON.stringify(state))
    }
  }
}

export default persist

内置持久化

matrox 内置的持久化有更简洁的写法, 当然你也可以使用globalConfig来全局配置.

import { StoreBase, createStore } from 'matrox'

class CounterStore extends StoreBase<CounterStore> {
  count = 0
}

// getStore 获取 store 实例, getState 获取 store 序列化之后的 plain object
const { getStore, getState } = createStore(CounterStore, { persist: true })

预加载

matrox 有预加载 store 的功能. 例如你需要请求一个非常大的 menu tree 数据, 你可以使用 preloadStore 提前实例化 store, 来预加载数据:

// store.ts
import { StoreBase, createStore } from 'matrox'

class CounterStore extends StoreBase<CounterStore> {
  data: any[] = []

  constructor() {
    super()
    this.fetchData()
  }

  fetchData = () => {
    fetch(`/tree`).then(data => (this.data = data))
  }
}

// getStore 获取 store 实例, getState 获取 store 序列化之后的 plain object
const { preloadStore } = createStore(CounterStore)

// app.ts
preloadStore()

依赖收集和性能优化

  • matrox 内部使用和 mobx 一样的 Proxy api 来进行依赖收集(只收集浅层数据,所以 store 内部数据无需toJS), 所以当你组件只订阅了其使用 store 属性的更新.
  • 为了应用批量更新策略, store 继承的 setProps, forceUpdate 方法均是异步的, 如果需要使用更新之后的数据, 请使用.then()方法, 或者使用 await 关键字.

About

A micro react state manager using like matrix

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published