Giter Site home page Giter Site logo

vue-store's Introduction

@samatech/vue-store

A small SPA storage system based on @vue/reactivity


Instructions

Install

# With NPM
npm i -S @samatech/vue-store

# With PNPM
pnpm i -S @samatech/vue-store

Usage

A basic module with explicit typing. See here for a slightly more advanced example.

import {
  IGetters,
  IMutations,
  IState,
  useModule,
  IModule,
  LocalStoragePlugin,
} from '@samatech/vue-store'

export type IUserModule = IModule<IUser, IUserGetters, IUserMutations>

interface IUser extends IState {
  name: string
}

interface IUserGetters extends IGetters {
  upperCaseName: () => string
}

interface IUserMutations extends IMutations {
  updateName: (name: string) => void
  logout: () => void
}

const getDefaultUser = (): IUser => ({
  name: '',
})

export const userModule = useModule<IUser, IUserGetters, IUserMutations>({
  name: 'user',
  version: 1,
  stateInit: getDefaultUser,
  getters: (state: IUser) => ({
    upperCaseName: () => state.name.toUpperCase(),
  }),
  mutations: (state: IUser) => ({
    updateName: (name: string) => (state.name = name),
    logout: () => {
      Object.assign(state, getDefaultUser())
    },
  }),
  plugins: [LocalStoragePlugin],
})

Typescript's ReturnType feature can be used to avoid the need for defining explicit interfaces:

import { LocalStoragePlugin, useModule, useRootModule } from '@samatech/vue-store'

interface IUser {
  name: string
}

const getDefaultUser = (): IUser => ({
  name: '',
})

const getters = (state: IUser) => ({
  upperCaseName: () => state.name.toUpperCase(),
})

const mutations = (state: IUser) => ({
  updateName: (name: string) => (state.name = name),
  logout: () => {
    Object.assign(state, getDefaultUser())
  },
})

const userModule = useModule<
  IUser,
  ReturnType<typeof getters>,
  ReturnType<typeof mutations>
>({
  name: 'user-store',
  version: 1,
  stateInit: getDefaultUser,
  getters,
  mutations,
  plugins: [LocalStoragePlugin],
})

export const store = useRootModule({
  name: 'web-store',
  version: 1,
  subModules: {
    user: userModule,
  },
})

Undefined handling

If a state field is set to undefined, it will not appear in the flattened module, or be saved with the LocalStoragePlugin. It is recommended to use null instead, and make use of strict type checking to avoid accidentally setting fields to undefined. It is possible to add undefined support to the LocalStoragePlugin, please file a feature request or submit a PR if you need this functionality.

Plugins

Plugins can help initialize state, and operate on state when it changes. A basic LocalStoragePlugin is provided for persisting a module's state to browser storage.

Writing plugins is straightforward, just provide an object conforming to IPlugin, or a function that accepts a module parameter and returns IPlugin.

interface IPlugin<S extends IState> {
  // Called when the module is initialized
  onStateInit?: (state: S) => S
  // Called any time the module's state changes
  onDataChange?: WatchCallback<UnwrapNestedRefs<S>, UnwrapNestedRefs<S> | undefined>
}

interface MyState {
  dots: string
}

const dummyPlugin: IPlugin<MyState> = {
  onDataChange: (value) => {
    value.dots += '.'
  },
}

Environment

TODO -- details about using alongside other versions of @vue/reactivity

Example

See the example folder.

Development

We use PNPM workspaces for development

# Clone
git clone [email protected]:samatechtw/vue-store
cd vue-store

# Install dependencies
pnpm install

# Run example
pnpm run all:dev

# Build
pnpm run build

License

MIT License © 2022 SamaTech Limited Company

vue-store's People

Contributors

sampullman avatar abemscac avatar

Stargazers

 avatar

Watchers

 avatar  avatar

vue-store's Issues

Decouple local storage to plugin

Currently, the Store always saves to localstorage on change. By default, it should be a memory store, and we should provide a plugin for saving to localstorage. The benefits are:

  • Many apps need both memory storage and persisted storage. This allows using the same Store/Module interface for both cases.
  • Some apps may prefer to persist the Store using a different storage method
  • The plugin architecture may be re-used for other purposes (custom serializability, etc)

The plugin system should have the following properties:

  • Initialization callback for setting up the state. The input is the the initial state (undefined for the first plugin), and the output is an object. The last plugin to run must produce an object that is a valid Store state.
  • Change callback triggered via watch. It should transparently pass the parameters provided by watch to the plugin
  • Plugins run in order, and can transform the result of the previous plugin

Github actions CI

Add some CI checks with Github actions

  • Prettier format
  • ESLint lint
  • Build
  • Test

Improve mutations dev UX

Currently, mutations are written like this:

  mutations: {
    updateName: (state) => (name: string) => {
      state.name = name
    },
  }

and called with module.updateName('new-name')

It would be more convenient if we didn't need to include the state in every mutation definition. A few options:

  1. Make the mutations object a function that takes the state as a parameter:
  mutations = (state) => {
    updateName: (name: string) => {
      state.name = name
    },
  }
  1. Inject the state into the module:
  mutations: {
    updateName: (name: string) => {
      this.state.name = name
    },
  }

Unit tests

Add unit tests with Jest. This should be done once the Store/Module API is in a stable state.

  • Add jest package and config
  • Add tests directory and package.json script
  • Add tests covering basic usage

Remove submodules

The submodule system is fairly complicated and doesn't offer much benefit. Originally, the main purpose was so we can have plugins that operate on groups of modules, but it's not that useful for us.

  • Remove submodules feature
  • Update tests
  • Update example(?)

Replace Store with Module

Hi @sampullman.
I've been thinking about the question you asked a couple months ago, "Why can't we just we a Module to contain everything? Why does it have to be a Store?"
I think libraries like Vuex and Redux do it that way because they are built on top of the Flux architecture, and Flux is a "proven" way to take care of state management. Everything sounds better after prefixed with "proven".

If it is not for Flux, I really can't think of any reason why we can't just replace the Store with Module.
So I implement a version where there's no Store, only Module.
I'll leave descriptions in the PR.
Thanks!

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.