meng
TypeScript icon, indicating that this package has built-in type declarations

3.4.6 • Public • Published

mengbi

an arbitrary magic react state management for rxjs enthusiast.

安装

npm i meng --save

灵感

众所周知,react是一个侧重同步数据到dom的库,也就是(data) => dom. 这里的data包含了propsstate,但是其他的数据源不好管理,比如说api。 而现有的方案用起来都太复杂了,本来(data) => dom一个函数能解决的问题,用了flux架构比如redux之后,需要的模块*3。所以能不能用一种简单的方式把所有数据源都抽象成data, 并且能用react的方式解决问题呢,答案是肯定的,我们可以这么设计({props, state, api}) => dom。于是就有了meng--一个把所有数据源都抽象合并成一种数据类型的库。 由于只有rxjs能帮我们实现流的特性,所以meng@3.x是强依赖rxjs的。

api

Store

每一个meng组件都有自己的store,store的结构是这样的:

interface Store<S> {
    state$: ReplaySubject<S> // store的触发器
    store$: Observable<S> // store的状态容器
    children: { [key: string]: Store<Object> } //存放子store,因为是扁平结构,所以只有根store的children才有子节点
    setState: Function, // 设置store$的状态
    subscribe: (success: (state: Object) => void, error?: (error: Error) => void, complete?: () => void) => Subscription // 订阅store$
}

所有的meng组件的状态又都会放在根节点的children里面,所以meng支持直接修改和订阅另一个组件的状态。也可以把store想象成ng的rootScope, 而children里面的store相当于ng里controller对应的scope。下面是一段demo:

import Store from 'meng'
 
Store.children.App.setState({user: {name: "corol"}}, error => {})

lift: (initialState, initialName) => React.Component

lift函数可以把react组件提升为meng组件,他只有两个参数:

  • initialState 初始化meng组件的状态
  • initialName 设置store的名字,如果不显式的设置,则默认使用displayName、函数名称、随机一个名字,按优先级排序。

inject: (Resource, string | (currentState, nextState) => object) => React.Component

  • Resource 给组件注入数据源,可以是promise,也可以是其他组件的store,也可以是函数。如果是函数,则第一个参数是 initialState,包括父级传下来的属性。
  • selector 注入到react组件的props的变量的名称,可以是stirng也可以是返回一个对象(会覆盖store里的其他状态)

listen: ((currentStore, nextStore) => any, string | (nextState, currentState) => object) => React.Component

可以监听 lift 里面的状态和其他 injected 数据源. 从3.4.0开始,也可以监听自己和其他 listen 数据源了,所以要判断好循环边界哦.

error: (errorHandler: (err: any) => void) => React.Component

在 meng 把状态提升之后,状态不在只是 Object 了,它现在是 Maybe(Observable),有可能会失败。输出成功的结果到组件,输出错误的结果到这个 errorhandler 里. 如果出错且没有注册 error,则什么事情都不会发生。如果已经注册,则调用它。注意,如果是通过 setState 传进去一个错误的 Observable 进去的话,只能在 setState 的 callback 捕获到异常信息

Example

展示一段我用meng写的博客里的代码吧:

const listenList = (currentStore: Props, nextStore: Props) => {
  return currentStore.latest !== nextStore.latest ? list("C0PKC07FB", nextStore.latest) : null
}
 
const listSelector = (currentState: Props, state: ISlackListType) => {
  const previousMessages = (currentState.post && currentState.post.messages) || []
  state.messages = previousMessages.concat(state.messages)
  return { post: state }
}
 
@inject(connect, "newmsg")
@listen(listenList, listSelector)
@inject(user, "user")
@lift({ latest: "0", newmsg: [] as Array<ISlackUserMessage & ISlackBotMessage> }, "Slack")
export default class Slack extends React.Component<Props, void> {
  public render() {
    return (
      <div className={Style.SLACK}>
        <Title />
        <TextInput />
        <Flex flexGrow={1} flexDirection={"row"}>
          <Messages newmsg={this.props.newmsg} post={this.props.post} user={this.props.user} latest={this.props.latest} />
          <Users user={this.props.user} />
        </Flex>
        </div>
    )
  }
}

详细信息请移步我的博客

高阶用法

因为inject可以接受并订阅你的任意类型数据,所以你可以随意组合你的数据源,也可以组合成高阶数据源(依赖其他数据源的数据源),并把他们作为你视图的数据层。

//组合数据源
didmount() {
  fetch("xxx")
    .then(x => fetch("yyy"))
    .then(yyy => this.setState({yyy}))
}
 
//数据源订阅数据源
didmount() {
  fetch("xxx")
    .then(xxx => {
      this.setState({xxx})
      return fetch("yyy")
    })
    .then(yyy => this.setState({yyy}))
}
 
//商品详情 url: xxx/:pid
componentReceiveProps(nextProps) {
  if (this.props.pid !== nextProps.pid) getById(pid).then(data => this.setState({data}))
}
 
componentDidMount() {
  getById(pid).then(data => this.setState({data}))
}
 
//websocket
didmount() {
  this.ws = new Websocket(url)
 
  ws.onmessage = (message) => {
    this.setState(message)
  }
}
 
willUnmount() {
  ws.close()
}

mengbi.jsÏ

//组合数据源
@inject(() => fetch(xxx).then(xxx => fetch(yyy)), "yyy")
@lift({yyy: null})
 
//数据源订阅数据源
@inject((currentStore, nextStore) => currentStore.xxx !== nextStore.xxx ? fetch(yyy) : null, "yyy")
@inject(() => fetch(xxx), "xxx")
@lift({xxx: null, yyy: null})
 
//商品详情 url: xxx/:pid
@inject((currentStore, nextStore) => currentStore.pid !== nextStore.pid ? getById(nextStore.pid) : null, "data")
 
//websocket(rxjs)
@inject(() => Observable.webSocket(url), "message")Ï

为什么要使用mengbi

  • 帮你去掉了组件生命周期,组件可以写更纯粹的业务(render, handle)
  • 声明式的注入数据源,而不是命令式的dispatch或者手动setState
  • 可订阅state和props和,而react只能订阅props: componentWillReceiveProps
  • 响应式设计,只需要考虑怎么写数据源,组合数据源和触发数据源
  • 跨组件通讯, 比如你有个Dialog组件被lift了,只需要Store.children.Dialog.setState({display: "open"})就能打开这个对话框了

使用上的建议

因为meng是管理数据源为主,夸组件通讯为辅的。你可以像redux那样直接注入数据源,但是应该尽量避免让组件从其他地方获取数据,原则上组件获取数据的方式只有一个,就是父组件, 所有的数据源都应该从视图开始注入,然后传到下面的子组件,这是符合依赖倒置原则的。上面的示例就是一个正确的用法。

其他

发现一款和我思路很像的库,强烈建议看看freactal 发现第二个和我思路很像的库,preact版本,可以说这个库的思路和我基本是完全一样了: wiretie

另附一篇我之前写的文章我为什么不用redux

Readme

Keywords

Package Sidebar

Install

npm i meng

Weekly Downloads

14

Version

3.4.6

License

ISC

Last publish

Collaborators

  • xaomon