I didn’t know that provide
and inject
existed until last week. Here was my situation: I was toying with creating a declarative <canvas>
Vue.js library to render things. I wanted to create something that would work like so:
<v-canvas name="primary">
<v-circle r="5" x="10" y="10" />
<v-rectangle x="50" y="40" width="100" height="100" />
</v-canvas>
The idea was that the v-canvas
element would create a canvas and provide (see what I did there?) a CanvasRenderingContext2D to each of it’s children.
I had trouble for a couple reasons…
- Each of the children of
v-canvas
needed the rendering context, and it was really hard to provide it to them using regular props - I wanted to avoid scoped slots, because I shouldn’t have to have the user care about the rendering context. The fact that those children are children should be what guarantees they have the context.
- What can normally be added manually using React’s
cloneElement
was hard for me to figure out using Vue’s API and renderless functions.
I thought, there must be a better way!
Provide and Inject
And voila! Somewhere I ran into provide
and inject
. You can find the docs here: https://vuejs.org/v2/api/#provide-inject
In my v-canvas
component I did something like this:
<template>
<div>
<canvas ref="canvas"></canvas>
<slot />
</div>
</template>
<script>
export default {
name: "VueCanvas",
data() {
return {
globalCanvas: {
ctx: null
}
};
},
provide() {
return {
globalCanvas: this.globalCanvas
}
},
};
</script>
Suddenly, the context was available to any component that injects globalCanvas
. It’s reactive. It’s easy. And I don’t have to wire up my children, and it doesn’t matter what child you are or how deep that child might be!
So here is an example of a component v-rectangle
that injects this provided context:
const CvsRectangle = {
name: 'CvsRectangle',
props: {
x: Number,
y: Number,
width: Number,
height: Number,
},
inject: ['globalCanvas'],
methods: {
renderToCanvas(ctx) {
ctx.fillRect(this.x, this.y, this.width, this.height);
}
},
render(createElement) {
if (this.globalCanvas.ctx) {
this.renderToCanvas(this.globalCanvas.ctx);
}
}
};
export default CvsRectangle;
Note that this is a special type of “renderless” component. Even if you haven’t encountered them before I think you can get the gist above. Here’s a great article on such things: https://adamwathan.me/renderless-components-in-vuejs/
Caveats
One, there is some weirdness to the fact that the actual prop you provide needs to be nested one layer. If it isn’t, apparently the provided bindings are not reactive? I wonder how this will work in Vue 3.
Two, the Vue docs are just as concerned as the React docs are about React.Context
and warn about using it. They say…
https://vuejs.org/v2/api/#provide-inject
provide
andinject
are primarily provided for advanced plugin / component library use cases. It is NOT recommended to use them in generic application code.
Conclusion
Give it a shot! Let me know what you think. It would be nice to know if there are alternative patterns and solutions out there besides provide and inject