vue - Оборачивание компонента с помощью render функции

Это перевод статьи https://medium.com/7gravity/wrapping-vue-components-by-using-render-function-983b396a3bfa

Часто нам необходимо обернуть определённые компоненты в один общий компонент, для достижения каких бы то ни было целей. В текущем проекте, над которым я работаю, Мне нужно иметь возможность оборачивать все компоненты в компонент c-component с конкретными классами атрибутами.

<c-chart><c-chart>
<c-table><c-table>
<c-list><c-list>
<c-element class="c-element" data-id="1">
  <c-chart><c-chart>
</c-element>
<c-element class="c-element" data-id="2">
  <c-table><c-table>
</c-element>
<c-element class="c-element" data-id="3">
  <c-list><c-list>
</c-element>

Для того, чтобы это сделать у нас есть много возможностей, которые нам предоставляет vue фреймворк: от использования композиции компонентов до наследования от базового компонента и обработки оборачивания в темплейтах или render функции.

Так как я предпочитаю примеси (mixins) и слишком ленив, для того, чтобы менять все компоненты, я подумал - "Почему бы не использовать render функцию внутри mixin? Как оказалось, render функция ведёт себя так же как и остальные функции в примеси - сначала вызываются функции примеси, а затем функции самого компонента.

К счастью vue предлагает определённые стратегии объединения функционала (последовательность вызова функций). Стоит попробовать использовать это! Для начала необходимо использовать render функцию из компонента и затем вставлять результат как последний аргумент в render функции примеси. В этом случае примесь имела бы оборачиваемый компонент как содержимое, вставляемое внутрь себя. Собственно здесь описана эта стратегия:

Vue.config.optionMergeStrategies.render = (parentVal, childVal) => {
  if (parentVal) {
    const mergedParentVal = function render(...args) {
      args.push([childVal.apply(this, args)]);
      return parentVal.apply(this, args);
    };

    return mergedParentVal;
  }

  return childVal;
};

Если у нас есть функция render в примеси (parentVal), мы создаём новую render функцию, которая выполняет render функцию компонента(childVal), затем добавляет результат как последний аргумент render функции примеси и выполняет её. Таким образом мы просто должны создать mпримесь для нашей обёртки (автор называет её elementable).

export default {
  render(createElement, children) {
    return createElement(
      "div",
      {
        class: "c-element",
        style: {
          background: "red",
          padding: "1em",
        },
      },
      [
        "This is a parent element",
        ...children,
      ],
    );
  },
};

Далее мы просто подключаем примесь в нужном компоненте

{
  mixins: [elementable],
  render(createElement) {
    return createElement(
      "div",
      {
        style: {
          background: "yellow"
        }
      },
      "This is child element"
    );
  }
}

Теперь мы знакомы с таким необычным путём управления композицией объектов, но этот путь работает и достаточно прост в реализации на больших количествах компонентов. Я не проводил тестов связанных с производительностью данных методов.

Реализация на codepen: