Исследуйте компоненты высшего порядка Vue
Компоненты высшего порядка (HOC
)ДаReact
Общий словарь экосистем,React
Основным способом повторного использования кода в Китае является использование компонентов более высокого порядка, и это также является официально рекомендуемой практикой. а такжеVue
Основным способом повторного использования кода является использованиеmixins
, И вVue
Концепция компонентов более высокого порядка редко упоминается вVue
Реализация компонентов более высокого порядка в среде не похожа наReact
так же просто, какReact
а такжеVue
Дизайнерское мышление отличается, но это не значит, чтоVue
Вы не можете использовать компоненты более высокого порядка вVue
Преимущества использования высокоуровневых компонентов в середине относительноmixins
Качественного изменения нет. Эта статья в основном с технической точки зренияVue
Реализация компонентов более высокого порядка, и начнется сReact
а такжеVue
Проанализировано с обеих сторон.
Начните с Реакта
вначалеReact
также использоватьmixins
Для полного повторного использования кода, например, чтобы избежать ненужного повторного рендеринга компонентов, мы можем смешивать компонентыPureRenderMixin
:
const PureRenderMixin = require('react-addons-pure-render-mixin')
const MyComponent = React.createClass({
mixins: [PureRenderMixin]
})
позжеReact
отказался от этого подхода и использовалshallowCompare
:
const shallowCompare = require('react-addons-shallow-compare')
const Button = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
})
Это нужно реализовать в компоненте самостоятельноshouldComponentUpdate
метод, но конкретная работа этого метода определяетсяshallowCompare
Чтобы помочь вам завершить, то есть поверхностное сравнение.
после этогоReact
Во избежание того, чтобы разработчики всегда писали один и тот же фрагмент кода в компоненте, рекомендуется использоватьReact.PureComponent
, вкратцеReact
шаг за шагомmixins
,они думаютmixins
существуетReact
Нехорошая модель в экосистеме (примечание: не сказаноmixins
нет, только дляReact
экосистема), мнение выглядит следующим образом:
1,
mixins
приносит неявные зависимости
2,mixins
а такжеmixins
между,mixins
Легко вызвать конфликт имен с компонентами.
3. Потому чтоmixins
является навязчивым, он изменяет исходный компонент, поэтому изменитеmixins
Эквивалентно модификации оригинальных компонентов по мере роста спросаmixins
станет сложным, ведущим к снежению сложности.
Для получения подробной информации вы можете проверить эту статьюMixins Considered Harmful. ноHOC
Это не серебряная пуля. Это, естественно, приносит свои проблемы. Заинтересованные студенты могут посмотреть это видео:Michael Jackson - Never Write Another HoC, чья точка зрения: использовать общие компоненты для сопряженияrender prop
Может делать все, что может делать HOC.
В этой статье не будет обсуждаться слишком многоmixins
а такжеHOC
Кто хорош, а кто плох, как и сама техника не бывает хорошей или плохой, только годной или нет. ЭтоReact
а такжеVue
Разве не так обстоит дело с этими двумя приятелями 🙂
ok
, вернемся к компонентам более высокого порядка.Так называемые компоненты более высокого порядка на самом деле являются функциями более высокого порядка.React
а такжеVue
Все это доказывает одно:Функция — это компонент. Таким образом, утверждение о том, что компонент является функцией, установлено, тогда компонент более высокого порядка, естественно, является функцией более высокого порядка, то есть функцией, которая возвращает функцию. Мы знаем, что вReact
Написание компонентов более высокого порядка в среде — это написание функций более высокого порядка, что очень просто.Vue
Так же легко реализовать компоненты высокого порядка? фактическиVue
Немного сложно, даже требует от васVue
Достаточно понять, тогда давайтеVue
Компоненты высокого уровня реализованы посередине, и мы проанализируем, почему они одинаковы, далее в статье.函数就是组件
подумал о,Vue
но не нравитсяReact
Так просто реализовать компоненты более высокого порядка.
Именно поэтому нам необходимо осознатьVue
Полностью понять, прежде чем компоненты более высокого порядкаReact
Компоненты высокого порядка в середине, см. СледующееReact
Код:
function WithConsole (WrappedComponent) {
return class extends React.Component {
componentDidMount () {
console.log('with console: componentDidMount')
}
render () {
return <WrappedComponent {...this.props}/>
}
}
}
WithConsole
Это компонент более высокого порядка, который имеет следующие характеристики:
1. Компоненты высшего порядка (
HOC
) должна быть чистой функцией без побочных эффектов и не должна изменять исходный компонент
можно увидетьWithConsole
это чистая функция, которая принимает компонент в качестве параметра и возвращает новый компонент в новом компонентеrender
Функция отображает только обернутый компонент (WrappedComponent
), без интрузивной модификации.
2. Компоненты высшего порядка (
HOC
) не заботится о данных, которые вы передаете (props
) есть и обернут компонентом (WrappedComponent
) не заботится об источнике данных
Это основа для обеспечения идеальной совместной работы компонентов более высокого порядка и упакованных компонентов.
3. Компоненты высшего порядка (
HOC
) полученоprops
должен быть передан упакованному компоненту (WrappedComponent
)
Компоненты более высокого порядка могут быть добавлены, удалены и измененыprops
, но помимоprops
Прозрачная передача, в противном случае в более глубоких вложенных отношениях (这是高阶组件的常见问题
) вызоветprops
блокировать.
ВышеReact
Основные соглашения о компонентах среднего и высокого порядка, в дополнение к другим вопросам, таким как: компоненты высокого порядка (HOC
) не должно быть вrender
функция; компоненты более высокого порядка (HOC
) также необходимо дублировать статические методы в компонентах; компоненты более высокого порядка (HOC
)серединаref
относится к самому внешнему компоненту контейнера, а не к обернутому компоненту (WrappedComponent
) так далее.
Компоненты высшего порядка в Vue
Зная это, мы можем приступить к реализацииVue
Компоненты высокого уровня, чтобы у всех было интуитивное ощущение, я все равно буду использоватьReact
а такжеVue
Сравнительное объяснение. Первый – это базовыйVue
компоненты, мы часто называем их упакованными компонентами (WrappedComponent
), предполагая, что наш компонент называетсяBaseComponent
:
base-component.vue
<template>
<div>
<span @click="handleClick">props: {{test}}</span>
</div>
</template>
<script>
export default {
name: 'BaseComponent',
props: {
test: Number
},
methods: {
handleClick () {
this.$emit('customize-click')
}
}
}
</script>
мы наблюдаемVue
Компоненты в основном соблюдают три пункта:props
,event
так же какslots
. дляBaseComponent
компонент, он получает числовой типprops
которыйtest
, и создайте пользовательское событие с именем:customize-click
,нетslots
. Мы будем использовать компонент следующим образом:
<base-component @customize-click="handleCustClick" :test="100" />
теперь нам нужноbase-component
Каждый раз, когда компонент монтируется, он печатает предложение:I have already mounted
, и это может быть требованием многих компонентов, поэтому следуйтеmixins
образом, мы можем сделать это, сначала определивmixins
:
export default consoleMixin {
mounted () {
console.log('I have already mounted')
}
}
затем вBaseComponent
компонент генерал-лейтенантconsoleMixin
миксин:
export default {
name: 'BaseComponent',
props: {
test: Number
},
mixins: [ consoleMixin ]
methods: {
handleClick () {
this.$emit('customize-click')
}
}
}
использовать вот такBaseComponent
Когда компонент установлен, предложение будет напечатано после каждого завершения монтирования.I have already mounted
, но теперь мы хотим использовать компоненты более высокого порядка для достижения той же функции, вспомните определение компонентов более высокого порядка:Принимает компонент в качестве параметра и возвращает новый компонент, то надо подумать оVue
Что такое средний компонент? У некоторых студентов могут возникнуть вопросы, разве это не функция? правильно,Vue
Нет проблем с тем, что средний компонент является функцией, но это конечный результат.Например, наше определение компонента в однофайловом компоненте на самом деле является обычным объектом опции, как показано ниже:
export default {
name: 'BaseComponent',
props: {...},
mixins: [...]
methods: {...}
}
Разве это не чистый объект? Итак, когда мы импортируем компонент из одного файла:
import BaseComponent from './base-component.vue'
console.log(BaseComponent)
Подумайте об этом, здесьBaseComponent
что это такое? Это функция? Нет, хотя однофайловые компоненты будутvue-loader
Лечение, но результаты лечения, то есть мы тутBaseComponent
Все еще обычный объект JSON, но когда вы регистрируете этот объект как компонент (components
вариант),Vue
Наконец, будет создан конструктор с объектом в качестве параметра, который является конструктором экземпляра производственного компонента, поэтому вVue
Средняя компонента действительно является функцией, но это конечный результат.До этого мы можем сказать, что вVue
Компонент также может быть обычным объектом, как и объект, экспортируемый в однофайловый компонент.
Исходя из этого, мы знаем, что вVue
Компонент может существовать как чистый объект, такVue
Компоненты более высокого порядка могут быть определены следующим образом:Принимает чистый объект и возвращает новый чистый объект, следующий код:
hoc.js
export default function WithConsole (WrappedComponent) {
return {
template: '<wrapped v-on="$listeners" v-bind="$attrs"/>',
components: {
wrapped: WrappedComponent
},
mounted () {
console.log('I have already mounted')
}
}
}
WithConsole
является компонентом более высокого порядка, который принимает компонент в качестве параметра:WrappedComponent
и возвращает новый компонент. В новом определении компонента мы будемWrappedComponent
зарегистрирован какwrapped
компоненты, а вtemplate
Визуализируй и добавьmounted
крючок, печатьI have already mounted
.
Вышеупомянутое делается сmixins
Та же функция, но на этот раз мы используем компоненты более высокого порядка, поэтому она ненавязчива, мы не модифицировали исходные компоненты (WrappedComponent
), исходный компонент визуализируется в новом компоненте без каких-либо изменений в исходном компоненте. И здесь всем следует обратить внимание$listeners
а также$attrs
:
'<wrapped v-on="$listeners" v-bind="$attrs"/>'
Это необходимо сделать, что эквивалентноReact
Средняя передачаprops
:
<WrappedComponent {...this.props}/>
В противном случае при использовании компонентов более высокого порядка обернутый компонент (WrappedComponent
)Не получитьprops
а также事件
.
Это действительно решает проблему идеально? Нет, прежде всегоtemplate
вариант только в полной версииVue
может использоваться в версии выполнения, но не в версии выполнения, поэтому, по крайней мере, мы должны использовать функцию рендеринга (render
) вместо шаблона (template
),следующим образом:
hoc.js
export default function WithConsole (WrappedComponent) {
return {
mounted () {
console.log('I have already mounted')
},
render (h) {
return h(WrappedComponent, {
on: this.$listeners,
attrs: this.$attrs,
})
}
}
}
В приведенном выше коде мы переписываем шаблон в функцию рендеринга, что вроде бы не проблема, но это не так.WrappedComponent
Компоненты еще не полученыprops
, могут спросить некоторые студенты, разве мы уже не вh
Вторым параметром функции будетattrs
Он был доставлен, почему я не могу его получить? Конечно, нет,attrs
относится к тем, которые не объявленыprops
, поэтому в функцию рендеринга нужно еще добавитьprops
параметр:
hoc.js
export default function WithConsole (WrappedComponent) {
return {
mounted () {
console.log('I have already mounted')
},
render (h) {
return h(WrappedComponent, {
on: this.$listeners,
attrs: this.$attrs,
props: this.$props
})
}
}
}
Тогда это возможно? еще нет, потому чтоthis.$props
всегда пустой объект, потому что здесьthis.$props
Относится к полученному компоненту более высокого порядкаprops
, в то время как HOC не объявляет никакихprops
,такthis.$props
Естественно это пустой объект, так что мне делать? Очень простойprops
установить с обернутым компонентомprops
Достаточно того же:
hoc.js
export default function WithConsole (WrappedComponent) {
return {
mounted () {
console.log('I have already mounted')
},
props: WrappedComponent.props,
render (h) {
return h(WrappedComponent, {
on: this.$listeners,
attrs: this.$attrs,
props: this.$props
})
}
}
}
Теперь это немного более полный и удобный компонент высшего порядка. Обратите внимание на слова:稍微
, Нани? Все изменилось на это, не так ли? Конечно, вышеприведенные компоненты более высокого порядка могут делать следующее:
1. Прозрачная передача
props
2. Прозрачная передача не заявлена какprops
свойства
3, пропускное событие
Вам не кажется, что здесь не хватает смысла? Мы уже говорили, что одинVue
Три важных фактора для компонентов:props
,事件
так же какslots
, первые два выполнены, ноslots
Еще нет. мы модифицируемBaseComponent
Компонент добавляет именованный слот и слот по умолчанию, следующим образом:
base-component.vue
<template>
<div>
<span @click="handleClick">props: {{test}}</span>
<slot name="slot1"/> <!-- 具名插槽 -->
<p>===========</p>
<slot/> <!-- 默认插槽 -->
</div>
</template>
<script>
export default {
...
}
</script>
Затем пишем следующий тестовый код:
<template>
<div>
<base-component>
<h2 slot="slot1">BaseComponent slot</h2>
<p>default slot</p>
</base-component>
<enhanced-com>
<h2 slot="slot1">EnhancedComponent slot</h2>
<p>default slot</p>
</enhanced-com>
</div>
</template>
<script>
import BaseComponent from './base-component.vue'
import hoc from './hoc.js'
const EnhancedCom = hoc(BaseComponent)
export default {
components: {
BaseComponent,
EnhancedCom
}
}
</script>
Синяя рамка на картинке вышеBaseComponent
Контент, отображаемый компонентом, является нормальным. Красное поле — это содержимое, отображаемое компонентом более высокого порядка, и можно обнаружить, что как именованный слот, так и слот по умолчанию отсутствуют. Причина очень проста, потому что мы не передаем прозрачно содержимое распределенного слота упакованному компоненту в компонент более высокого порядка (WrappedComponent
), поэтому мы пытаемся изменить компонент более высокого порядка:
hoc.js
function WithConsole (WrappedComponent) {
return {
mounted () {
console.log('I have already mounted')
},
props: WrappedComponent.props,
render (h) {
// 将 this.$slots 格式化为数组,因为 h 函数第三个参数是子节点,是一个数组
const slots = Object.keys(this.$slots)
.reduce((arr, key) => arr.concat(this.$slots[key]), [])
return h(WrappedComponent, {
on: this.$listeners,
attrs: this.$attrs,
props: this.$props
}, slots) // 将 slots 作为 h 函数的第三个参数
}
}
}
Хорошо, все готово, обновите страницу следующим образом:
Нани 😱? Мы обнаружили, что содержимое раздачи действительно отображается, но кажется, что порядок не правильный. . . . . . Синий прямоугольник — это нормально, и есть разделительная линия между именованным слотом и слотом по умолчанию (===========
), а все слоты в красной рамке отображаются на разделительной линии (===========
), похоже, что именованные слоты также обрабатываются как слоты по умолчанию. что это за события такого рода?
Чтобы понять это, я возвращаюсь к тому, о чем я упоминал в начале статьи, а именно о том, что вам нужноVue
Необходимо иметь некоторое представление о принципе реализации, иначе решения нет. Далее мы объясним, как решить эту проблему по принципу срабатывания. Корень этой проблемы в следующем:Vue
Соображения области действия принимаются во внимание при работе с именованными слотами. Неважно, если вы не понимаете, давайте немного проанализируем.
Сначала добавьте подсказку:Vue
поставлю шаблон(template
) компилируется в функцию рендеринга (render
), например следующий шаблон:
<div>
<h2 slot="slot1">BaseComponent slot</h2>
</div>
Будет скомпилировано в следующую функцию рендеринга:
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", [
_c("h2", {
attrs: { slot: "slot1" },
slot: "slot1"
}, [
_vm._v("BaseComponent slot")
])
])
}
Легко просмотреть скомпилированную функцию рендеринга шаблона компонента, просто посетитеthis.$options.render
Вот и все. Вышеуказанные наблюдения мы находим общий функцию рендерингаDOM
через_c
функция для создания соответствующейVNode
из. Теперь модифицируем шаблон, разве что есть обычныеDOM
Кроме того, существуют следующие компоненты:
<div>
<base-component>
<h2 slot="slot1">BaseComponent slot</h2>
<p>default slot</p>
</base-component>
</div>
Затем результирующая функция рендеринга (render
) такова, что:
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
[
_c("base-component", [
_c("h2", { attrs: { slot: "slot1" }, slot: "slot1" }, [
_vm._v("BaseComponent slot")
]),
_vm._v(" "),
_c("p", [_vm._v("default slot")])
])
],
1
)
}
Мы обнаружили, что будь то обычный DOM или компонент, он передается через_c
функция создает соответствующийVNode
из. фактически_c
существуетVue
внутриcreateElement
функция.createElement
Функция автоматически определит, является ли первый параметр обычным тегом DOM, если это не обычный тег DOM, тоcreateElement
будет рассматривать его как компонент и создавать экземпляр компонента,Обратите внимание, что экземпляр компонента создается только в это время.. Но есть проблема в процессе создания экземпляра компонента: компоненту нужно знать прошел ли родительский шаблонslot
И сколько было пройдено, было ли пройдено именно или безымянно и т.д. Так как же подкомпоненты могут узнать эту информацию? Очень просто, если шаблон компонента такой:
<div>
<base-component>
<h2 slot="slot1">BaseComponent slot</h2>
<p>default slot</p>
</base-component>
</div>
Шаблон родительского компонента в конечном итоге сгенерирует соответствующий родительский компонент.VNode
, поэтому приведенный выше шаблон соответствуетVNode
Все они принадлежат родительскому компоненту, поэтому при создании экземпляра дочернего компонента вы можете получитьVNode
получитьslot
содержание? То есть через родительский компонент следующий шаблон соответствуетVNode
получать:
<base-component>
<h2 slot="slot1">BaseComponent slot</h2>
<p>default slot</p>
</base-component>
Если вы можете получить соответствующий шаблон этого шаблона через родительскийVNode
, то дочерний компонент знает, что отображатьslot
Да, на самом делеVue
Это то, что он делает внутри, на самом деле вы можете получить доступ к подкомпонентуthis.$vnode
чтобы получить соответствующий шаблонVNode
:
вthis.$vnode
не написал вVue
официальная документация. Дочерний компонент получает то, что нужно отобразитьslot
После этого он вступил в ключевой шаг, который должен вызвать прозрачную передачу в компонентах высокого уровня.slot
ДатьBaseComponent
Причина, по которой он не может быть отрендерен правильно, смотрите на следующем рисунке:
Это изображение такое же, как и предыдущее изображение, напечатанное в дочернем компоненте.this.$vnode
, на этикеткеcontext
цитируетсяVNode
Экземпляр компонента, в котором он был создан, посколькуthis.$vnode
цитируется вVNode
Объект создается в родительском компоненте, поэтомуthis.$vnode
серединаcontext
Ссылается на родительский экземпляр. Теоретически два отмеченных на рисункеcontext
должно быть равно:
console.log(this.$vnode.context === this.$vnode.componentOptions.children[0].context) // true
а такжеVue
Очень важная вещь была сделана внутри, то есть вышеприведенное выражение должно быть установлено, чтобы правильно обработать названныйslot
, иначе даже еслиslot
Именованные тоже не считаются, а используются как слоты по умолчанию. Вот почему компоненты более высокого порядка не отображаются правильно.slot
причина.
Так почему же приведенное выше выражение не выполняется в компонентах более высокого порядка? Это связано с тем, что из-за введения компонентов более высокого порядка компонент (也就是高阶组件
), что приводит к доступу кthis.$vnode
Его больше нет в исходном родительском компонентеVNode
Фрагменты, но компоненты более высокого порядкаVNode
фрагмент, так что на этот разthis.$vnode.context
относится к компонентам более высокого порядка, но вместо этого мыslot
Проникнуть,slot
серединаVNode
изcontext
Ссылка по-прежнему относится к исходному экземпляру родительского компонента, поэтому следующее выражение становится ложным:
console.log(this.$vnode.context === this.$vnode.componentOptions.children[0].context) // false
Это приводит к тому, что именованный слот используется в качестве слота по умолчанию, который отображается неправильно.
Решение также очень простое, просто нужно вручную установитьslot
серединаVNode
изcontext
Значение может быть экземпляром компонента более высокого порядка. Измените компонент более высокого порядка следующим образом:
hoc.js
function WithConsole (WrappedComponent) {
return {
mounted () {
console.log('I have already mounted')
},
props: WrappedComponent.props,
render (h) {
const slots = Object.keys(this.$slots)
.reduce((arr, key) => arr.concat(this.$slots[key]), [])
// 手动更正 context
.map(vnode => {
vnode.context = this._self
return vnode
})
return h(WrappedComponent, {
on: this.$listeners,
props: this.$props,
attrs: this.$attrs
}, slots)
}
}
}
Теперь все может быть отрендерено нормально, как показано ниже:
Вот ключевые моменты помимо того, что вам нужно знатьVue
иметь дело сslot
В дополнение к пути, вам также нужно знать текущий экземпляр_self
Доступ к свойству осуществляется, когда сам экземпляр, а не напрямуюthis
,потому чтоthis
является прокси-объектом.
Теперь это выглядит, казалось бы, без проблем, но мы забыли одно, то есть,scopedSlots
,ноscopedSlots
а такжеslot
Механизм реализации другой, по сутиscopedSlots
Это метод, который получает данные в качестве параметра и отображает их.VNode
функции, поэтому не существуетcontext
Концепция, поэтому прямой проход к:
hoc.js
function WithConsole (WrappedComponent) {
return {
mounted () {
console.log('I have already mounted')
},
props: WrappedComponent.props,
render (h) {
const slots = Object.keys(this.$slots)
.reduce((arr, key) => arr.concat(this.$slots[key]), [])
.map(vnode => {
vnode.context = this._self
return vnode
})
return h(WrappedComponent, {
on: this.$listeners,
props: this.$props,
// 透传 scopedSlots
scopedSlots: this.$scopedSlots,
attrs: this.$attrs
}, slots)
}
}
}
На данный момент реализованы основные функции, которыми должен обладать компонент более высокого порядка, но это только начало для достижения полного и надежногоVue
Для компонентов более высокого порядка необходимо учитывать множество факторов, таких как:
Для использования в функциональных компонентах
render
Второй параметр функции заменяетthis
.
Выше мы говорили только об объектах, существующих в виде чистых объектов.Vue
Сборка, однако, в дополнение к чистым объектам может также функционировать.
Создайтеrender
Многие шаги функции могут быть инкапсулированы.
Параметры для самостоятельной обработки функциональных компонентов более высокого порядка (而不仅仅是上面例子中的一个简单的生命周期钩子
)
Я думаю, мне нужно поставить две справочные ссылки о компонентах более высокого порядка для ознакомления и обмена:
Discussion: Best way to create a HOC
GitHub.com/История Джека Мела/…
Почему сложно реализовать компоненты высшего порядка в Vue
Я сказал ранее, чтобы проанализировать, почемуVue
Реализация высокоуровневых компонентов более сложна иReact
Полегче. В основном это связано с тем, что идеи дизайна и цели дизайна у них разные.React
Написание компонентов в Китае — это написание функций, а функции имеют функциональные компоненты. а такжеVue
больше похоже на сильно инкапсулированные функции, на более высоком уровнеVue
Он позволяет легко выполнять некоторые вещи, но в отличие от высокой степени инкапсуляции теряет некоторую гибкость: необходимо соблюдать определенные правила, чтобы система работала лучше.
Есть хорошая поговорка:
Будет сложно, трудно не
Сложные или простые понятия относительные, и, наконец, я надеюсь, что каждый сможет играть.Vue
также оценилReact
. Выложите две картинки, с которыми я согласен больше для вашего обсуждения: