smox

Fast 1kB state management that path-updating.

Usage no npm install needed!

<script type="module">
  import smox from 'https://cdn.skypack.dev/smox';
</script>

README

Smox .Fast 1kB state management with prefect API.

Use

npm i smox -S
import { Smox } from 'smox'

const state = {
  count: 0,
}

const actions = {
  up(state) {
    state.count++
  },
  down(state) {
    state.count--
  },
}

const effects = {
  async upAsync(actions) {
    await new Promise(t => setTimeout(t, 1000))
    actions.up()
  },
}

const store = new Smox({ state, actions, effects })

以上,smox 的部分就结束啦,创建了一个 store

React

对外暴露 Provider 和 Consumer 组件,可以方便的用于 react 组件中

为什么使用 render props 而不是 HOC?由于 hooks API 的出现,导致 HOC 只适用于 class API,render props 可同时适用于 class 和 function,是最合适的拓展机制

其中,Provider 组件接受 store 作为参数,而 Consumer 可以接受到 path 限定过的 part store

import { Provider, Consumer } from 'smox'

class App extends React.Component {
  render() {
    return (
      <>
        <Consumer>
          {({ state, actions, effects }) => (
            <>
              <h1>{state.count}</h1>
              <button onClick={actions.up}>+</button>
              <button onClick={actions.down}>-</button>
              <button onClick={effects.upAsync}>x</button>
            </>
          )}
        </Consumer>
      </>
    )
  }
}

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Path

path 是一个匹配机制,举例说明:

const state = {
  counter: {
    count: 0,
  },
}

const actions = {
  counter: {
    up(state, data) {
      //此处的 state 为同路径的 { count:0 }
      state.count += data
    },
    down(state, data) {
      state.count -= data
    },
  },
}

const effects = {
  counter: {
    async upAsync(actions) {
      //此处的 actions 为同路径的 { up(), down() }
      await new Promise(t => setTimeout(t, 1000))
      actions.up()
    },
  },
}

可以看到,跟对象下面的 counter 对象,此时的 path 是 /counter

现在我们有个 <App /> 跟组件,它默认匹配全局的 store,此时它的 path 是 /

然后 <App /> 有个子组件 <Counter />,这个组件的 path 是 /counter,那么它匹配的就是 store 对象下面的 counter 对象的属性和方法

function App() {
  //跟组件匹配的是全局 store
  return <Counter />
}

function Counter() {
  return (
    <Consumer>
      {({ state, actions, effects }) => ( 
        /*此处是 counter 对象中的 
        { state:{ count }, 
          actions:{ up(), down() }, 
          effects:{ asyncUp() } 
        }*/
        <>
          <h1>{state.count}</h1>
          <button onClick={actions.up}>+</button>
          <button onClick={actions.down}>-</button>
          <button onClick={effects.upAsync}>x</button>
        </>
      )}
    </Consumer>
  )
}

通过这个约定,我们不需要关心 store 的拆分,只需要按照规定安排 store 和 组件即可

Proxy、async/await

Proxy、async/await 可以使得 actions 代码同步,更好看

const actions = {
  up(state) {
    state.count += 1
    state.count += 2
  },
}

使用这个 polyfill 可以兼容 ie9+

同时 effects 下面,配合 async/await,也能同步的编写逻辑

const effects = {
  async upAsync(actions) {
    await new Promise(t => setTimeout(t, 1000))
    actions.up()
    actions.down()
  },
}

Immed

immed 是 smox 内部的一个子包,它和 immer 类似,但是和 path 结合使用,性能更好

import { produce } from 'smox'

class App extends React.Component {
  onClick = () => {
    this.setState(
      produce(this.state, draft => {
        draft.count++
      })
    )
  }
}

export default App

Demo

License

MIT