pinia-decorator
[pinia-decorator] is a JavaScript library that simplifies the integration of [Pinia] stores with [Vue class-style components] using the [stage 3 proposal of JavaScript decorators]. In other words, it provides functionality similar to the [vuex-class] and [pinia-class] library.
This library was inspired by [vuex-class] and [pinia-class] but with a few key differences:
- It is implemented in pure JavaScript and does not require TypeScript support.
- It supports [Vue 3].
- It supports the JavaScript-based class-style Vue components using [vue3-class-component], while [pinia-class] primarily targets TypeScript-based class-style Vue components using [vue-facing-decorator].
- It provides the
toStorefunction which converts a class into a Pinia store.
Table of Content
Installation
You can install [pinia-decorator] via npm or yarn:
npm install @qubit-ltd/pinia-decorator
or
yarn add @qubit-ltd/pinia-decorator
Usage
[pinia-decorator] provides the following decorators/functions for your [Vue 3 class-style components]:
@State: Used to inject a read-only state from a [Pinia] store.@WritableState: Used to inject a writable state from a [Pinia] store.@Getter: Used to inject a getter from a [Pinia] store.@Action: Used to inject an action from a [Pinia] store.@Store: Used to inject the entire [Pinia] store.toStore: Used to convert a class into a [Pinia] store.@RawField: Used to define a raw field (non-reactive field) in the state of a [Pinia] store.
The @State Decorator
The @State decorator is used to inject read-only state from a [Pinia] store
into [Vue class-style components]. It allows you to access and use the state
properties of the [Pinia] store within your component.
The syntax of the @State decorator is as follows:
@State(store: object, stateName?: string)
store(required): The injected [Pinia] store object defined using thedefineStore()function from [Pinia].stateName(optional): The name of the injected state of the store. If not provided, the decorator uses the decorated field name as the injected state name.
Example:
@State(useUserStore)
username; // injects the 'username' state from the user store
@State(useUserStore, 'avatar')
userAvatar; // injects the 'avatar' state from the user store as 'userAvatar'
The @WritableState Decorator
The @WritableState decorator is similar to @State, but it allows you to
inject a writable state from a [Pinia] store into a [Vue class-style component].
This means you can both read and modify the state properties.
The syntax of the @WritableState decorator is as follows:
@WritableState(store: object, stateName?: string)
store(required): The injected [Pinia] store object defined using thedefineStore()function from [Pinia].stateName(optional): The name of the injected state of the store. If not provided, the decorator uses the decorated field name as the injected state name.
Example:
@WritableState(useUserStore)
nickname; // injects the 'nickname' state from the user store, allowing both read and write
@WritableState(useUserStore, 'avatar')
userAvatar; // injects the 'avatar' state from the user store as 'userAvatar', allowing both read and write
The @Getter Decorator
The @Getter decorator is used to inject a getter from a [Pinia] store into a
[Vue class-style component]. It allows you to call getter functions from the
store within your component.
The syntax of the @Getter decorator is as follows:
@Getter(store: object, getterName?: string)
store(required): The injected [Pinia] store object defined using thedefineStore()function from [Pinia].getterName(optional): The name of the injected getter of the store. If not provided, the decorator uses the decorated field name as the injected getter name.
Example:
@Getter(useUserStore)
isLoggedIn; // injects the 'isLoggedIn' getter from the user store
@Getter(useUserStore, 'fullName')
userName; // injects the 'fullName' getter from the user store as 'userName'
The @Action Decorator
The @Action decorator is used to inject an action from a [Pinia] store into a
[Vue class-style component]. It allows you to call action functions from the store
within your component.
The syntax of the @Action decorator is as follows:
@Action(store: object, actionName?: string)
store(required): The injected [Pinia] store object defined using thedefineStore()function from [Pinia].actionName(optional): The name of the injected action of the store. If not provided, the decorator uses the decorated field name as the injected action name.
Example:
@Action(useUserStore)
login; // injects the 'login' action from the user store
@Action(useUserStore, 'updateProfile')
updateUserProfile; // injects the 'updateProfile' action from the user store as 'updateUserProfile'
The @Store Decorator
The @Store decorator is used to inject the entire [Pinia] store into a Vue
class-style component. It allows you to access all the state, getters, and
actions of the store.
The syntax of the @Store decorator is as follows:
@Store(store: object)
store(required): The function creating a [Pinia] store object defined with thedefineStore()function from Pinia.
Example:
@Store(useUserStore)
userStore; // injects the entire user store as 'userStore'
The @RawField Decorator
The @RawField decorator is used to mark a field in the state of a [Pinia] store
as a raw field, which means the field will not be made reactive when the class is converted
to a Pinia store using toStore().
The syntax of the @RawField decorator is as follows:
@RawField
fieldName;
Example:
class UserStore {
username = '';
@RawField
logger = createLogger('UserStore'); // this field won't be reactive
login(username, password) {
this.logger.info(`User ${username} attempting to log in`);
// login logic
}
}
export default toStore('user', UserStore);
The toStore Function
The function toStore() is used to convert a class into a [Pinia] store.
The syntax of the toStore() function is as follows:
toStore(storeId: string, Class: function)
storeId(required): The id of the store.Class(required): The (constructor of) class to be converted into a [Pinia] store.
Notes:
- The
toStorefunction supports the inheritance of the store class. - Properties defined in the class become state in the Pinia store.
- Getter methods in the class become getters in the Pinia store.
- Regular methods in the class become actions in the Pinia store.
Example:
import { toStore } from '@qubit-ltd/pinia-decorator';
class UserStore {
username = '';
token = null;
get isLoggedIn() {
return !!this.token;
}
async login(username, password) {
// login logic
this.username = username;
this.token = await api.getToken(username, password);
}
logout() {
this.username = '';
this.token = null;
}
}
export default toStore('user', UserStore);
Example
Here's a simple example of how to use these decorators in your Vue component:
import { Component, toVue } from '@qubit-ltd/vue3-class-component';
import { State, WritableState, Getter, Action, Store } from '@qubit-ltd/pinia-decorator';
import { useMyStore } from './my-pinia-store-module';
@Component
export class MyComponent extends Vue {
@State(useMyStore)
myValue;
@State(useMyStore, 'message')
myMessage;
@Getter(useMyStore)
myGetter
@Getter(useMyStore, 'count')
myCountGetter
@Action(useMyStore)
fetchData;
@Action(useMyStore, 'sayMessage')
mySayMessage;
@Store(useMyStore)
store;
someOtherMessage = 'Hello World!';
callSayMessage() {
console.log('MyComponent.callSayMessage');
this.mySayMessage(this.myMessage);
}
}
export default toVue(MyComponent);
Here is an example to define a Pinia store using the toStore function.
Note that the function also support the inheritance of the store class.
import { toStore, RawField } from '@qubit-ltd/pinia-decorator';
import { Logger } from '@qubit-ltd/logging';
import dayjs from 'dayjs';
class BaseUserStore {
id = '';
username = '';
password = '';
nickname = '';
token = {
value: 'token-value',
expired: 1000,
};
get age() {
throw new Error('This getter will be overridden by subclass');
}
setNickname(nickname) {
this.nickname = nickname;
}
login() {
throw new Error('This function will be overridden by subclass');
}
}
class UserStore extends BaseUserStore { // support class inheritance
avatar = '';
birthday = '';
@RawField
logger = Logger.getLogger('store.user'); // this field is marked as raw field
get age() { // override the super class getter
return dayjs().diff(this.birthday, 'year');
}
setAvatar(avatar) {
this.avatar = avatar;
}
updatePassword(newPassword) {
this.password = newPassword;
return api.updatePassword(this.username, newPassword);
}
login() { // override the super class method
this.logger.info('Logging in as:', this.username);
return api.login(this.username, this.password);
}
}
export default toStore('user', UserStore);
The above example defines a Pinia store named user which is equivalent to the following code:
import { defineStore } from 'pinia';
import { markRaw } from 'vue';
const useUserStore = defineStore('user', {
state: () => ({
id: '',
username: '',
password: '',
nickname: '',
token: {
value: 'token-value',
expired: 1000,
},
avatar: '',
birthday: '',
logger: markRaw(Logger.getLogger('store.user')),
}),
getters: {
age: (state) => dayjs().diff(state.birthday, 'year'),
},
actions: {
setNickname(nickname) {
this.nickname = nickname;
},
setAvatar(avatar) {
this.avatar = avatar;
},
updatePassword(newPassword) {
this.password = newPassword;
return api.updatePassword(this.username, newPassword);
},
login() {
this.logger.info('Logging in as:', this.username);
return api.login(this.username, this.password);
},
},
});
export default useUserStore;
```We can use the `user` store in the Vue components as follows:
```vue
<template>
<div>
<div>Username: {{ username }}</div>
<div>Nickname: {{ nickname }}</div>
<div>Age: {{ age }}</div>
<div>Avatar: <img :src="avatar" /></div>
<button @click="setNickname('new-nickname')">Set Nickname</button>
<button @click="avatar = 'new-avatar.png'">Set Avatar</button>
<button @click="updatePassword('new-password')">Change Password</button>
<button @click="login()">Login</button>
</div>
</template>
<script>
import { Component, toVue } from '@qubit-ltd/vue3-class-component';
import { State, WritableState, Getter, Action } from '@qubit-ltd/pinia-decorator';
import UserStore from 'src/stores/user';
@Component
class UserPage {
@State(UserStore)
username;
@State(UserStore)
nickname;
@WritableState(UserStore)
avatar;
@Getter(UserStore)
age;
@Action(UserStore)
setNickname;
@Action(UserStore)
updatePassword;
@Action(UserStore)
login;
}
export default toVue(UserPage);
</script>
```For more details, check the following demo projects:
- [The demo project using vite](https://github.com/haixing-hu/pinia-decorator-demo-vite)
- [The demo project using webpack](https://github.com/haixing-hu/pinia-decorator-demo-webpack)
## <span id="contributing">Contributing</span>
If you find any issues or have suggestions for improvements, please feel free
to open an issue or submit a pull request to the [GitHub repository].
We welcome your contributions and feedback!
## <span id="license">License</span>
[pinia-decorator] is distributed under the Apache 2.0 license.
See the [LICENSE](LICENSE) file for more details.
[pinia-decorator]: https://npmjs.com/package/@qubit-ltd/pinia-decorator
[Pinia]: https://pinia.vuejs.org/
[Vue]: https://vuejs.org/
[Vue 3]: https://vuejs.org/
[Vue class-style components]: https://npmjs.com/package/@qubit-ltd/vue3-class-component
[Vue class-style component]: https://npmjs.com/package/@qubit-ltd/vue3-class-component
[vue3-class-component]: https://npmjs.com/package/@qubit-ltd/vue3-class-component
[stage 3 proposal of JavaScript decorators]: https://github.com/tc39/proposal-decorators
[vuex-class]: https://github.com/ktsn/vuex-class
[pinia-class]: https://github.com/jquagliatini/pinia-class
[vue-facing-decorator]: https://github.com/facing-dev/vue-facing-decorator
[GitHub repository]: https://github.com/qubit-ltd/pinia-decorator