Vue: Обсуждение: лучший способ создать HOC

Созданный на 25 июл. 2017  ·  40Комментарии  ·  Источник: vuejs/vue

Версия

2.4.2

Ссылка на воспроизведение

https://jsfiddle.net/o7yvL2jd/

Действия по воспроизведению

Я искал правильный способ реализации HoC с помощью vue.js. Но не нашел подходящего примера.
Ссылка ниже — известные реализации HoC. Но не сработал ожидаемо.
https://jsfiddle.net/o7yvL2jd/

Как я могу реализовать HoC с помощью vue.js?
Я просто хочу знать, как реализовать HoC в стиле react.js.

Что ожидается?

Это HoC, которые просто отображают компоненты, переданные в качестве параметров.
HoC, содержащие слоты и события, будут отображаться нормально.

Что происходит на самом деле?

Отрисовываемый элемент отсутствует или порядок отображения отличается от базового компонента.
Некоторые реализации HoC не работают с обработчиками событий.

discussion

Самый полезный комментарий

Итак, я поиграл со способом сделать это проще.

Посмотрите здесь: https://jsfiddle.net/Linusborg/j3wyz4d6/

Я не доволен API, так как это очень грубый набросок идеи, но он может делать все, что должен.

import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
  props: ['message']
  template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)

createHOC позаботится о:

  • копирование реквизита из компонента
  • замена $createElement на это от родителя для правильного разрешения слотов.
  • добавить имя
  • если второй аргумент не указан (как в приведенном выше примере), он отображает компонент и передает дальше:

    • реквизит

    • атрибуты

    • слушатели

    • обычные слоты

    • слоты с ограниченной областью действия

Само по себе это, конечно, не очень полезно. Итак, самое интересное происходит во втором аргументе, который является простым объектом Component.

Если вы хотите написать функцию рендеринга самостоятельно, вы, конечно, можете. Если вы просто хотите расширить props, attrs или listeners, вы можете использовать хелпер createRenderFn . он создаст функцию рендеринга, подобную описанной выше по умолчанию, но объединит любые attrs , props или listeners , которые вы передаете ей, с теми, что были получены от родителя.

const HOC = createHOC(Component, {
  mounted() { 
    console.log(`lifecycle hooks work, it's simply component options`) 
  },
  render: createRenderFn(Component, {
    props: {
      message: 'Hello from your HOC!'
    }
  }
})

Если вы хотите написать свою собственную функцию рендеринга, вы можете использовать помощник normalizeSlots для преобразования объекта из this.$slots в правильный массив для передачи:

const HOC = createHOC(Component, {
  render(h) {
    return h(Component, {
      props: { message: 'hi from HOC'}, // nothing from parent will be passed on
    }, normalizeSlots(this.$slots))
  }
})

Комментарии нужны :)

Все 40 Комментарий

Привет @eu81273

Спасибо за ваш интерес к этому проекту.

Однако ваша проблема связана с использованием/поддержкой, а средство отслеживания проблем предназначено исключительно для отчетов об ошибках и запросов функций (как указано в нашем Руководстве по участию ).

Мы рекомендуем вам задать этот вопрос на форуме , в Stack Overflow или в нашем чате в разногласиях , и мы будем рады вам помочь.

FWIW, посмотрел из личного интереса - должно сработать в 100% случаев.

const HOC = WrappedComponent => ({
  props: typeof WrappedComponent === 'function' // accept both a construtor and an options object
    ? WrappedComponent.options.props 
    : WrappedComponent.props,
  render (h) {
    // reduce all slots to a single array again.
    const slots = Object.keys(this.$slots).reduce((arr, key) => arr.concat(this.$slots[key]), []);
    return h(WrappedComponent, {
      attrs: this.$attrs,
      props: this.$props,
      on: this.$listeners,
    }, slots);
 }
});

Я отредактировал HOC04 в вашем примере, так как он был ближе всего к решению:

https://jsfiddle.net/Linusborg/o7yvL2jd/22/

Редактировать: все еще проблема со слотами, расследование ...

Я мог бы решить это: https://jsfiddle.net/BogdanL/ucpz8ph4/. Слоты сейчас просто жестко запрограммированы, но решить их несложно.

Кажется, решение находится в соответствии с методом @lbogdan, но createElement должен иметь способ использования слотов, точно так же, как он может использовать scopedSlots.

Тем не менее, создание HoC требует _по-прежнему_ больших усилий. Есть много вещей, о которых нужно помнить, в то время как с реакцией вы просто визуализируете WrappedComponent с реквизитами.

Я только что подумал об очень простом решении... дайте мне знать, если я что-то здесь упустил:

