Vue: Discusión: La mejor manera de crear un HOC

Creado en 25 jul. 2017  ·  40Comentarios  ·  Fuente: vuejs/vue

Versión

2.4.2

Enlace de reproducción

https://jsfiddle.net/o7yvL2jd/

pasos para reproducir

He estado buscando la forma correcta de implementar HoC con vue.js. Pero no pude encontrar ningún ejemplo adecuado.
El siguiente enlace es una implementación conocida de HoC. Pero no funcionó como se esperaba.
https://jsfiddle.net/o7yvL2jd/

¿Cómo puedo implementar HoC con vue.js?
Solo quiero saber cómo implementar HoC a la manera de react.js.

¿Lo que es esperado?

Son HoC que simplemente representan los componentes pasados ​​como parámetros.
Los HoC que contienen espacios y eventos se renderizarán normalmente.

¿Qué está pasando realmente?

Falta el elemento a representar, o el orden de representación difiere del componente base.
Algunas implementaciones de HoC no funcionan con controladores de eventos.

discussion

Comentario más útil

Bien, jugué con una forma de hacer esto más fácil.

Echa un vistazo aquí: https://jsfiddle.net/Linusborg/j3wyz4d6/

No estoy contento con la API, ya que es un boceto de idea muy aproximado, pero puede hacer todo lo que debería poder hacer.

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

createHOC se encargará de:

  • copiando los accesorios del componente
  • intercambiando $createElement con eso del padre para resolver las ranuras adecuadas.
  • agregar un nombre
  • si no se proporciona un segundo argumento (como en el ejemplo anterior), representa el componente y transmite:

    • accesorios

    • atributos

    • oyentes

    • tragamonedas normales

    • ranuras con alcance

Eso por sí solo no es muy útil, por supuesto. Entonces, la diversión ocurre en el segundo argumento, que es un objeto Componente simple.

Si desea escribir la función de renderizado por su cuenta, por supuesto que puede hacerlo. Si solo desea ampliar accesorios, attrs u oyentes, puede usar el ayudante createRenderFn . creará una función de representación como la predeterminada descrita anteriormente, pero fusionará cualquier attrs , props o listeners que le pase con los del padre.

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

Si desea escribir su propia función de representación, puede usar el ayudante normalizeSlots para transformar el objeto de this.$slots en una matriz adecuada para transmitir:

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

Se buscan comentarios :)

Todos 40 comentarios

Hola @eu81273

Gracias por su interés en este proyecto.

Sin embargo, su problema es una pregunta de uso/soporte, y el rastreador de problemas está reservado exclusivamente para informes de errores y solicitudes de funciones (como se describe en nuestra Guía de contribución ).

Le recomendamos que lo pregunte en el foro , Stack Overflow o en nuestro chat de discordia y estaremos encantados de ayudarle.

FWIW, eché un vistazo por interés personal: esto debería funcionar en el 100% de los casos.

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);
 }
});

Edité HOC04 en su ejemplo ya que era el más cercano a la solución:

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

Editar: todavía hay un problema con las máquinas tragamonedas, investigando...

Podría haberlo resuelto: https://jsfiddle.net/BogdanL/ucpz8ph4/. Las tragamonedas ahora están codificadas, pero eso es trivial de resolver.

Parece que la solución sigue el método de @lbogdan, pero createElement debería tener una forma de tomar ranuras, al igual que puede tomar scopedSlots.

Sin embargo, es _todavía_ un gran esfuerzo crear un HoC. Hay mucho que recordar para pasar, mientras que con reaccionar solo renderiza el WrappedComponent con accesorios.

Acabo de pensar en una solución muy simple... avíseme si me estoy perdiendo algo aquí:

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

Según los ejemplos dados por @LinusBorg y @lbogdan , la implementación de HoC más mínima que puede manejar componentes con ranuras es:

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,
        });
     }
});

Como mencionó @blocka , todavía es un gran esfuerzo crear un HoC con vue.js.

@eu81273

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

Esto parece pasar tu prueba, ¿no? Por supuesto, tendría que ajustarlo dependiendo de si WrappedComponent es un constructor o un objeto, pero no es necesario pasar ranuras, eventos o accesorios.

todavía es un gran esfuerzo crear un HoC con vue.js.

Además del problema con las máquinas tragamonedas, esto se debe solo al hecho de que Vue tiene una API más compleja que React, lo que en este escenario es una desventaja. Admiro la API mínima de React en este tipo de casos de uso: Vue se diseñó con objetivos de diseño ligeramente diferentes, por lo que los HOC no son tan fáciles como en React.

Pero debería ser bastante trivial crear una función de ayuda createHOC() que envuelva esta configuración inicial por usted, ¿no es así?

Bueno, realmente depende de cuál sea el objetivo final. Por lo que entiendo, el objetivo de HoC es cambiar (decorar) de alguna manera el componente original (WrappedComponent) para agregar (o inyectar) accesorios, métodos, detectores de eventos, etc. (muy parecido a un mixin, realmente :smile: ). La variante HOC06 solo cambia la definición del componente, no cambia la forma en que se instancia.

@blocka El objetivo de los HOC a menudo es obtener el estado (por ejemplo, de redux/vuex) e inyectarlo en los accesorios del componente envuelto; eso no funcionaría con su enfoque.

@LinusBorg cierto. Sabía que era demasiado bueno para ser verdad y que me estaba olvidando de algo obvio.

Creo que este es un buen ejemplo de implementación de un caso de uso real HoC en Vue: https://github.com/ktsn/vuex-connect.

Vue Hocs sería una gran ventaja (ya que casi siempre se menciona en cualquier debate de vue vs react). ¿Quizás se podría crear un repositorio oficial para desarrollar un paquete vue-hoc-creator? De esa manera, podríamos trabajar en una implementación robusta y compatible.

Por cierto, tengo una mejor manera: use $createElement del componente principal en lugar del propio HOC; esto hace que el niño resuelva las ranuras correctamente:

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

Lindo, pero con más razón para que haya alguna herramienta oficial, así que
no todos sigan reinventando este código.

El domingo 30 de julio de 2017 a las 4:33 p. m., Thorsten Lünborg [email protected]
escribió:

Por cierto, tengo una mejor manera: usa $createElement del componente principal
en lugar del propio HOC, esto hace que el niño resuelva las ranuras correctamente:

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


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-318927628 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AACoury4Fix2jsX_lyTsS6CYOiHJaOuVks5sTOiugaJpZM4Oh0Ij
.

Lo siento por no encontrar una solución oficial todavía. Me alegro de que pienses que es lindo sin embargo.

Mi solución tampoco es perfecta, hay otros problemas que resolver, es decir, las ranuras con ámbito no funcionarán con mi último truco.

editar: oh, no importa, funcionan

Probablemente se hará una solución oficial, al menos eso esperaría, pero debe pensarse y probarse minuciosamente.

Bien, jugué con una forma de hacer esto más fácil.

Echa un vistazo aquí: https://jsfiddle.net/Linusborg/j3wyz4d6/

No estoy contento con la API, ya que es un boceto de idea muy aproximado, pero puede hacer todo lo que debería poder hacer.

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

createHOC se encargará de:

  • copiando los accesorios del componente
  • intercambiando $createElement con eso del padre para resolver las ranuras adecuadas.
  • agregar un nombre
  • si no se proporciona un segundo argumento (como en el ejemplo anterior), representa el componente y transmite:

    • accesorios

    • atributos

    • oyentes

    • tragamonedas normales

    • ranuras con alcance

Eso por sí solo no es muy útil, por supuesto. Entonces, la diversión ocurre en el segundo argumento, que es un objeto Componente simple.

Si desea escribir la función de renderizado por su cuenta, por supuesto que puede hacerlo. Si solo desea ampliar accesorios, attrs u oyentes, puede usar el ayudante createRenderFn . creará una función de representación como la predeterminada descrita anteriormente, pero fusionará cualquier attrs , props o listeners que le pase con los del padre.

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

