Package Exports
- vue-functional-data-merge
- vue-functional-data-merge/dist/lib.common.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (vue-functional-data-merge) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
vue-functional-data-merge
Vue.js util for intelligently merging data passed to functional components. (0.8K => 0.4K gzipped)
Getting Started
Load the util from npm:
# NPM:
npm i vue-functional-data-merge
# Yarn:
yarn add vue-functional-data-merge
Now import and use it in your functional component declaration:
// MyFunctionalComponent.js
// ESM
import { mergeData } from "vue-functional-data-merge"
// Common JS
const { mergeData } = require("vue-functional-data-merge/dist/lib.common.js")
export default {
name: "my-functional-component",
functional: true,
props: ["foo", "bar", "baz"],
render(h, { props, data, children }) {
const componentData = {
staticClass: "fn-component", // concatenates all static classes
class: {
// object|Array|string all get merged and preserved
active: props.foo,
"special-class": props.bar,
},
attrs: {
id: "my-functional-component", // now overrides any id placed on the component
},
on: {
// Event handlers are merged to an array of handlers at each event.
// The last data object passed to `mergeData` will have it's event handlers called first.
// Right-most arguments are prepended to event handler array.
click(e) {
alert(props.baz)
},
},
}
return h("div", mergeData(data, componentData), children)
},
}
Why do I need this util?
When writing functional Vue components, the render function receives a context.data
object
(see vue docs). This object that contains the
entire data object passed to the component (the shape of which
can be found here). In order to write
flexible components, the data object used to create the component must be merged with the data received. If not, only
the properties defined by the component will be rendered.
Consider this example:
// MyBtn.js
export default {
name: "my-btn",
props: ["variant"],
functional: true,
render(h, { props, children }) {
return h(
"button",
{
staticClass: "btn",
class: [`btn-${props.variant}`],
attrs: { type: "button" },
},
children
)
},
}
This exports a functional button component that applies a base .btn
class and a .btn-<variant>
class based on the
variant
prop passed to the component. It's just a simple wrapper around some Bootstrap styling to make repetitive
usage simpler. Usage would look like this:
<template>
<form>
<input type="text" placeholder="Name" required>
<input type="email" placeholder="email" required>
<my-btn variant="primary" type="submit" id="form-submit-btn" @click="onClick">Submit</my-btn>
</form>
</template>
We've used our Bootstrap button component in a form and conveniently applied the primary
variant, but we also wanted
to change the button type
from button
to submit
, give it an id
, and attach a click handler. This won't work
because we haven't passed the attributes, listeners, etc. to the create element call in the component's render function.
To fix this, we might extract out props, merge listeners/attributes, etc. This works well, but gets verbose fast when
attempting to support all dom attributes, event listeners, etc. One might think to simply use Object spread or
Object.assign
to solve this like so:
return h("button", { ...context.data, ...componentData }, children)
Now when we try to add any dom attributes, Object spread is essentially performing something like this:
Object.assign(
{},
{
props: { variant: "primary" },
attrs: { id: "form-submit-btn", type: "submit" }
on: { click: onClick }
},
{
staticClass: "btn",
class: [`btn-${props.variant}`],
attrs: { type: "button" },
on: {
click() {
alert("Hello from MyBtn!")
}
}
}
)
The component data will wipe out all the context's attrs
and on
handlers as Object.assign
merges these properties.
This is where the mergeData
util can help you. It will dig into the nested properties of the context.data
and apply
different merge strategies for each data property. mergeData
works like a nested Object.assign
in that the util has
a variadic argument length—you can pass any number of arguments to it, and they will all be merged from left to
right (the right most arguments taking merge priority). You don't have to pass a new target object as the first
argument, as the return value will always be a fresh object.
Additional Info
This util was written with performance in mind. Since functional components are perfect for components that are
stateless and have many nodes rendered, the mergeData
util is expected to be called extensively. As such, minimal
variable allocations are made as well as minimal internal function calls (for loops are preferred over map
, reduce
,
& forEach
to avoid adding stack frames). TypeScript is used with Vue typings to ensure the most accurate merge
strategy for each property of the context.data
object. You can run the benchmark yourself, but simple merges run at
~1,000,000 ops/sec and complex merges at ~400,000 ops/sec.