All Projects → alexsasharegan → vue-functional-data-merge

alexsasharegan / vue-functional-data-merge

Licence: MIT license
Vue.js util for intelligently merging data passed to functional components.

Programming Languages

typescript
32286 projects
javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to vue-functional-data-merge

PowerUp
⚡ Decompilation Tools and High Productivity Utilities ⚡
Stars: ✭ 1,526 (+1426%)
Mutual labels:  utils
Comet
iOS 项目的 Swift 基础库,提供常用组件、便利方法等。支持 Swift 3.x、Swift 4.x,iOS 8.0+
Stars: ✭ 49 (-51%)
Mutual labels:  utils
utils
General serverless utilities
Stars: ✭ 30 (-70%)
Mutual labels:  utils
XinFramework
Android 快速开发框架 总结以往开发结合三方项目 不断更新
Stars: ✭ 21 (-79%)
Mutual labels:  utils
wx-tool
微信小程序工具类
Stars: ✭ 31 (-69%)
Mutual labels:  utils
deep utils
An open-source toolkit which is full of handy functions, including the most used models and utilities for deep-learning practitioners!
Stars: ✭ 73 (-27%)
Mutual labels:  utils
BaseToolsLibrary
Android通用适配器和常用的工具类
Stars: ✭ 24 (-76%)
Mutual labels:  utils
JavaBaseUtils
Java常用工具类包
Stars: ✭ 22 (-78%)
Mutual labels:  utils
mUtils
JavaScript常用方法
Stars: ✭ 59 (-41%)
Mutual labels:  utils
exfatprogs
exFAT filesystem userspace utilities
Stars: ✭ 26 (-74%)
Mutual labels:  utils
relay-helpers
Helpers to simplify and enhance Relay (https://facebook.github.io/relay/)
Stars: ✭ 19 (-81%)
Mutual labels:  utils
ramdu
Small utils set built around Ramda
Stars: ✭ 18 (-82%)
Mutual labels:  utils
snapdragon-util
Utilities for the snapdragon parser/compiler.
Stars: ✭ 17 (-83%)
Mutual labels:  utils
fileutils
Golang file system utils such as copy files and directories
Stars: ✭ 19 (-81%)
Mutual labels:  utils
skeletoid
Bootstrapping and Utils code for Android applications 🤖made at Mindera 💛
Stars: ✭ 24 (-76%)
Mutual labels:  utils
fat ecto
Query mechanism for Ecto
Stars: ✭ 20 (-80%)
Mutual labels:  utils
utils
Common utils used by PSR-15 middlewares
Stars: ✭ 47 (-53%)
Mutual labels:  utils
anyangdp-frame
基于mybatis,springboot,tk.mybatis等框架二次开发,实现crud,controller,service,dao。
Stars: ✭ 16 (-84%)
Mutual labels:  utils
react-semantic-render
Semantic helper components for rendering content with React.
Stars: ✭ 13 (-87%)
Mutual labels:  utils
perl-scripts
A nice collection of day-to-day Perl scripts.
Stars: ✭ 92 (-8%)
Mutual labels:  utils

vue-functional-data-merge

npm npm downloads GitHub stars GitHub issues Travis Coverage Status GitHub license

Vue.js util for intelligently merging data passed to functional components. (1K => 0.5K 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.

Scoped Styles

You may run into cases where you are using a functional component in another component with scoped styles. This would look something like this:

<template>
  <button class="my-class">
    <slot></slot>
  </button>
</template>
<style scoped>
  .my-class {
    text-align: center;
  }
</style>

This will generate data attributes on the component elements and the css selector.

<style>
  .my-class[data-v-f3f3eg9] {
    text-align: center;
  }
</style>

<button data-v-f3f3eg9 class="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 pull this attribute out manually and add it to the VNodeData used in a render function's createElement call. Doing this requires reaching into Vue internals, which can be risky due to the private nature of the API and its potential to change. For that reason, this is not supported in this util.

However, this util can make that manual merging easier by conforming to the VNodeData shape required by mergeData and Vue itself. Here is an example of a helper function to manually extract a parent's style scope id and conditionally apply it in the functional component's render function.

const FunctionalComponent = {
  functional: true,
  render(createElement, context) {
    let { parent, data, children } = context;
    let componentData = { class: "my-class" };

    return createElement(
      "button",
      mergeData(data, getScopedStyleData(parent), componentData),
      children
    );
  },
};

/**
 * @param {Vue} parent
 * @returns {VNodeData}
 */
export function getScopedStyleData(parent) {
  let data = { attrs: {} };

  if (parent.$options._scopeId) {
    data.attrs[`data-v-${parent.$options._scopeId}`] = "";
  }

  return data;
}
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].