From: Eduardo San Martin Morote Date: Mon, 28 Sep 2020 13:58:25 +0000 (+0200) Subject: refactor: rename createStore to defineStore X-Git-Tag: v2.0.0-alpha.3~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a9ad160bb38d6bfae3a52c66ae28793937af05d6;p=thirdparty%2Fvuejs%2Fpinia.git refactor: rename createStore to defineStore BREAKING CHANGE: renamed `createStore` to `defineStore`. `createStore` will be marked as deprecated during the alpha releases and then be dropped. --- diff --git a/README.md b/README.md index 969b0ffd..f1f28be6 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,8 @@ A few notes about the project and possible questions: ## Roadmap / Ideas - [x] Should the state be merged at the same level as actions and getters? -- [ ] Allow grouping stores together into a similar structure and allow defining new getters (`pinia`) +- [ ] ~~Allow grouping stores together into a similar structure and allow defining new getters (`pinia`)~~ + You can directly call `useOtherStore()` inside of a getter or action. - [ ] Getter with params that act like computed properties (@ktsn) ## Installation @@ -82,16 +83,16 @@ app.use(createPinia()) ``` This will also add devtools support. -**NOTE**: this API is still experimental and is currently only used for devtools support but that might change in the future +**NOTE**: this API is still experimental and is currently only used for devtools and SSR but that might change in the future. ### Creating a Store You can create as many stores as you want, and they should each exist in different files: ```ts -import { createStore } from 'pinia' +import { defineStore } from 'pinia' -export const useMainStore = createStore({ +export const useMainStore = defineStore({ // name of the store // it is used in devtools and allows restoring state id: 'main', @@ -120,7 +121,7 @@ export const useMainStore = createStore({ }) ``` -`createStore` returns a function that has to be called to get access to the store: +`defineStore` returns a function that has to be called to get access to the store: ```ts import { useMainStore } from '@/stores/main' @@ -141,9 +142,9 @@ export default defineComponent({ }) ``` -Note: the SSR implementation is yet to be decided on Pinia, but if you intend having SSR on your application, you should avoid using `useStore` functions at the root level of a file to make sure the correct store is retrieved for your request. +Note: the SSR implementation is yet to be decided on Pinia, but if you intend having SSR on your application, you should avoid using `useStore` functions at the root level of a file to make sure the correct store is retrieved for your request. Here is an example: -Or: +**Avoid doing this\***: ```ts import { createRouter } from 'vue-router' @@ -155,12 +156,12 @@ const router = createRouter({ const main = useMainStore() router.beforeEach((to, from, next) => { - if (main.state.isLoggedIn) next() + if (main.isLoggedIn) next() else next('/login') }) ``` -It must be called **after the Composition API plugin is installed**. That's why calling `useStore` inside functions is usually safe, because they are called after the plugin being installed: +Instead, call `useMainStore()` at the top of `setup`, like `inject` and `provide` in Vue: ```ts export default defineComponent({ @@ -174,12 +175,11 @@ export default defineComponent({ // In a different file... -router.beforeEach((to, from, next) => { +router.beforeEach((to) => { // ✅ This will work (requires an extra param for SSR, see below) const main = useMainStore() - if (main.state.isLoggedIn) next() - else next('/login') + if (to.meta.requiresAuth && !main.isLoggedIn) return '/login' }) ``` @@ -191,9 +191,13 @@ You can access any property defined in `state` and `getters` directly on the sto export default defineComponent({ setup() { const main = useMainStore() - const text = main.name - const doubleCount = main.doubleCount - return {} + const text = main.name // "eduardo" + const doubleCount = main.doubleCount // 2 + + return { + text, // will always be "eduardo" + textDynamic: computed(() => main.name), // reactive value + } }, }) ``` @@ -242,7 +246,7 @@ main.patch({ }) ``` -The main difference here is that `patch` allows you to group multiple changes into one single entry in the devtools (which are not yet available for Vue 3.x). +The main difference here is that `patch` allows you to group multiple changes into one single entry in the devtools. ### Replacing the `state` @@ -254,7 +258,34 @@ main.state = { counter: 666, name: 'Paimon' } ### SSR -To be decided once SSR is implemented on Vue 3 +Pinia should work out of the box for SSR as long as you call your `useStore()` functions at the top of `setup` functions, `getters` and `actions`: + +```ts +export default defineComponent({ + setup() { + // this works because pinia knows what application is running + const main = useMainStore() + return { main } + }, +}) +``` + +If you need to use the store somewhere else, you need to pass the `pinia` instance [that was passed to the app](#install-the-plugin) to the `useStore()` function call: + +```ts +const pinia = createPinia() +const app = createApp(App) + +app.use(router) +app.use(pinia) + +router.beforeEach((to) => { + // ✅ This will work make sure the correct store is used for the current running app + const main = useMainStore(pinia) + + if (to.meta.requiresAuth && !main.isLoggedIn) return '/login' +}) +``` ### Composing Stores @@ -264,17 +295,43 @@ If **multiple stores use each other** or you need to use **multiple stores** at If one store uses an other store, there is no need to create a new file, you can directly import it. Think of it as nesting. +You can call `useOtherStore()` at the top of any getter an action: + +```ts +import { useUserStore } from './user' + +export const cartStore = defineStore({ + id: 'cart', + getters: { + // ... other getters + summary() { + const user = useUserStore() + + return `Hi ${user.name}, you have ${this.list.length} items in your cart. It costs ${this.price}.` + }, + }, + + actions: { + purchase() { + const user = useUserStore() + + return apiPurchase(user.id, this.list) + }, + }, +}) +``` + #### Shared Getters If you need to compute a value based on the `state` and/or `getters` of multiple stores, you may be able to import all the stores but one into the remaining store, but depending on how your stores are used across your application, **this would hurt your code splitting** because importing the store that imports all others stores, would result in **one single big chunk** with all of your stores. To prevent this, **we follow the rule above** and we create a new file with a new store: ```ts -import { createStore } from 'pinia' +import { defineStore } from 'pinia' import { useUserStore } from './user' import { useCartStore } from './cart' -export const useSharedStore = createStore({ +export const useSharedStore = defineStore({ id: 'shared', getters: { summary() { @@ -292,11 +349,11 @@ export const useSharedStore = createStore({ When an actions needs to use multiple stores, we do the same, we create a new file with a new store: ```ts -import { createStore } from 'pinia' +import { defineStore } from 'pinia' import { useUserStore } from './user' import { useCartStore } from './cart' -export const useSharedStore = createStore({ +export const useSharedStore = defineStore({ id: 'shared', state: () => ({}), actions: { @@ -315,42 +372,6 @@ export const useSharedStore = createStore({ }) ``` -#### Creating _Pinias_ - -_Not implemented_. Still under discussion, needs more feedback as this doesn't seem necessary because it can be replaced by shared stores as shown above. - -Combine multiple _stores_ (gajos) into a new one: - -```ts -import { pinia } from 'pinia' -import { useUserStore } from './user' -import { useCartStore, emptyCart } from './cart' - -export const useCartUserStore = pinia( - { - user: useUserStore, - cart: useCartStore, - }, - { - getters: { - combinedGetter () { - return `Hi ${this.user.name}, you have ${this.cart.list.length} items in your cart. It costs ${this.cart.price}.`, - } - }, - actions: { - async orderCart() { - try { - await apiOrderCart(this.user.token, this.cart.items) - this.cart.emptyCart() - } catch (err) { - displayError(err) - } - }, - }, - } -) -``` - ## Related ## License diff --git a/__tests__/actions.spec.ts b/__tests__/actions.spec.ts index fb7aee81..a97a7227 100644 --- a/__tests__/actions.spec.ts +++ b/__tests__/actions.spec.ts @@ -1,10 +1,10 @@ -import { createStore, setActiveReq } from '../src' +import { defineStore, setActiveReq } from '../src' describe('Actions', () => { const useStore = () => { // create a new store setActiveReq({}) - return createStore({ + return defineStore({ id: 'main', state: () => ({ a: true, @@ -41,12 +41,12 @@ describe('Actions', () => { })() } - const useB = createStore({ + const useB = defineStore({ id: 'B', state: () => ({ b: 'b' }), }) - const useA = createStore({ + const useA = defineStore({ id: 'A', state: () => ({ a: 'a' }), actions: { diff --git a/__tests__/getters.spec.ts b/__tests__/getters.spec.ts index 4b337a88..29aac184 100644 --- a/__tests__/getters.spec.ts +++ b/__tests__/getters.spec.ts @@ -1,10 +1,10 @@ -import { createStore, setActiveReq } from '../src' +import { defineStore, setActiveReq } from '../src' describe('Getters', () => { const useStore = () => { // create a new store setActiveReq({}) - return createStore({ + return defineStore({ id: 'main', state: () => ({ name: 'Eduardo', @@ -23,12 +23,12 @@ describe('Getters', () => { })() } - const useB = createStore({ + const useB = defineStore({ id: 'B', state: () => ({ b: 'b' }), }) - const useA = createStore({ + const useA = defineStore({ id: 'A', state: () => ({ a: 'a' }), getters: { diff --git a/__tests__/pinia/stores/cart.ts b/__tests__/pinia/stores/cart.ts index 241991b7..f7bd76c1 100644 --- a/__tests__/pinia/stores/cart.ts +++ b/__tests__/pinia/stores/cart.ts @@ -1,7 +1,7 @@ -import { createStore } from '../../../src' +import { defineStore } from '../../../src' import { useUserStore } from './user' -export const useCartStore = createStore({ +export const useCartStore = defineStore({ id: 'cart', state: () => ({ rawItems: [] as string[], @@ -21,6 +21,29 @@ export const useCartStore = createStore({ }, [] as { name: string; amount: number }[]) }, }, + actions: { + addItem(name: string) { + this.rawItems.push(name) + }, + + removeItem(name: string) { + const i = this.rawItems.indexOf(name) + if (i > -1) this.rawItems.splice(i, 1) + }, + + // TODO: use multiple stores + // https://github.com/vuejs/vue-next-internal-discussions/issues/22 + async purchaseItems() { + const user = useUserStore() + if (!user.name) return + + // console.log('Purchasing', this.items) + const n = this.items.length + this.state.rawItems = [] + + return { amount: n, user: user.name } + }, + }, }) export type CartStore = ReturnType diff --git a/__tests__/pinia/stores/user.ts b/__tests__/pinia/stores/user.ts index 52f9a1f3..79c07d00 100644 --- a/__tests__/pinia/stores/user.ts +++ b/__tests__/pinia/stores/user.ts @@ -1,11 +1,11 @@ -import { createStore } from 'src/store' +import { defineStore } from '../../../src' function apiLogin(a: string, p: string) { if (a === 'ed' && p === 'ed') return Promise.resolve({ isAdmin: true }) return Promise.reject(new Error('invalid credentials')) } -export const useUserStore = createStore({ +export const useUserStore = defineStore({ id: 'user', state: () => ({ name: 'Eduardo', diff --git a/__tests__/rootState.spec.ts b/__tests__/rootState.spec.ts index d3ee12f7..5c9519ae 100644 --- a/__tests__/rootState.spec.ts +++ b/__tests__/rootState.spec.ts @@ -1,12 +1,12 @@ -import { createStore, getRootState } from '../src' +import { defineStore, getRootState } from '../src' describe('Root State', () => { - const useA = createStore({ + const useA = defineStore({ id: 'a', state: () => ({ a: 'a' }), }) - const useB = createStore({ + const useB = defineStore({ id: 'b', state: () => ({ b: 'b' }), }) diff --git a/__tests__/state.spec.ts b/__tests__/state.spec.ts index 7b6599c9..3a3ba497 100644 --- a/__tests__/state.spec.ts +++ b/__tests__/state.spec.ts @@ -1,11 +1,11 @@ -import { createStore, setActiveReq } from '../src' +import { defineStore, setActiveReq } from '../src' import { computed } from 'vue' describe('State', () => { const useStore = () => { // create a new store setActiveReq({}) - return createStore({ + return defineStore({ id: 'main', state: () => ({ name: 'Eduardo', diff --git a/__tests__/store.patch.spec.ts b/__tests__/store.patch.spec.ts index 90352926..a8927f38 100644 --- a/__tests__/store.patch.spec.ts +++ b/__tests__/store.patch.spec.ts @@ -1,10 +1,10 @@ -import { createStore, setActiveReq } from '../src' +import { defineStore, setActiveReq } from '../src' describe('store.patch', () => { const useStore = () => { // create a new store setActiveReq({}) - return createStore({ + return defineStore({ id: 'main', state: () => ({ a: true, diff --git a/__tests__/store.spec.ts b/__tests__/store.spec.ts index d8cf1afb..9b1b0367 100644 --- a/__tests__/store.spec.ts +++ b/__tests__/store.spec.ts @@ -1,4 +1,4 @@ -import { createStore, setActiveReq, setStateProvider } from '../src' +import { defineStore, setActiveReq, setStateProvider } from '../src' describe('Store', () => { let req: object @@ -6,7 +6,7 @@ describe('Store', () => { // create a new store req = {} setActiveReq(req) - return createStore({ + return defineStore({ id: 'main', state: () => ({ a: true, @@ -47,14 +47,14 @@ describe('Store', () => { }) it('can create an empty state if no state option is provided', () => { - const store = createStore({ id: 'some' })() + const store = defineStore({ id: 'some' })() expect(store.state).toEqual({}) }) it('can hydrate the state', () => { setActiveReq({}) - const useStore = createStore({ + const useStore = defineStore({ id: 'main', state: () => ({ a: true, diff --git a/__tests__/subscriptions.spec.ts b/__tests__/subscriptions.spec.ts index bdf393ef..3b426c69 100644 --- a/__tests__/subscriptions.spec.ts +++ b/__tests__/subscriptions.spec.ts @@ -1,10 +1,10 @@ -import { createStore, setActiveReq } from '../src' +import { defineStore, setActiveReq } from '../src' describe('Subscriptions', () => { const useStore = () => { // create a new store setActiveReq({}) - return createStore({ + return defineStore({ id: 'main', state: () => ({ name: 'Eduardo', diff --git a/package.json b/package.json index 6e66f070..0a3e9952 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@types/jest": "^26.0.14", "@types/node": "^14.11.2", "@vue/devtools-api": "^6.0.0-beta.2", + "@vue/server-renderer": "^3.0.0", "brotli": "^1.3.2", "codecov": "^3.6.1", "conventional-changelog-cli": "^2.1.0", diff --git a/test-dts/store.test-d.ts b/test-dts/store.test-d.ts index 943e8c03..57d1ba5a 100644 --- a/test-dts/store.test-d.ts +++ b/test-dts/store.test-d.ts @@ -1,6 +1,6 @@ -import { createStore, expectType } from './' +import { defineStore, expectType } from './' -const useStore = createStore({ +const useStore = defineStore({ id: 'name', state: () => ({ a: 'on' as 'on' | 'off', nested: { counter: 0 } }), getters: { diff --git a/yarn.lock b/yarn.lock index 2ee6f962..66a4e07c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1002,6 +1002,14 @@ "@vue/compiler-core" "3.0.0" "@vue/shared" "3.0.0" +"@vue/compiler-ssr@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.0.0.tgz#d717abcd23a89fb38d1497228633a21bcf9a0e28" + integrity sha512-Er41F9ZFyKB3YnNbE6JSTIGCVWve3NAQimgDOk4uP42OnckxBYKGBTutDeFNeqUZBMu/9vRHYrxlGFC9Z5jBVQ== + dependencies: + "@vue/compiler-dom" "3.0.0" + "@vue/shared" "3.0.0" + "@vue/devtools-api@^6.0.0-beta.2": version "6.0.0-beta.2" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.2.tgz#833ad3335f97ae9439e26247d97f9baf7b5a6116" @@ -1031,6 +1039,14 @@ "@vue/shared" "3.0.0" csstype "^2.6.8" +"@vue/server-renderer@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.0.0.tgz#d531f62f64b67481aae32034eb003a16941b1a52" + integrity sha512-Ft4kiymPdI8BOYMTVSwB2bUeVH/PsnuEc6s7i9SzQxbz5JZpzzK8ugzICxtSYc8WkNaqi0J1xqdXz2df/YXAqQ== + dependencies: + "@vue/compiler-ssr" "3.0.0" + "@vue/shared" "3.0.0" + "@vue/shared@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.0.tgz#ec089236629ecc0f10346b92f101ff4339169f1a"