@uni-store/react
Unified Store for React.
Installation
pnpm add @uni-store/core @uni-store/react
# or with yarn
yarn add @uni-store/core @uni-store/react
# or with npm
npm install @uni-store/core @uni-store/react
Usage
Create a Store
You can create as many stores as you want:
// src/stores/counter
import { defineStore, ref, computed } from '@uni-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:
import { nextTick, computed } from '@uni-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
import { reactiveReact } from '@uni-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:
@uni-store/core
export all @vue/reactivity API by default.
You can use all Vue reactivity API. Includes the following API from @vue/runtime-core:
Define Store
A Store is defined using defineStore()
API, just like Vue setup:
// @/stores/counter
import { defineStore, ref } from '@uni-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:
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:
const anotherStore = useStore(true)
console.log(anotherStore.n) // should log 1
Subscribing state
// 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
import { reactiveReact } from '@uni-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 useuseSetup
anddefineSetup
:
import { reactiveReact, useSetup, defineSetup } from '@uni-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 useuseSetup<S, DependencyList>(() => S, DependencyList)
, but this API is deprecated afterv0.3.0
.
import { ref, computed } from '@uni-store/core'
import { reactiveReact, useSetup } from '@uni-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>
)
})