const HOC06 = WrappedComponent => Vue.extend({
   mounted () {
      console.log('mounted 6')
   },
   ...WrappedComponent
})

Основываясь на примерах, приведенных @LinusBorg и @lbogdan , самая минимальная реализация HoC, которая может обрабатывать компоненты со слотами:

const HoC = WrappedComponent => ({
    props: typeof WrappedComponent === 'function' 
        ? WrappedComponent.options.props 
        : WrappedComponent.props,
    render (h) {
        const slots = this.$slots;
        const scopedSlots = {};
        Object.keys(slots).map(key => (scopedSlots[key] = () => slots[key]));

        return h(WrappedComponent, {
            attrs: this.$attrs,
            props: this.$props,
            on: this.$listeners,
            scopedSlots,
        });
     }
});

Как уже упоминал @blocka , создание HoC с vue.js по-прежнему требует больших усилий.

@eu81273

const HOC06 = WrappedComponent => Vue.extend({
   mounted () {
      console.log('mounted 6')
   },
   ...WrappedComponent
})

Кажется, это проходит твой тест, нет? Конечно, вам придется настроить его в зависимости от того, является ли WrappedComponent конструктором или объектом, но нет необходимости передавать слоты, события или реквизиты.

все еще требуется много усилий для создания HoC с vue.js.

Помимо проблемы со слотами, это просто связано с тем, что Vue имеет более сложный API, чем React, что в данном сценарии является недостатком. Я восхищаюсь минимальным API Reacts в таких случаях использования — Vue просто был разработан с немного другими целями дизайна, поэтому HOC не так легко, как в React.

Но создать вспомогательную функцию createHOC() , которая обертывает для вас эту начальную настройку, должно быть довольно тривиально, не так ли?

Ну, это действительно зависит от конечной цели. Насколько я понимаю, цель HoC состоит в том, чтобы каким-то образом изменить (украсить) исходный компонент (WrappedComponent), чтобы добавить (или внедрить) реквизиты, методы, прослушиватели событий и т. д. (на самом деле очень похоже на миксин :smile: ). Вариант HOC06 изменяет только определение компонента, но не способ его создания.

@blocka Цель HOC часто состоит в том, чтобы получить состояние (например, из redux / vuex) и внедрить его в свойства обернутого компонента - это не сработает с вашим подходом.

@LinusBorg верно. Я знал, что это слишком хорошо, чтобы быть правдой, и что я забыл кое-что очевидное.

Я думаю, что это хороший пример реализации реального варианта использования HoC в Vue: https://github.com/ktsn/vuex-connect.

Vue Hocs был бы отличным плюсом (поскольку он почти всегда упоминается в любых дебатах vue и react). Возможно, можно создать официальное репо для разработки пакета vue-hoc-creator? Таким образом, мы могли бы работать над надежной, поддерживаемой реализацией

Кстати, есть лучший способ: используйте $createElement из родительского компонента вместо собственного HOC - это заставит дочерний элемент правильно разрешать слоты:

https://jsfiddle.net/o7yvL2jd/23/

Симпатично, но тем больше причин для того, чтобы был какой-то официальный инструмент, поэтому мы
не все продолжают изобретать этот код.

Вс, 30 июля 2017 г., 16:33, Торстен Люнборг, уведомления на адрес github.com
написал:

Кстати, есть способ получше: использовать $createElement из родительского компонента
вместо собственного HOC - это заставит ребенка правильно разрешать слоты:

https://jsfiddle.net/o7yvL2jd/23/


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-318927628 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AACoury4Fix2jsX_lyTsS6CYOiHJaOuVks5sTOiugaJpZM4Oh0Ij
.

Прошу прощения, что пока не придумал официального решения. Рад, что ты считаешь это милым.

Мое решение тоже не идеально, есть и другие проблемы, которые нужно решить - например, слоты с ограниченной областью действия не будут работать с моим последним трюком.

редактировать: о, неважно, они работают

Официальное решение, вероятно, будет сделано, по крайней мере, я этого ожидаю, но оно должно быть тщательно продумано и протестировано.

Итак, я поиграл со способом сделать это проще.

Посмотрите здесь: https://jsfiddle.net/Linusborg/j3wyz4d6/

Я не доволен API, так как это очень грубый набросок идеи, но он может делать все, что должен.

import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
  props: ['message']
  template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)

createHOC позаботится о:

  • копирование реквизита из компонента
  • замена $createElement на это от родителя для правильного разрешения слотов.
  • добавить имя
  • если второй аргумент не указан (как в приведенном выше примере), он отображает компонент и передает дальше:

    • реквизит

    • атрибуты

    • слушатели

    • обычные слоты

    • слоты с ограниченной областью действия

Само по себе это, конечно, не очень полезно. Итак, самое интересное происходит во втором аргументе, который является простым объектом Component.

