GithubHelp home page GithubHelp logo

taigabrew / pinia Goto Github PK

View Code? Open in Web Editor NEW

This project forked from vuejs/pinia

0.0 0.0 0.0 2.95 MB

🍍Automatically Typed, Modular and lightweight Store for Vue using the composition api with DevTools support

Home Page: https://j4qzw.csb.app/

License: MIT License

JavaScript 18.23% TypeScript 79.26% Shell 2.51%

pinia's Introduction

Pinia Build Status npm package coverage thanks

Pronounced like the fruit in Spanish, Piña

Piña is also an invalid package name... that's why it has to be pinia which sounds very similar

🍍 Automatically Typed, Modular and lightweight (but Experimental) Store for Vue 3.x based on the composition api with devtools support

⚠️⚠️⚠️ This project is experimental, it's an exploration of what a Store could be like using the composition api. It works both for Vue 2.x and Vue 3.x and you are currently on the branch that supports Vue 3.x. Go here for the Vue 2.x compatible version.

What I want is to inspire others to think about ways to improve Vuex and come up with something that works very well with the composition api. Ideally it could also be used without it.

These are the core principles that I try to achieve with this experiment:

  • Autocompletion: even if you write your code in JavaScript!
  • Flat modular structure 🍍 No nesting, only stores, compose them as needed
  • Light layer on top of Vue 💨 keep it very lightweight
  • Only state, getters and actions
  • No more verbose mutations, 👐 patch is the mutation
  • Actions are like methods ⚗️ Group your business logic there
  • Import what you need, let webpack code split 📦 No need for dynamically registered modules
  • SSR support ⚙️
  • DevTools support 💻 Which is crucial to make this enjoyable

Help me keep working on Open Source in a sustainable way 🚀. Help me with as little as $1 a month, sponsor me on Github.

Silver Sponsors

Vue Mastery logo

Bronze Sponsors


FAQ

A few notes about the project and possible questions:

Q: Does this replace Vuex, is it its successor?

A: No, or at least that's not the main intention

Q: What about dynamic modules?

A: Dynamic modules are not type safe, so instead we allow creating different stores that can be imported anywhere

Roadmap / Ideas

  • 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) You can directly call useOtherStore() inside of a getter or action.
  • Getter with params that act like computed properties (@ktsn)

Installation

yarn add pinia@next
# or with npm
npm install pinia@next

Usage

Install the plugin

Create a pinia (the root store) and pass it to app:

import { createPinia } from 'pinia'

app.use(createPinia())

This will also add devtools support. Some features like time traveling and editing are still not supported because vue-devtools doesn't expose the necessary APIs yet. 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:

import { defineStore } from 'pinia'

export const useMainStore = defineStore({
  // name of the store
  // it is used in devtools and allows restoring state
  id: 'main',
  // a function that returns a fresh state
  state: () => ({
    counter: 0,
    name: 'Eduardo',
  }),
  // optional getters
  getters: {
    doubleCount() {
      return this.counter * 2,
    },
    // use getters in other getters
    doubleCountPlusOne() {
      return this.doubleCount * 2
    }
  },
  // optional actions
  actions: {
    reset() {
      // `this` is the store instance
      this.counter = 0
    },
  },
})

defineStore returns a function that has to be called to get access to the store:

import { useMainStore } from '@/stores/main'

export default defineComponent({
  setup() {
    const main = useMainStore()

    return {
      // gives access to the whole store
      main,
      // gives access only to specific state
      state: computed(() => main.counter),
      // gives access to specific getter; like `computed` properties
      doubleCount: computed(() => main.doubleCount),
    }
  },
})

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:

Avoid doing this*:

import { createRouter } from 'vue-router'
const router = createRouter({
  // ...
})

// ❌ Depending on where you do this it will fail
const main = useMainStore()

router.beforeEach((to, from, next) => {
  if (main.isLoggedIn) next()
  else next('/login')
})

Instead, call useMainStore() at the top of setup, like inject and provide in Vue:

export default defineComponent({
  setup() {
    // ✅ This will work
    const main = useMainStore()

    return {}
  },
})

// In a different file...

router.beforeEach((to) => {
  // ✅ This will work (requires an extra param for SSR, see below)
  const main = useMainStore()

  if (to.meta.requiresAuth && !main.isLoggedIn) return '/login'
})

⚠️: Note that if you are developing an SSR application, you will need to do a bit more.

You can access any property defined in state and getters directly on the store, similar to data and computed properties in a Vue component.

export default defineComponent({
  setup() {
    const main = useMainStore()
    const text = main.name // "eduardo"
    const doubleCount = main.doubleCount // 2

    return {
      text, // will always be "eduardo"
      textDynamic: computed(() => main.name), // reactive value
    }
  },
})

The main store in an object wrapped with reactive, meaning there is no need to write .value after getters but, like props in setup, we cannot destructure it:

export default defineComponent({
  setup() {
    // ❌ This won't work because it breaks reactivity
    // it's the same as destructuring from `props`
    const { name, doubleCount } = useMainStore()
    return { name, doubleCount }
  },
})

Actions are invoked like methods:

export default defineComponent({
  setup() {
    const main = useMainStore()
    // call the action as a method of the store
    main.reset()

    return {}
  },
})

Mutating the state

To mutate the state you can either directly change something:

main.counter++

or call the method $patch that allows you apply multiple changes at the same time with a partial state object:

main.$patch({
  counter: -1,
  name: 'Abalam',
})

The main difference here is that $patch allows you to group multiple changes into one single entry in the devtools.

Replacing the state

Simply set it to a new object;

main.state = { counter: 666, name: 'Paimon' }

SSR

Creating stores with 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:

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 to the useStore() function call:

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'
})

To hydrate the initial state, you need to make sure the rootState is included somewhere in the HTML for Pinia to pick it up later on:

import { createPinia, getRootState } from 'pinia'
// retrieve the rootState server side
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)

// after rendering the page, the root state is build and can be read
getRootState() // serialize, escape, and place it somewhere on the page, for example, as a global variable

On client side, you must tell pinia how to read that variable:

import { setStateProvider } from 'pinia'

// if the previous step appended a script to the page that sets a global variable named `__pinia` with the rootState
setStateProvider(() => window.__pinia)

Composing Stores

Composing stores may look hard at first glance but there is only one rule to follow really:

If multiple stores use each other or you need to use multiple stores at the same time, you must create a separate file where you import all of them.

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:

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:

import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useCartStore } from './cart'

export const useSharedStore = defineStore({
  id: 'shared',
  getters: {
    summary() {
      const user = useUserStore()
      const cart = useCartStore()

      return `Hi ${user.name}, you have ${cart.list.length} items in your cart. It costs ${cart.price}.`
    },
  },
})

Shared Actions

When an actions needs to use multiple stores, we do the same, we create a new file with a new store:

import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useCartStore } from './cart'

export const useSharedStore = defineStore({
  id: 'shared',
  state: () => ({}),
  actions: {
    async orderCart() {
      const user = useUserStore()
      const cart = useCartStore()

      try {
        await apiOrderCart(user.token, cart.items)
        cart.emptyCart()
      } catch (err) {
        displayError(err)
      }
    },
  },
})

Related

License

MIT

pinia's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar pierresaid avatar pimlie avatar posva avatar trickstival avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.