Vue에서 고차 컴포넌트는 실제로 고차 함수, 즉 컴포넌트 함수를 반환하는 함수입니다. 고차 컴포넌트의 특징: 1. 부작용이 없는 순수한 기능이며 원래 컴포넌트를 수정해서는 안 됩니다. 즉, 원래 컴포넌트를 변경할 수 없습니다. 2. 데이터(props)를 신경 쓰지 않습니다. 3. 수신된 props는 패키징된 컴포넌트에 전달되어야 합니다. 즉, 원래 컴포넌트 props는 패키징 컴포넌트에 직접 전달됩니다. 주문 구성 요소는 소품을 완전히 추가, 삭제, 수정할 수 있습니다.
이 튜토리얼의 운영 환경: windows7 시스템, vue3 버전, DELL G3 컴퓨터.
vue 고차 구성 요소에 대한 이해, React 구성 요소는 재사용된 코드로 구현되는 반면 Vue에서는 믹스인으로 구현되며 고차 구성 요소의 일부 개념도 누락되어 있습니다. Vue에서는 상위 그룹을 구현하기가 어렵기 때문에 React만큼 간단하지 않습니다. 실제로 Vue의 믹스인도 믹스인으로 대체됩니다. of Vue
소위 고차 컴포넌트는 실제로는 고수준 컴포넌트, 즉 컴포넌트 함수를 반환하는 함수입니다. 고차 컴포넌트는 다음과 같은 특징을 가지고 있습니다.
高阶组件(HOC)应该是无副作用的纯函数,且不应该修改原组件,即原组件不能有变动 高阶组件(HOC)不关心你传递的数据(props)是什么,并且新生成组件不关心数据来源 高阶组件(HOC)接收到的 props 应该传递给被包装组件即直接将原组件prop传给包装组件 高阶组件完全可以添加、删除、修改 props
고차 컴포넌트의 예
Base.vue
<template> <div> <p @click="Click">props: {{test}}</p> </div> </template> <script> export default { name: 'Base', props: { test: Number }, methods: { Click () { this.$emit('Base-click') } } } </script>
Vue 컴포넌트는 주로 소품, 이벤트, 슬롯 세 가지 포인트를 가지고 있습니다. 기본 구성 요소 구성 요소의 경우 숫자 유형 소품, 즉 테스트를 수신하고 이벤트 이름은 슬롯 없이 베이스 클릭입니다. 우리는 다음과 같이 컴포넌트를 사용할 것입니다:
이제 문장을 인쇄하려면 기본 컴포넌트 컴포넌트가 필요합니다. 하하 동시에 마운트될 때마다 이것은 많은 컴포넌트의 요구 사항일 수 있으므로 mixins 메소드를 사용하면 이렇게 할 수 있습니다. 먼저 mixins를 정의한 다음
export default consoleMixin { mounted () { console.log('haha') } }
그런 다음 consoleMixin을 Base 컴포넌트에 혼합합니다:
<template> <div> <p @click="Click">props: {{test}}</p> </div> </template> <script> export default { name: 'Base', props: { test: Number }, mixins: [ consoleMixin ], methods: { Click () { this.$emit('Base-click') } } } </script>
이렇게 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: '<wrapped v-on="$listeners" v-bind="$attrs"/>', 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
<template> <div> <span @click="handleClick">props: {{test}}</span> <slot name="slot1"/> <!-- 具名插槽 --></slot> <p>===========</p> <slot><slot/> <!-- 默认插槽 --> </div> </template> <script> export default { ... } </script> <template> <div> <Base> <h2 slot="slot1">BaseComponent slot</h2> <p>default slot</p> </Base> <wrapBase> <h2 slot="slot1">EnhancedComponent slot</h2> <p>default slot</p> </wrapBase> </div> </template> <script> import Base from './Base.vue' import hoc from './Console.js' const wrapBase = Console(Base) export default { components: { Base, wrapBase } } </script>
여기서 실행 결과는 다음과 같습니다. 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는 템플릿을 렌더링 기능(렌더링)으로 컴파일합니다. 예를 들어
<div> <p slot="slot1">Base slot</p> </div>
템플릿은
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 외에도 템플릿에는 다음과 같은 구성 요소가 있습니다.
<div> <Base> <p slot="slot1">Base slot</p> <p>default slot</p> </Base> </div>
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 以及传递了多少,传递的是具名的还是不具名的等等。那么子组件如何才能得知这些信息呢?很简单,假如组件的模板如下
<div> <Base> <p slot="slot1">Base slot</p> <p>default slot</p> </Base> </div>
父组件的模板最终会生成父组件对应的 VNode,所以以上模板对应的 VNode 全部由父组件所有,那么在创建子组件实例的时候能否通过获取父组件的 VNode 进而拿到 slot 的内容呢?即通过父组件将下面这段模板对应的 VNode 拿到
<Base> <p slot="slot1">Base slot</p> <p>default slot</p> </Base>
如果能够通过父级拿到这段模板对应的 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 是一个代理对象
위 내용은 Vue 고차 컴포넌트란 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!