>웹 프론트엔드 >프런트엔드 Q&A >Vue 고차 컴포넌트란 무엇입니까?

Vue 고차 컴포넌트란 무엇입니까?

青灯夜游
青灯夜游원래의
2022-12-20 13:24:372104검색

Vue에서 고차 컴포넌트는 실제로 고차 함수, 즉 컴포넌트 함수를 반환하는 함수입니다. 고차 컴포넌트의 특징: 1. 부작용이 없는 순수한 기능이며 원래 컴포넌트를 수정해서는 안 됩니다. 즉, 원래 컴포넌트를 변경할 수 없습니다. 2. 데이터(props)를 신경 쓰지 않습니다. 3. 수신된 props는 패키징된 컴포넌트에 전달되어야 합니다. 즉, 원래 컴포넌트 props는 패키징 컴포넌트에 직접 전달됩니다. 주문 구성 요소는 소품을 완전히 추가, 삭제, 수정할 수 있습니다.

Vue 고차 컴포넌트란 무엇입니까?

이 튜토리얼의 운영 환경: windows7 시스템, vue3 버전, DELL G3 컴퓨터.

고차 구성 요소 소개

vue 고차 구성 요소에 대한 이해, React 구성 요소는 재사용된 코드로 구현되는 반면 Vue에서는 믹스인으로 구현되며 고차 구성 요소의 일부 개념도 누락되어 있습니다. Vue에서는 상위 그룹을 구현하기가 어렵기 때문에 React만큼 간단하지 않습니다. 실제로 Vue의 믹스인도 믹스인으로 대체됩니다. of Vue

소위 고차 컴포넌트는 실제로는 고수준 컴포넌트, 즉 컴포넌트 함수를 반환하는 함수입니다. 고차 컴포넌트는 다음과 같은 특징을 가지고 있습니다.

高阶组件(HOC)应该是无副作用的纯函数,且不应该修改原组件,即原组件不能有变动
高阶组件(HOC)不关心你传递的数据(props)是什么,并且新生成组件不关心数据来源
高阶组件(HOC)接收到的 props 应该传递给被包装组件即直接将原组件prop传给包装组件
高阶组件完全可以添加、删除、修改 props

고차 컴포넌트의 예

Base.vue

d477f9ce7bf77f53fbcf36bec1b69b7a
  dc6dce4a544fdca2df29d5ac0ea9906b
    be3b8d7b68acfe2d6133cd36dc6a63f1props: {{test}}94b3e26ee717c64999d7867364b1b4a3
  16b28748ea4df4d9c2150843fecfba68
21c97d3a051048b8e55e3c8f199a54b2
3f1c4e4b6b16bbbd69b2ee476dc4f83a
export default {
  name: 'Base',
  props: {
    test: Number
  },
  methods: {
    Click () {
      this.$emit('Base-click')
    }
  }
}
2cacc6d41bbb37262a98f745aa00fbf0

Vue 컴포넌트는 주로 소품, 이벤트, 슬롯 세 가지 포인트를 가지고 있습니다. 기본 구성 요소 구성 요소의 경우 숫자 유형 소품, 즉 테스트를 수신하고 이벤트 이름은 슬롯 없이 베이스 클릭입니다. 우리는 다음과 같이 컴포넌트를 사용할 것입니다:

이제 문장을 인쇄하려면 기본 컴포넌트 컴포넌트가 필요합니다. 하하 ​​동시에 마운트될 때마다 이것은 많은 컴포넌트의 요구 사항일 수 있으므로 mixins 메소드를 사용하면 이렇게 할 수 있습니다. 먼저 mixins를 정의한 다음

export default consoleMixin {
  mounted () {
    console.log('haha')
  }
}

그런 다음 consoleMixin을 Base 컴포넌트에 혼합합니다:

d477f9ce7bf77f53fbcf36bec1b69b7a
  dc6dce4a544fdca2df29d5ac0ea9906b
    be3b8d7b68acfe2d6133cd36dc6a63f1props: {{test}}94b3e26ee717c64999d7867364b1b4a3
  16b28748ea4df4d9c2150843fecfba68
