Skip to content
An abstracted util factory for creating scoped, declarative, and reactive context-components in Vue.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src
.babelrc
.gitignore
.prettierrc
CHANGELOG.md
LICENSE
README.md
package.json
tsconfig.json
yarn.lock

README.md

Vue createContext

npm version

An abstracted util factory for creating scoped, declarative, and reactive context-components in Vue. This API abstraction is greatly inspired by React.createContext using Vue's prvide/inject API under the hood. The usage and its behaviour is exactly the same as you may expect if you already familiar with React. With this library to power up your Vue application, you then now able to use prvide/inject in an explicit, but declarative manner for managing application contexts using component composition. It's just that easy.

Highlights

  • Solve prop-drilling without the full state management solution; good for library development.
  • Declarative, reusable, but explicit, you know where your data came from and how to access it.
  • Easier to debug, just search Context.Provider in the devTool then you know where data get injected.
  • Free from name clash, there are just no chance to mess up provide/inject. Zero overhead!
  • Seamless developing experiences for people came from React.

🧰 Requirements

This library recommend to have Vue 2.6+, so we can leverage the v-slot derivative to write readable code.

🎬 Getting started

To get it started, add this package into your project:

yarn add vue-create-context

📔 API

It is recommended to read React context doc to have a better idea on the API design. The doc here will just focus on the usage and the difference in Vue.

createContext

import { createContext } from 'vue-create-context'

const MyContext = createContext(defaultValue)

Calling createContext with the defaultValue to create a context object. The defaultValue can be either a referential object or primitive, which is ONLY used when the Consumer component can not find its conjugated Provider above the rendering tree.

Context.Provider

<MyContext.Provider value={/* the provided value */}>

The Provider accept a value prop to be any value you want it to pass down the rendering tree. Similar to React, you can use this component multiple times an any level of the tree. Its conjugated Consumer will receive the value from the closest Provider among its ancestors.

Note: If the provided value is reactive, update this value "reactively" will also update all its subscribed descended Consumers. Also, make the value undefined (either explicitly passed in or implicitly set to) WON'T letting Consumer to use the defaultValue, which is the same as in React.

Context.Consumer

<MyContext.Consumer v-slot="/* slot props: the value injected by the closed Provider */">
  <!-- you can access the value within the block -->
</MyContext.Consumer>

The Consumer is a functional component gives the access to the injected value from the closest Provider. Unlike React, where uses the CAAF (children as a function, also known as the "render prop") pattern to access the value, we uses v-slot inside the component block template to access the value (the so called "slot props"). If you uses single file component (SFC) or browsers supports ES6+ object spread operator, you can take the advantage of object destructuring (see more on Vue's official documentation).

It is worth to mention that due to the current implementation of Vue's scoped slot API, the slot props have to be an object, so it is recommended to give the value as an plan old javascript object (POJO). In the case of the provided value to be a primitive, it will be normalized as an object with a value property to get the passed value in v-slot, i.e. { value: /* your provided value */ }.

Note: You might be tempted to mutate the injected value from the consumer block. This is generally a bad idea since it violate the principle of "props down, event up"; therefore, it is recommend to treat the slot props as read only properties. Under the hood, this reactivity behaviour of slot props is just a reflection of the provide/inject API.

Context._context

Internally, calling createContext will generate an unique identifier as a key to access the inject value at runtime, that is why there are no such chance to have clashes. But if you need the injected value to be used in computed property, then you manually have to setup inject property on the component instance (thanks to Vue's core team member @linusborg for pointing out this). For such usage, you can bind the injected context as the following:

{
  inject: {
    [name]: MyContext._context,
  },
  computed: {
    [something]() {
      return this[name]() // please note 'this[name]' is a accessor function with reactivity.
    },
  },
}

Node: the bound property is a function that returns the injected value, which follows the same rule as in Consumer. You can still expect to receive the defaultValue in case have no Provider being traced.

💎 Example

There is a story in the Official Storybook Vue as an example of the createContexts.

For people using the SFC format, here is an conceptual example.

SomeContext.js

import { createContext } from 'vue-create-context';

export const CurrentUserContext = createContext({ firstName: 'Null', lastName: 'Unknown' });

SomeComponent.vue

<template>
  <div>
    <!-- ... -->
    <Consumer v-slot="{ firstName, lastName }">
      <!-- Do something, where firstName, lastName is destructured from the received value -->
    </Consumer>
    <!-- ... -->
  </div>
</template>

<script>
import { Consumer } from './SomeContext';

module.exports = {
  name: 'SomeComponent',
  components: {
    Consumer,
  },
};
</script>

App.vue

<template>
  <div>
    <Provider :value="user">
      <div>
        <SomeComponent />
        <!-- Consumer in the above component get { firstName: 'Jack', lastName: 'Newman' } -->
        <Provider value="{ firstName: 'Amy', lastName: 'Smith' }">
          <SomeComponent />
          <!-- Consumer in the above component get { firstName: 'Amy', lastName: 'Smith' } -->
        </Provider>
      </div>
    </Provider>
    <SomeComponent />
    <!-- Consumer in the above component get { firstName: 'Null', lastName: 'Unknown' } -->
  </div>
</template>

<script>
import { Provider } from './SomeContext';
import SomeComponent from './SomeComponent';

module.exports = {
  name: 'App',
  components: {
    Provider,
    SomeComponent,
  },
  data() {
    return {
      user: {
        firstName: 'Jack',
        lastName: 'Newman',
      },
    };
  },
};
</script>

📖 License

MIT

You can’t perform that action at this time.