Если вы хотите написать функцию рендеринга самостоятельно, вы, конечно, можете. Если вы просто хотите расширить props, attrs или listeners, вы можете использовать хелпер createRenderFn . он создаст функцию рендеринга, подобную описанной выше по умолчанию, но объединит любые attrs , props или listeners , которые вы передаете ей, с теми, что были получены от родителя.

const HOC = createHOC(Component, {
  mounted() { 
    console.log(`lifecycle hooks work, it's simply component options`) 
  },
  render: createRenderFn(Component, {
    props: {
      message: 'Hello from your HOC!'
    }
  }
})

Если вы хотите написать свою собственную функцию рендеринга, вы можете использовать помощник normalizeSlots для преобразования объекта из this.$slots в правильный массив для передачи:

const HOC = createHOC(Component, {
  render(h) {
    return h(Component, {
      props: { message: 'hi from HOC'}, // nothing from parent will be passed on
    }, normalizeSlots(this.$slots))
  }
})

Комментарии нужны :)

@LinusBorg Очень приятно!

Что, я думаю, поможет, так это придумать реальные варианты использования HoC и решить их с помощью этих примитивов.

Абсолютно.

Я упоминаю об этой проблеме (EDIT (mybad): https://github.com/vuejs/vuejs.org/issues/658).
Поскольку вы используете недокументированный API $createElement, было бы целесообразно задокументировать его для разработчиков плагинов.

Ваша ссылка неверна (если вы действительно не хотели ссылаться на выпуск 2014 года)

Но да, технически API $ceateElement по-прежнему отсутствует на странице API.

@AlexandreBonaventure Эта проблема связана с vue 0.x days. :улыбка:
Кроме того, createElement задокументировано здесь: https://vuejs.org/v2/guide/render-function.html#createElement -Arguments.

Функция задокументирована как аргумент функции рендеринга, но не то, чтобы она была доступна через this.$createElement . Для этого есть открытая проблема на vuejs/vuejs.org/issues/658.

@LinusBorg Но в основном это та же самая функция, которая отправляется в функцию render() , верно?

точно так же. Просто четко не задокументировано, что он также доступен вне функции рендеринга через этот метод экземпляра.

Я просто играю с приведенным выше примером, и есть пара проблем при его использовании в более сложном сценарии, поэтому мне нужен официальный репозиторий. Пара замечаний:

  • createRenderFn должен проверять, являются ли attrs/props/listeners функциями, и оценивать их, это позволит вам динамически устанавливать реквизиты и т. д. на основе существующих реквизитов.
  • для возможности компоновки компонент должен быть вторым параметром, и если бы весь метод createHOC был каррирован, мы могли бы легко объединить несколько создателей hoc вместе.
  • потому что hoc добавляется как миксин, если вы попытаетесь соединить 2 hoc вместе (т.е. withDefaultProps(withProps(component, {}), {}) , второй hoc не имеет доступа к примеру родительского реквизита

Привет, ребята, я хотел написать реализацию этого.
https://github.com/jackmellis/vue-hoc/дерево/разработка
Дайте мне знать, что вы думаете, и я постараюсь опубликовать это в ближайшее время. Я также думаю о написании пакета в стиле перекомпоновки.

Пакет стиля перекомпоновки был бы замечательным. Действительно мог бы использовать что-то
как с State недавно.

В субботу, 19 августа 2017 г., в 5:50, Джек уведомления на адрес github.com написал:

Привет, ребята, я хотел написать реализацию этого.
https://github.com/jackmellis/vue-hoc/дерево/разработка
Дайте мне знать, что вы думаете, и я постараюсь опубликовать это в ближайшее время. я также
думаю о написании пакета в стиле перекомпоновки.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-323513287 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AACoumQWQMgpVeIvzJ3Ti8A4kLBD04Hhks5sZq_zgaJpZM4Oh0Ij
.

@jackmellis Здорово , что вы взяли на себя инициативу :)

Несколько мыслей по поводу вашего отзыва, который вы дали ранее:

  • Я думаю, что каррированная версия — отличный момент, и она должна быть в некотором роде «по умолчанию», поскольку именно так обычно делаются HOC, не так ли?
  • Хорошее замечание о проблеме с миксинами. У вас уже есть идея, как смягчить это? В данный момент у меня его нет, но я чувствую, что это должно быть смягчено в комбинации созданных вами каррированных вариантов и использования Vue.config.optionsMergeStrategies

Я тоже думала над названием. Мне не нравится createRenderFn , что-то вроде renderComponentWith было бы более значимым и имело бы больше смысла в сценарии, когда мы встраиваем его в некоторые другие узлы:

render(h) {
  return h('DIV', {staticClass: 'some-styling' }, renderComponentWith({ props: {... } }))
}

  • В итоге я выбрал оба. Таким образом, createHOC — это компонентный вариант без каррирования, а затем есть каррированный вариант createHOCc . Экосистема React очень функционально ориентирована, в отличие от Vue, который больше похож на ООП-фреймворк. Поэтому я думаю, что лучше придерживаться остальной части Vue, но при этом предлагать функциональную альтернативу.
  • Я только что добавил код, чтобы справиться с этим. Вместо того, чтобы иметь весь hoc в качестве примеси, я вручную объединяю hoc и параметры вместе, используя optionsMergeStrategies. Единственная проблема заключается в том, что optionsMergeStrategies ожидает виртуальную машину в качестве последнего параметра, но я выполняю слияние перед созданием экземпляра, поэтому виртуальной машины нет.
  • Я вполне доволен createRenderFn , поскольку это именно то, что он делает. Но чем больше я собираю это вместе, тем меньше я думаю, что люди будут использовать его напрямую. Общее использование будет примерно таким:
const hoc = createHOC(
  Component,
  {
    created(){
      /* ... */
    }
  },
  {
    props: (props) => ({
      ...props,
      someProp: 'foo'
    })
  }
);

Может я не совсем понял ваш пример?

Может я не совсем понял ваш пример?

Нет, я думаю, вы на правильном пути, мои мысли шли в том же направлении, когда я еще немного подумал об этом после моего последнего ответа.

В свой первоначальный POC я включил его, чтобы люди могли добавлять дополнительную разметку вокруг отрендеренного компонента, но это означало бы, что это больше не HOC, так как он также добавил бы пользовательский интерфейс...

Да, я думаю, вы пытаетесь отобразить дополнительный контент, вы вышли за пределы территории HOC и могли бы также создать SFC для его обработки.

Я только что опубликовал vue-hoc на npm!

Я также работал над vue-compose , который также является быстрым сеансом документации, который еще не готов. Хотя это похоже на recompose, Vue обрабатывает множество умных вещей (например, кэширование вычислений и поощряет использование this ), поэтому на самом деле ему не нужен такой сложный (или функциональный) синтаксис.

к вашему сведению

https://www.npmjs.com/package/vue-compose

Предварительная прошивка: https://vuejs.org/v2/api/#extends

extends работает иначе, но может использоваться для достижения аналогичных результатов, это правда.

Я закрою это, так как я думаю, что проект @jackmellis обеспечивает прочную основу. Мы не собираемся включать способ создания HOC в ядро, главным образом потому, что не видим большого преимущества перед миксинами/расширениями.

в основном потому, что мы не видим большого преимущества перед миксинами/расширениями.

@LinusBorg React уже отказался от mixin , HOC приносит много пользы.

В этой статье проанализированы преимущества:

  • Контракт между компонентом и его примесями является неявным.
  • Когда вы используете больше примесей в одном компоненте, они начинают конфликтовать.
  • Миксины, как правило, добавляют больше состояния к вашему компоненту, тогда как вы должны стремиться к меньшему.
  • ....

Я думаю, что команда Vue должна рассмотреть вопрос о поддержке HoC более тщательно... хотя это непросто (кажется, что Vue не разработан таким образом).

Я не уверен, что HoC — это такая превосходная концепция. Например, потенциальные конфликты «неявного контракта» могут произойти и с HoC.

Смотрите об этом доклад мейнтейнера React-router: https://www.youtube.com/watch?v=BcVAq3YFiuc

При этом я не думаю, что они плохие , они полезны во многих ситуациях. Они просто не волшебная палочка, как их хвалят, как в мире React, однако у них есть свои недостатки.

Как видно из приведенного выше обсуждения, реализация HoC в Vue не так тривиальна, как в React, потому что API и функциональность Vue шире, и необходимо учитывать больше крайних случаев.

Мы, безусловно, можем говорить о том, как улучшить это, если это не требует поломки чего-либо во Vue — с моей точки зрения, HoC не стоит серьезных изменений.

666

самая минимальная реализация HoC, которая может обрабатывать компоненты со слотами:

function hoc(WrappedComponent) {
  return {
    render(h) {
      return h(WrappedComponent, {
        on: this.$listeners,
        attrs:this.$attrs,
        scopedSlots: this.$scopedSlots,
      });
    },
  };
}

по сравнению с ответами выше,

  • Конфиг реквизита не нужен, его можно передать из this.$attrs в дочерний
  • дополнительные слоты не нужны, это. $scopedSlots содержат слоты начиная с версии 2.6.0+

я написал пример для проверки

Была ли эта страница полезной?
0 / 5 - 0 рейтинги