21c97d3a051048b8e55e3c8f199a54b2
3f1c4e4b6b16bbbd69b2ee476dc4f83a
export default {
  name: 'Base',
  props: {
    test: Number
  },
  mixins: [ consoleMixin ],
  methods: {
    Click () {
      this.$emit('Base-click')
    }
  }
}
2cacc6d41bbb37262a98f745aa00fbf0

이렇게 Base 컴포넌트를 사용하면 각 마운팅이 완료된 후 하하 메시지가 인쇄되지만 지금은 동일한 기능을 달성하려면 고차 구성 요소를 사용해야 합니다. 고차 구성 요소의 정의를 떠올려 보세요. 구성 요소를 매개 변수로 받고 새 구성 요소를 반환합니다. Vue의 구성 요소? Vue의 구성 요소는 함수이지만 이것이 최종 결과입니다. 예를 들어 단일 파일 구성 요소의 구성 요소 정의는 실제로 다음과 같은 일반 옵션 개체입니다.

export default {
  name: 'Base',
  props: {...},
  mixins: [...]
  methods: {...}
}

이것은 순수 개체가 아닌가요?

import Base from './Base.vue'
console.log(Base)

뭐죠? 여기서 Base는 JSON 개체이고, 이를 구성 요소의 구성 요소에 추가하면 Vu는 결국 이 매개 변수인 옵션을 사용하여 인스턴스의 생성자를 구성합니다. Object가 도입되기 전에는 여전히 옵션일 뿐이므로 Vue의 컴포넌트는 처음에는 객체일 뿐이라는 것을 쉽게 이해할 수 있습니다. 즉, 고차 컴포넌트는 순수 객체를 받아들이고 반환하는 함수입니다. new pure object

export default function Console (BaseComponent) {
  return {
    template: '0040ddd46d317a97de61e65cb3442a89',
    components: {
      wrapped: BaseComponent
    },
    mounted () {
      console.log('haha')
    }
  }
}

여기 콘솔은 매개변수를 받아들이는 상위 컴포넌트입니다. BaseComponent는 전달된 컴포넌트이고, 새 컴포넌트를 반환하고, BaseComponent를 새 컴포넌트의 하위 컴포넌트로 사용하고, 인쇄를 위해 마운트된 후크 기능을 설정합니다. 하하. 우리는 mixins와 동일한 작업을 수행할 수 있습니다. 여기서 $listeners $attrs는 실제로 props와 이벤트를 투명하게 전송하고 있습니다. 이것이 실제로 문제를 완벽하게 해결합니까? 아니요, 우선 템플릿 옵션은 런타임 버전이 아닌 Vue 정식 버전에서만 사용할 수 있으므로 최소한 템플릿(템플릿) 대신 렌더링 기능(render)을 사용해야 합니다

Console .js

export default function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    render (h) {
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
      })
    }
  }
}

템플릿을 렌더링 함수로 다시 작성했는데, 문제가 없는 것 같은데, 사실 위 코드에서는 여전히 BaseComponent 컴포넌트가 props를 받지 못하는 이유가 무엇인가요? h 함수의 두 번째 매개변수에 attrs를 전달하면 왜 받을 수 없나요? 물론 받을 수는 없습니다. Attrs는 props로 선언되지 않은 속성을 참조하므로 렌더링 함수에 props 매개변수를 추가해야 합니다.

export default function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    render (h) {
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      })
    }
  }
}

그러면 여전히 Props는 항상 빈 객체로 작동하지 않습니다. 여기 props는 high-order 컴포넌트의 객체인데, high-order 컴포넌트는 props를 선언하지 않았기 때문에 또 다른 props를 선언해야 합니다

export default function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    props: BaseComponent.props,
    render (h) {
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      })
    }
  }
}

ok 비슷한 high-order 컴포넌트가 완성되었지만 여전히 가능합니다. 완료되었습니다. props의 투명 전송과 이벤트의 투명 전송만 구현했습니다. 슬롯만 남았습니다. Base 구성요소를 수정하여 명명된 슬롯과 기본 슬롯을 추가합니다. Base.vue