Si desea escribir su propia función de representación, puede usar el ayudante normalizeSlots para transformar el objeto de this.$slots en una matriz adecuada para transmitir:

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

Se buscan comentarios :)

@LinusBorg ¡Muy bien!

Lo que creo que ayudaría es encontrar casos de uso de HoC de la vida real y resolverlos usando estas primitivas.

Absolutamente.

Menciono este problema (EDIT (mybad): https://github.com/vuejs/vuejs.org/issues/658).
Dado que está utilizando la API $createElement no documentada, valdría la pena documentarla para los desarrolladores de complementos.

Su enlace es incorrecto (a menos que realmente quisiera vincular a un número de 2014)

Pero sí, técnicamente todavía falta la API $ceateElement en la página de la API.

@AlexandreBonaventure Ese problema es de vue 0.x días. :sonrisa:
Además, createElement se documenta aquí: https://vuejs.org/v2/guide/render-function.html#createElement -Arguments .

La función está documentada como un argumento de la función de representación, pero no está disponible a través this.$createElement . Hay un problema abierto para eso en vuejs/vuejs.org/issues/658

@LinusBorg Pero es básicamente la misma función que se envía a la función render() , ¿verdad?

exactamente lo mismo. Simplemente no está documentado claramente que también está disponible fuera de la función de renderizado a través de este método de instancia.

Solo estoy jugando con el ejemplo anterior y hay un par de problemas al usarlo en un escenario más complejo, de ahí la necesidad de un repositorio oficial. Un par de notas:

  • createRenderFn debe verificar si attrs/props/listeners son funciones y evaluarlas, esto le permitiría establecer dinámicamente accesorios, etc. en función de los accesorios existentes.
  • para la compatibilidad, el componente debería ser el segundo parámetro, y si todo el método createHOC se currara, podríamos encadenar fácilmente a varios creadores hoc juntos.
  • porque el hoc se agrega como una mezcla, si intenta encadenar 2 hocs juntos (es decir withDefaultProps(withProps(component, {}), {}) el segundo hoc no tiene acceso al ejemplo de accesorios de los padres

Hola amigos, he estado buscando escribir una implementación de esto.
https://github.com/jackmellis/vue-hoc/tree/develop
Déjame saber lo que piensas y buscaré publicarlo pronto. También estoy pensando en escribir un paquete de estilo recomponer.

Un paquete de estilo de recomposición sería genial. Realmente podría haber usado algo
como withState recientemente.

El sábado 19 de agosto de 2017 a las 5:50 a. m., Jack [email protected] escribió:

Hola amigos, he estado buscando escribir una implementación de esto.
https://github.com/jackmellis/vue-hoc/tree/develop
Déjame saber lo que piensas y buscaré publicarlo pronto. También estoy
pensando en escribir un paquete de estilo recompose.


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-323513287 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AACoumQWQMgpVeIvzJ3Ti8A4kLBD04Hhks5sZq_zgaJpZM4Oh0Ij
.

@jackmellis Genial que tomes la iniciativa en esto :)

Un par de pensamientos sobre sus comentarios que dio anteriormente:

  • Creo que la versión con curry es un gran punto y debería ser la "predeterminada" de alguna manera, ya que así es como generalmente se hacen los HOC, ¿no es así?
  • Buen punto sobre el problema con los mixins. ¿Ya tienes una idea de cómo mitigar esto? No tengo uno en este momento, pero mi intuición es que esto debería ser mitigable en una combinación de las variantes con curry que creó y usando Vue.config.optionsMergeStrategies

También pensé en nombrar. No me gusta createRenderFn , algo como renderComponentWith sería más significativo y tendría más sentido en un escenario en el que lo integramos en otros nodos:

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

  • Al final me he decantado por los dos. Entonces createHOC es el componente primero sin curry, y luego hay una variante con curry createHOCc . El ecosistema React está muy centrado en la funcionalidad, a diferencia de Vue, que actúa más como un marco OOP. Así que creo que es mejor mantener la coherencia con el resto de Vue, pero seguir ofreciendo una alternativa funcional.
  • Acabo de agregar un código para lidiar con esto. En lugar de tener todo el hoc como una mezcla, combino manualmente el hoc y las opciones usando las opcionesMergeStrategies. El único problema con eso es que optionsMergeStrategies espera una máquina virtual como último parámetro, pero estoy fusionando antes de la creación de instancias, por lo que no hay una máquina virtual.
  • Estoy bastante contento con createRenderFn ya que eso es exactamente lo que está haciendo. Pero cuanto más reúna esto, menos creo que la gente lo use directamente. El uso general será algo como:
const hoc = createHOC(
  Component,
  {
    created(){
      /* ... */
    }
  },
  {
    props: (props) => ({
      ...props,
      someProp: 'foo'
    })
  }
);

¿Quizás no entiendo muy bien tu ejemplo?

¿Quizás no entiendo muy bien tu ejemplo?

No, creo que estás en el camino correcto, mis pensamientos iban en una dirección similar cuando pensé un poco más sobre esto después de mi última respuesta.

En mi POC inicial, lo incluí para que las personas pudieran agregar marcas adicionales alrededor del componente renderizado, pero eso significaría que ya no es realmente un HOC, ya que también traería una interfaz de usuario...

Sí, creo que está intentando generar contenido adicional, salió del territorio HOC y también podría crear un SFC para manejarlo.

¡Acabo de publicar vue-hoc en npm!

También he estado trabajando en vue-compose, que es una sesión de documentación rápida, lejos de estar listo también. Aunque es similar a recomponer, Vue maneja muchas cosas inteligentes (como el almacenamiento en caché de cálculos y fomenta el uso de this ), por lo que en realidad no necesita una sintaxis tan compleja (o de estilo funcional).

extends funciona de manera diferente, pero se puede usar para lograr resultados similares, eso es cierto.

Cerraré esto ya que creo que el proyecto @jackmellis proporciona una base sólida. No tenemos la intención de incluir una forma de crear HOC en el núcleo, principalmente porque no vemos un gran beneficio sobre mixins / Extends.

principalmente porque no vemos un gran beneficio sobre mixins / Extends.

@LinusBorg React ya ha abandonado mixin , HOC trae muchos beneficios.

Este artículo ha analizado los beneficios:

  • El contrato entre un componente y sus mixins es implícito.
  • A medida que usa más mixins en un solo componente, comienzan a chocar.
  • Los mixins tienden a agregar más estado a su componente, mientras que usted debe esforzarse por obtener menos.
  • ....

Creo que el equipo de Vue debería considerar apoyar HoC con más cuidado... aunque no es fácil (Parece que Vue no está diseñado de esta manera).

No estoy convencido de que los HoC sean un concepto tan superior. Los conflictos potenciales del "contrato implícito" también pueden ocurrir con los HoC, por ejemplo.

Vea esta charla del mantenedor de React-router al respecto: https://www.youtube.com/watch?v=BcVAq3YFiuc

Dicho esto, tampoco creo que sean malos , son útiles en muchas situaciones. Simplemente no son la bala mágica por la que son elogiados en el mundo de React, aunque tienen sus propias desventajas.

Como se desprende de la discusión anterior, implementar HoC en Vue no es tan trivial como en React porque la API y la funcionalidad de Vue son más amplias y se deben atender más casos extremos.

Seguramente podemos hablar sobre cómo mejorar esto, siempre que no requiera romper nada en Vue: en mi opinión, los HoC no valen un cambio radical.

666

la implementación más mínima de HoC que puede manejar componentes con ranuras es:

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

en comparación con las respuestas anteriores,

  • La configuración de accesorios no es necesaria, lo que podría pasarse de this.$attrs a child
  • No se necesitan ranuras adicionales, esto. $scopedSlots contienen ranuras desde v2.6.0+

Escribí un ejemplo para verificar

¿Fue útil esta página
0 / 5 - 0 calificaciones