Использование шаблона HOC в Vue

JavaScript

предисловие

HOCдаReactЧасто используемый шаблон, ноHOCтолько вReactиграть? Первый взглядReactКак вводится официальный документ?HOCиз:

Компоненты высшего порядка (HOC) — это продвинутая техника в React для повторного использования логики компонентов. HOC сам по себе не является частью ReactAPI, это шаблон проектирования, основанный на композиционных особенностях React.

HOCЭто образец, это идея, не толькоReactможно использовать в середине. так комбинируйVueхарактеристики, также могут быть использованы вVueигратьHOC.

HOC

Проблемы, которые должен решить HOC

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

HOCОсновная проблема — повторное использование. существуетVue, этот вид проблемы обычно используетсяMixinфиксированный.MixinЭто способ расширения функции сбора, которая по существу копирует свойства одного объекта в другой объект.

исходныйReactтакже использоватьMixin, но потом узналMixinсуществуетReactне очень хороший режим, он имеет следующие недостатки:

  • mixinЛегко вызвать конфликт имен с компонентами.
  • mixinЭто навязчиво, изменяет исходные компоненты и сильно увеличивает сложность.

такReactмедленно ушелmixin, поэтому рекомендуется использоватьHOC. нетmixinНе здорово, простоmixinНеподходящийReact.

Что такое ХОС

HOCПолное имя:high-order component-- то есть компоненты более высокого порядка. В частности, компонент более высокого порядка — это функция, параметр которой является компонентом, а возвращаемое значение — новым компонентом.

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

приди и посмотриHOCсуществуетReactПрименение:

function withComponent(WrappedComponent) {
    return class extends Component {
        componentDidMount () {
          	console.log('已经挂载完成')
        }
        render() {
         	return <WrappedComponent {...props} />;
        }
    }
}

withComponentЭто компонент более высокого порядка, который имеет следующие характеристики:

  • HOCявляется чистой функцией и не должна изменять исходный компонент
  • HOCне заботиться о прохожденииpropsчто такое иWrappedComponentНе заботиться об источнике данных
  • HOCполученоpropsдолжен пройти черезWrapperComponent

Использование HOC с Vue

как можетVueКак насчет режима, который использует HOC?

мы обычно пишемVueКомпоненты такие:

<template>
  <div>
    <p>{{title}}</p>
    <button @click="changeTitle"></button>
  </div>
</template>

<script>
export default {
  name: 'ChildComponent',
  props: ['title'],
  methods: {
  	changeTitle () {
  		this.$emit('changeTitle');
  	}
  }
}
</script>

а такжеwithComponeФункция функции t состоит в том, чтобы печатать предложение после каждого завершения монтирования: монтирование завершено.
теперь, когдаHOCальтернативаmixin, поэтому мы сначала используемmixinПишу снова:

export default {
	mounted () {
    	console.log('已经挂载完成')
    }
}

затем импортируйте вChildComponentсередина

import withComponent from './withComponent';
export default {
	...
    mixins: ['withComponet'],
}

Для этого компонента мы вызываем его так в родительском компоненте

<child-component :title='title' @changeTitle='changeTitle'></child-component>

<script>
import ChildComponent from './childComponent.vue';
export default {
	...
  	components: {ChildComponent}
}
</script>

Вы заметили, что когда мы импортируемVueКогда вы создаете компонент, вы фактически импортируете объект.

export default {}

Поскольку компонент является функцией, то это фактически результат обработки. такVueКомпоненты более высокого порядка в также могут быть: получить чистый объект, вернуть чистый объект.

Так что измените наHOCшаблон, например:

export default function withComponent (WrapperComponent) {
	return {
        mounted () {
            console.log('已经挂载完成')
        },
        props: WrappedComponent.props,
        render (h) {
            return h(WrapperComponent, {
                on: this.$listeners,
                attrs: this.$attrs,
                props: this.$props
            })
        }
    }
}

Уведомление{on: this.$listeners,attr: this.$attrs, props: this.props}Это предложение является прозрачной передачейpropsпринцип, который эквивалентенReactсередина<WrappedComponent {...props} />;

this.$propsзначит объявленоpropsАтрибуты,this.$attrsзначит не заявленpropsАтрибуты. Это должно быть прозрачно передано вместе, какой из них отсутствует,propsНи то, ни другое не завершено.

Для общности здесь используетсяrenderфункция для построения, это потому, чтоtemplateтолько в полной версииVueможно использовать в.

Вроде бы неплохо, но есть важная проблема, вVueСлоты могут использоваться в компонентах.

Например:

<template>
  <div>
    <p>{{title}}</p>
    <button @click="changeTitle"></button>
    <slot></slot>
  </div>
</template>

в родительском компоненте

<child-component :title='title' @changeTitle='changeTitle'>Hello, HOC</child-component>

Можно использоватьthis.$soltsДоступ к контенту, распространяемому слотом. Каждому именованному слоту соответствует свойproperty,Напримерv-slot:fooСодержимое будет вthis.$slots.fooбыл найден в. а такжеdefault propertyвключает все узлы, не содержащиеся в именованных слотах, илиv-slot:defaultСодержание.

Поэтому при написании компонента с функцией рендеринга доступthis.$slotsсамый полезный.

первыйthis.$slotsПреобразуется в массив, потому что третий параметр функции рендеринга — это дочерний узел, который является массивом