d477f9ce7bf77f53fbcf36bec1b69b7a
  dc6dce4a544fdca2df29d5ac0ea9906b
    0817b6bf5b3183ab1a144d078c45fdbcprops: {{test}}54bdf357c58b8a65c66d7c19c8e4d114
    e8dddc28d3bfad1f72ef15bb99cfb297 ce4ce005a24c94d9b4e4a39d8a2161ed7971cf77a46923278913ee247bc958ee
    e388a4556c0f65e1904146cc1a846bee===========94b3e26ee717c64999d7867364b1b4a3
    58cb293b8600657fad49ec2c8d37b47238b537b6886351ac721d89624ba185ca 4842836e9b56128d85f8ca400a7b33ba
  16b28748ea4df4d9c2150843fecfba68
21c97d3a051048b8e55e3c8f199a54b2
 
3f1c4e4b6b16bbbd69b2ee476dc4f83a
export default {
  ...
}
2cacc6d41bbb37262a98f745aa00fbf0

d477f9ce7bf77f53fbcf36bec1b69b7a
  dc6dce4a544fdca2df29d5ac0ea9906b
    88d574120e9e0d5bf9eb3bcd60be7003
      9d8e5d4a2d5c644920def765daa3de8aBaseComponent slot2e9b454fa8428549ca2e64dfac4625cd
      e388a4556c0f65e1904146cc1a846beedefault slot94b3e26ee717c64999d7867364b1b4a3
    686fde5332128d14fa2aa6dd6ebb0c77
    df1b898a74c042562246884f2d946ef4
      9d8e5d4a2d5c644920def765daa3de8aEnhancedComponent slot2e9b454fa8428549ca2e64dfac4625cd
      e388a4556c0f65e1904146cc1a846beedefault slot94b3e26ee717c64999d7867364b1b4a3
    c29ea36a6f271e266acde995154dc3a8
  16b28748ea4df4d9c2150843fecfba68
21c97d3a051048b8e55e3c8f199a54b2
 
3f1c4e4b6b16bbbd69b2ee476dc4f83a
  import Base from './Base.vue'
  import hoc from './Console.js'
 
  const wrapBase = Console(Base)
 
  export default {
    components: {
      Base,
      wrapBase
    }
  }
2cacc6d41bbb37262a98f745aa00fbf0

여기서 실행 결과는 다음과 같습니다. WrapBase의 슬롯이 없어져 상위 컴포넌트를 변경해야 합니다

function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    props: BaseComponent.props,
    render (h) {
 
      // 将 this.$slots 格式化为数组,因为 h 函数第三个参数是子节点,是一个数组
      const slots = Object.keys(this.$slots)
        .reduce((arr, key) => arr.concat(this.$slots[key]), [])
 
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      }, slots) // 将 slots 作为 h 函数的第三个参数
    }
  }
}

이때 슬롯 내용은 실제로 렌더링되는데 순서가 맞지 않아 상위 컴포넌트가 모두 렌더링됩니다. . 실제로 Vue는 명명된 슬롯을 처리할 때 범위 요소를 고려합니다. 먼저 Vue는 템플릿을 렌더링 기능(렌더링)으로 컴파일합니다. 예를 들어

dc6dce4a544fdca2df29d5ac0ea9906b
  74997f6adba13580e4e439db54218f0dBase slot94b3e26ee717c64999d7867364b1b4a3
16b28748ea4df4d9c2150843fecfba68

템플릿은

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c("div", [
    _c("div", {
      attrs: { slot: "slot1" },
      slot: "slot1"
    }, [
      _vm._v("Base slot")
    ])
  ])
}

로 컴파일됩니다. 위의 렌더링 함수를 관찰해 보면 일반 DOM은 _c 함수를 통해 해당 VNode를 생성하는 것을 알 수 있습니다. 이제 템플릿을 수정합니다. 일반 DOM 외에도 템플릿에는 다음과 같은 구성 요소가 있습니다.

dc6dce4a544fdca2df29d5ac0ea9906b
  88d574120e9e0d5bf9eb3bcd60be7003
    74997f6adba13580e4e439db54218f0dBase slot94b3e26ee717c64999d7867364b1b4a3
    e388a4556c0f65e1904146cc1a846beedefault slot94b3e26ee717c64999d7867364b1b4a3
  686fde5332128d14fa2aa6dd6ebb0c77
