@uni-store/react
Version:
Unified Store for React
312 lines (246 loc) • 7.38 kB
Markdown
# -store/react [](https://www.npmjs.com/package/@uni-store/react) [](https://github.com/dolymood/uni-store/actions/workflows/test.yml) [](https://codecov.io/github/dolymood/uni-store)
Unified Store for React.
## Installation
```bash
pnpm add -store/core @uni-store/react
# or with yarn
yarn add -store/core @uni-store/react
# or with npm
npm install -store/core @uni-store/react
```
## Usage
### Create a Store
You can create as many stores as you want:
```ts
// src/stores/counter
import { defineStore, ref, computed } from '-store/core'
export const useCounter = defineStore(() => {
const n = ref(0)
const increment = (amount = 1) => {
n.value += amount
}
const computedN = computed(() => {
return n.value + 100
})
return {
n,
increment,
computedN
}
})
```
`defineStore` returns a function that has to be called to get access to the store:
```ts
import { nextTick, computed } from '-store/core'
import { useCounter } from '@/stores/counter'
const counter = useCounter()
const stateN = computed(() => {
return counter.n
})
let calledTimes = 0
// subscribe state change
counter.$subscribe((newState) => {
calledTimes += 1
expect(newState.n).toEqual(stateN.value)
})
expect(counter.n).toEqual(0)
expect(counter.computedN).toEqual(100)
expect(stateN.value).toEqual(0)
expect(calledTimes).toEqual(0)
counter.increment()
expect(counter.n).toEqual(1)
expect(counter.computedN).toEqual(101)
expect(stateN.value).toEqual(1)
nextTick(() => {
expect(calledTimes).toEqual(1)
counter.increment(10)
expect(counter.n).toEqual(11)
expect(counter.computedN).toEqual(111)
expect(stateN.value).toEqual(11)
nextTick(() => {
expect(calledTimes).toEqual(2)
})
})
```
### With React
```tsx
import { reactiveReact } from '-store/react'
const ReactiveView = reactiveReact(function () {
const { n, computedN, increment } = useCounter()
return (
<div>
<p>You clicked {n} times</p>
<p>The computed times {computedN}</p>
<button onClick={() => increment()}>
Click me
</button>
</div>
)
})
ReactDOM.render(<ReactiveView />, document.body)
```
## Documentation
First of all, you need to read:
- Vue [Composition API/setup section](https://v3.vuejs.org/guide/composition-api-setup.html)
- Vue [Reactivity Fundamentals](https://v3.vuejs.org/guide/reactivity-fundamentals.html)
`-store/core` export all [@vue/reactivity](https://www.npmjs.com/package/@vue/reactivity) API by default.
You can use all Vue [reactivity API](https://v3.vuejs.org/api/reactivity-api.html). Includes the following API from [@vue/runtime-core](https://www.npmjs.com/package/@vue/runtime-core):
- [nextTick](https://v3.vuejs.org/api/global-api.html#nexttick)
- [watch](https://v3.vuejs.org/api/computed-watch-api.html#watch)
- [watchEffect](https://v3.vuejs.org/api/computed-watch-api.html#watcheffect)
- [watchPostEffect](https://v3.vuejs.org/api/computed-watch-api.html#watchposteffect)
- [watchSyncEffect](https://v3.vuejs.org/api/computed-watch-api.html#watchsynceffect)
### Define Store
A Store is defined using `defineStore()` API, just like [Vue setup]((https://v3.vuejs.org/guide/composition-api-setup.html)):
```ts
// @/stores/counter
import { defineStore, ref } from '-store/core'
export const useStore = defineStore(() => {
const n = ref(1)
const increment = (amount = 1) => {
n.value += amount
}
return {
n,
increment
}
})
```
Now you can get a custum `useStore`. Also you can rename it to other variable name, like `useCounter`.
#### Use Store
The store won't be created until `useStore()` is called:
```ts
import { useStore } from '@/stores/counter'
const store = useStore()
// you can use the states:
console.log(store.n) // should log 1
// increment n, amount = 10
store.increment(10)
console.log(store.n) // should log 11
```
You can even get another store instance in some special cases:
```ts
const anotherStore = useStore(true)
console.log(anotherStore.n) // should log 1
```
##### Subscribing state
```ts
// subscribe state change
store.$subscribe((state) => {
// keep the whole state to local storage whenever it changes
localStorage.setItem('cart', JSON.stringify(state))
})
```
### With React
#### reactiveReact
```tsx
import { reactiveReact } from '-store/react'
const ReactiveView = reactiveReact(function () {
const { n } = useStore()
return <p>You clicked {n} times</p>
})
ReactDOM.render(<ReactiveView />, document.body)
```
You can get a Reactive React Component by `const ReactiveComponent = reactiveReact(Component: React.FunctionComponent)`.
#### useSetup & defineSetup
- After `v0.3.0` you can use `useSetup` and `defineSetup`:
```tsx
import { reactiveReact, useSetup, defineSetup } from '-store/react'
type P = {
base: number
}
const useTimer = (reactiveProps: P) => {
const s = ref(1)
const timer = computed(() => {
return s.value + reactiveProps.base
})
const increment = (amount = 1) => {
s.value += amount
}
return {
timer,
increment
}
}
// use `defineSetup`
const useCustomTimer = defineSetup(useTimer)
const LocalTimerView = reactiveReact<P>(function (props) {
const { timer, increment: timerIncrement } = useCustomTimer(props)
// or useSetup with plain useTimer
const { timer, increment: timerIncrement } = useSetup(useTimer, props)
return (
<div>
<p>timer {timer}</p>
<button onClick={() => timerIncrement()}>
Click me
</button>
</div>
)
})
// just use `useSetup`
const LocalReactiveView = reactiveReact<P>(function (props) {
// you can also use useCustomTimer here
const { n, increment } = useSetup((reactiveProps) => {
setupCalledTimes++
const s = ref(0)
const n = computed(() => {
return s.value + reactiveProps.base
})
const increment = (amount = 1) => {
s.value += amount
}
return {
n,
increment
}
}, props)
return (
<div>
<p>You clicked {n} times</p>
<button onClick={() => increment()}>
Click me
</button>
</div>
)
})
const App = () => {
const [base, setBase] = useState(0)
return (
<div>
<LocalReactiveView base={base} />
<LocalTimerView base={base} />
<button data-testid="setBaseEle" onClick={() => setBase(base + 2)}>setBaseEle</button>
</div>
)
}
```
- After `v0.2.0`, you can use `useSetup<S, DependencyList>(() => S, DependencyList)`, but this API is **deprecated** after `v0.3.0`.
```tsx
import { ref, computed } from '-store/core'
import { reactiveReact, useSetup } from '-store/react'
const LocalReactiveView = reactiveReact(function () {
const { num, computedNum, ins } = useSetup(() => {
const num = ref(0)
const computedNum = computed(() => {
return num.value + 10
})
const ins = () => {
num.value += 2
}
return {
num,
computedNum,
ins
}
}, [])
return (
<div>
<p>Num { num }</p>
<p>ComNum { computedNum }</p>
<button onClick={ () => ins() }>Ins</button>
</div>
)
})
```
## License
[MIT](http://opensource.org/licenses/MIT)