export default function withComponent (WrapperComponent) {
	return {
        mounted () {
			      console.log('已经挂载完成')
        },
        props: WrappedComponent.props,
        render (h) {
            const keys = Object.keys(this.$slots);
            const slotList = keys.reduce((arr, key) => arr.concat(this.$slots[key]), []);
            return h(WrapperComponent, {
                on: this.$listeners,
                attrs: this.$attrs,
                props: this.$props
            }, slotList)
        }
    }
}

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

Немного озадачен, иди и смотриVueКак называется слот в исходном коде.
существуетsrc/core/instance/render.jsнашел в файлеinitRenderфункция при инициализацииrenderвремя функции

const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)

Этот фрагмент кодаVueРазобрать и обработатьslotиз.
Будуvm.$options._parentVnodeназначить какvm.$vnode, то есть,$vnodeявляется родительским компонентомvnode. Если родительский компонент существует, определитеrenderContext = vm.$vnode.context.renderContextЭто экземпляр, который будет отображаться родительским компонентом. тогда поставьrenderContextа также$options._renderChildrenпередается как параметрresolveSlots()в функции.

Давайте взглянемresolveSlots()функция, вsrc/core/instance/render-helper/resolve-slots.jsв файле

export function resolveSlots (
  children: ?Array<VNode>,
  context: ?Component
): { [key: string]: Array<VNode> } {
  if (!children || !children.length) {
    return {}
  }
  const slots = {}
  for (let i = 0, l = children.length; i < l; i++) {
    const child = children[i]
    const data = child.data
    // remove slot attribute if the node is resolved as a Vue slot node
    if (data && data.attrs && data.attrs.slot) {
      delete data.attrs.slot
    }
    // named slots should only be respected if the vnode was rendered in the
    // same context.
    if ((child.context === context || child.fnContext === context) &&
      data && data.slot != null
    ) {
      const name = data.slot
      const slot = (slots[name] || (slots[name] = []))
      if (child.tag === 'template') {
        slot.push.apply(slot, child.children || [])
      } else {
        slot.push(child)
      }
    } else {
      (slots.default || (slots.default = [])).push(child)
    }
  }
  // ignore slots that contains only whitespace
  for (const name in slots) {
    if (slots[name].every(isWhitespace)) {
      delete slots[name]
    }
  }
  return slots
}

Сосредоточьтесь на части внутриifутверждение

// named slots should only be respected if the vnode was rendered in the
// same context.
if ((child.context === context || child.fnContext === context) &&
  data && data.slot != null
) {
  const name = data.slot
  const slot = (slots[name] || (slots[name] = []))
  if (child.tag === 'template') {
    slot.push.apply(slot, child.children || [])
  } else {
    slot.push(child)
  }
} else {
  (slots.default || (slots.default = [])).push(child)
}

только тогда, когдаif ((child.context === context || child.fnContext === context) && data && data.slot != null ) Когда это правда, он будет рассматриваться как именованный слот, в противном случае он будет рассматриваться как слот по умолчанию, независимо от того, именован он или нет.

else {
  (slots.default || (slots.default = [])).push(child)
}

так почемуHOCВверхifусловие не установлено?

Это потому, что из-заHOCВмешательство вставляет компонент между исходным родительским компонентом и дочерним компонентом, то естьHOC, что приводит к доступу в дочернем компонентеthis.$vodeЭто больше не исходный родительский компонентvnode, ноHOCсерединаvnode, так что на этот разthis.$vnode.contextотносится к компонентам более высокого порядка, но вместо этого мыslotпрошел через,slotсерединаVNodeизcontextСсылка по-прежнему является исходным экземпляром родительского компонента, поэтому она не сохраняется.

Таким образом, оба обрабатываются как слоты по умолчанию.

Решение тоже очень простое, просто вручнуюslotсерединаvnodeизcontextуказывает наHOCПросто пример. Обратите внимание на текущий экземпляр_selfсвойства получают доступ к самому текущему экземпляру вместо того, чтобы использовать его напрямуюthis,потому чтоthisЭто прокси-объект.

export default function withComponent (WrapperComponent) {
	return {
        mounted () {
			      console.log('已经挂载完成')
        },
        props: WrappedComponent.props,
        render (h) {
            const keys = Object.keys(this.$slots);
            const slotList = keys.reduce((arr, key) => arr.concat(this.$slots[key]), []).map(vnode => {
                vnode.context = this._self
                return vnode
            });
            return h(WrapperComponent, {
            	on: this.$listeners,
                attrs: this.$attrs,
        		props: this.$props
            }, slotList)
        }
    }
}

а такжеscopeSlotа такжеslotобрабатывается по-разному, поэтомуscopeSlotпройти вместе

export default function withComponent (WrapperComponent) {
	return {
        mounted () {
			      console.log('已经挂载完成')
        },
        props: WrappedComponent.props,
        render (h) {
            const keys = Object.keys(this.$slots);
            const slotList = keys.reduce((arr, key) => arr.concat(this.$slots[key]), []).map(vnode => {
                vnode.context = this._self
                return vnode
            });
            return h(WrapperComponent, {
                on: this.$listeners,
                attrs: this.$attrs,
                props: this.$props,
                scopedSlots: this.$scopedSlots
            }, slotList)
        }
    }
}

хорошо.

конец

Больше статей, пожалуйста, переместитеГитхаб арендодателя, Если вам это нравится, пожалуйста, нажмите звездочку, это также поощрение автора.