16b28748ea4df4d9c2150843fecfba68

its 렌더링 기능

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c(
    "div",
    [
      _c("Base", [
        _c("p", { attrs: { slot: "slot1" }, slot: "slot1" }, [
          _vm._v("Base slot")
        ]),
        _vm._v(" "),
        _c("p", [_vm._v("default slot")])
      ])
    ],
  )
}

我们发现无论是普通DOM还是组件,都是通过 _c 函数创建其对应的 VNode 的 其实 _c 在 Vue 内部就是 createElement 函数。createElement 函数会自动检测第一个参数是不是普通DOM标签如果不是普通DOM标签那么 createElement 会将其视为组件,并且创建组件实例,注意组件实例是这个时候才创建的 但是创建组件实例的过程中就面临一个问题:组件需要知道父级模板中是否传递了 slot 以及传递了多少,传递的是具名的还是不具名的等等。那么子组件如何才能得知这些信息呢?很简单,假如组件的模板如下

dc6dce4a544fdca2df29d5ac0ea9906b
  88d574120e9e0d5bf9eb3bcd60be7003
    74997f6adba13580e4e439db54218f0dBase slot94b3e26ee717c64999d7867364b1b4a3
    e388a4556c0f65e1904146cc1a846beedefault slot94b3e26ee717c64999d7867364b1b4a3
  686fde5332128d14fa2aa6dd6ebb0c77
16b28748ea4df4d9c2150843fecfba68

父组件的模板最终会生成父组件对应的 VNode,所以以上模板对应的 VNode 全部由父组件所有,那么在创建子组件实例的时候能否通过获取父组件的 VNode 进而拿到 slot 的内容呢?即通过父组件将下面这段模板对应的 VNode 拿到

88d574120e9e0d5bf9eb3bcd60be7003
    74997f6adba13580e4e439db54218f0dBase slot94b3e26ee717c64999d7867364b1b4a3
    e388a4556c0f65e1904146cc1a846beedefault slot94b3e26ee717c64999d7867364b1b4a3
  686fde5332128d14fa2aa6dd6ebb0c77

如果能够通过父级拿到这段模板对应的 VNode,那么子组件就知道要渲染哪些 slot 了,其实 Vue 内部就是这么干的,实际上你可以通过访问子组件的 this.$vnode 来获取这段模板对应的 VNode

this.$vnode 并没有写进 Vue 的官方文档

子组件拿到了需要渲染的 slot 之后进入到了关键的一步,这一步就是导致高阶组件中透传 slot 给 Base组件 却无法正确渲染的原因 children的VNode中的context引用父组件实例 其本身的context也会引用本身实例 其实是一个东西

console.log(this. vnode.context===this.vnode.componentOptions.children[0].context) //ture

而 Vue 内部做了一件很重要的事儿,即上面那个表达式必须成立,才能够正确处理具名 slot,否则即使 slot 具名也不会被考虑,而是被作为默认插槽。这就是高阶组件中不能正确渲染 slot 的原因

即 高阶组件中 本来时父组件和子组件之间插入了一个组件(高阶组件),而子组件的 this.$vnode其实是高阶组件的实例,但是我们将slot透传给子组件,slot里 VNode 的context实际引用的还是父组件 所以

console.log(this.vnode.context === this.vnode.componentOptions.children[0].context) // false

最终导致具名插槽被作为默认插槽,从而渲染不正确。

决办法也很简单,只需要手动设置一下 slot 中 VNode 的 context 值为高阶组件实例即可

function Console (Base) {
  return {
    mounted () {
      console.log('haha')
    },
    props: Base.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)
    }
  }
}

说明白就是强制把slot的归属权给高阶组件 而不是 父组件 通过当前实例 _self 属性访问当实例本身,而不是直接使用 this,因为 this 是一个代理对象

【相关推荐:vuejs视频教程web前端开发

위 내용은 Vue 고차 컴포넌트란 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.