- Notifications
You must be signed in to change notification settings - Fork1
Vue.js util for intelligently merging data passed to functional components.
License
alexsasharegan/vue-functional-data-merge
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Vue.js util for intelligently merging data passed to functional components. (1K=> 0.5K gzipped)
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// ESMimport{mergeData}from"vue-functional-data-merge";// Common JSconst{ mergeData}=require("vue-functional-data-merge/dist/lib.common.js");exportdefault{name:"my-functional-component",functional:true,props:["foo","bar","baz"],render(h,{ props, data, children}){constcomponentData={staticClass:"fn-component",// concatenates all static classesclass:{// object|Array|string all get merged and preservedactive: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);},},};returnh("div",mergeData(data,componentData),children);},};
When writing functional Vue components, the render function receives acontext.data object(see vue docs).This object that contains the entire data object passed to the component (theshape of whichcan be found here).In order to write flexible components, the data object used to create thecomponent must be merged with the data received. If not, only the propertiesdefined by the component will be rendered.
Consider this example:
// MyBtn.jsexportdefault{name:"my-btn",props:["variant"],functional:true,render(h,{ props, children}){returnh("button",{staticClass:"btn",class:[`btn-${props.variant}`],attrs:{type:"button"},},children);},};
This exports a functional button component that applies a base.btn class anda.btn-<variant> class based on thevariant prop passed to the component.It's just a simple wrapper around some Bootstrap styling to make repetitiveusage simpler. Usage would look like this:
<template><form><inputtype="text"placeholder="Name"required/><inputtype="email"placeholder="email"required/><my-btnvariant="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 theprimary variant, but we also wanted to change the buttontype frombuttontosubmit, give it anid, and attach a click handler. This won't workbecause we haven't passed the attributes, listeners, etc. to the create elementcall in the component's render function.
To fix this, we might extract out props, merge listeners/attributes, etc. Thisworks well, but gets verbose fast when attempting to support all dom attributes,event listeners, etc. One might think to simply use Object spread orObject.assign to solve this like so:
returnh("button",{ ...context.data, ...componentData},children);
Now when we try to add any dom attributes, Object spread is essentiallyperforming 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'sattrs andon handlers asObject.assign merges these properties. This is where themergeData util canhelp you. It will dig into the nested properties of thecontext.data and applydifferent merge strategies for each data property.mergeData works like anestedObject.assign in that the util has a variadic argument length—youcan pass any number of arguments to it, and they will all be merged from left toright (the right most arguments taking merge priority). You don't have to pass anew target object as the first argument, as the return value will always be afresh object.
You may run into cases where you are using a functional component in anothercomponent with scoped styles. This would look something like this:
<template><buttonclass="my-class"><slot></slot></button></template><stylescoped> .my-class {text-align: center; }</style>
This will generate data attributes on the component elements and the cssselector.
<style> .my-class[data-v-f3f3eg9] {text-align: center; }</style><buttondata-v-f3f3eg9class="my-class">Click me!</button>
When a parent component with scoped styles makes use of a functional component,the data attribute won't be passed down automatically. Instead, you must pullthis attribute out manually and add it to theVNodeData used in a renderfunction'screateElement call. Doing this requires reaching into Vueinternals, which can be risky due to the private nature of the API and itspotential to change. For that reason, this is not supported in this util.
However, this util can make that manual merging easier by conforming to theVNodeData shape required bymergeData and Vue itself. Here is an example ofa helper function to manually extract a parent's style scope id andconditionally apply it in the functional component's render function.
constFunctionalComponent={functional:true,render(createElement,context){let{ parent, data, children}=context;letcomponentData={class:"my-class"};returncreateElement("button",mergeData(data,getScopedStyleData(parent),componentData),children);},};/** *@param {Vue} parent *@returns {VNodeData} */exportfunctiongetScopedStyleData(parent){letdata={attrs:{}};if(parent.$options._scopeId){data.attrs[`data-v-${parent.$options._scopeId}`]="";}returndata;}
About
Vue.js util for intelligently merging data passed to functional components.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors4
Uh oh!
There was an error while loading.Please reload this page.