Ember.js: [QUEST] Componentes Glimmer en Ember

Creado en 28 feb. 2018  ·  23Comentarios  ·  Fuente: emberjs/ember.js

Este problema de búsqueda rastrea la implementación de los componentes de Glimmer en Ember.js.

El plan

Los componentes de Glimmer.js tienen las siguientes características:

  1. Plantillas de "HTML externo" (sin tagName , attributeBindings , etc.)
  2. Los argumentos en las plantillas tienen el prefijo @ , como {{@firstName}}
  3. Los argumentos se establecen en el componente como this.args
  4. Las clases de componentes utilizan la sintaxis de clases de JavaScript
  5. El estado del componente mutable se anota con @tracked propiedades
  6. Los componentes se invocan mediante la sintaxis <AngleBracket />
  7. Los atributos se pueden "distribuir" a través de …attributes

De acuerdo con el espíritu de incrementalismo de Ember, queremos aterrizar esta funcionalidad pieza por pieza a través de un complemento. Esto permite que la comunidad comience a usar funciones y a proporcionar comentarios al principio del proceso.

De hecho, ya hemos comenzado el camino hacia los componentes Glimmer en Ember. Las dos primeras características ya han comenzado a aterrizar:

  1. En los componentes de solo plantilla, las plantillas son "HTML externo" (suponiendo que se haya habilitado la función opcional template-only-glimmer-components ).
  2. Se puede acceder a los argumentos en la plantilla a través del prefijo @ (por ejemplo, {{@firstName}} ).

Este problema propone terminar el proceso de traer componentes de Glimmer a Ember al permitir que los complementos proporcionen implementaciones de componentes alternativos, y luego transformar el paquete @glimmer/component en un complemento de Ember que implemente la API del componente Glimmer.js.

Dividiremos ese trabajo en fases, cada una de las cuales desbloqueará beneficios para las aplicaciones y complementos existentes de Ember. La fase 0 trata de agregar las primitivas necesarias a Ember.js para admitir implementaciones de componentes alternativos. Las fases 1, 2 y 3 tratan sobre la habilitación incremental de la API del componente Glimmer.js.

Mientras profundizamos en las Fases 0 y 1, postergaremos la exploración de los detalles técnicos de las fases posteriores hasta que las primeras estén más cerca de completarse.

Fase 0: Personalización del comportamiento de los componentes

TL; DR: agregue una API CustomComponentManager a Ember.js para permitir que los complementos implementen la API de componentes personalizados.

Actualmente, se supone que todos los componentes de una aplicación Ember son subclases de Ember.Component . Para admitir API de componentes alternativos en Ember, necesitamos alguna forma de decirle a Ember cuándo y cómo debería cambiar el comportamiento de los componentes.

Cuando decimos "comportamiento de componentes personalizados", nos referimos específicamente a:

  1. Cómo se crean las instancias de componentes.
  2. Cómo se destruyen las instancias de componentes.
  3. Cómo se proporcionan los argumentos a la instancia del componente.
  4. Cómo se notifica al componente de estos cambios en el ciclo de vida.

Si bien Glimmer VM introduce el concepto de "administrador de componentes", un objeto que toma estas decisiones, esta API es de muy bajo nivel. Sería prematuro adoptarlos directamente como API pública en Ember porque son difíciles de escribir, fáciles de crear de una manera que rompe otros componentes y aún no son estables.

En su lugar, proponemos una nueva API de Ember llamada CustomComponentManager que implementa un patrón de delegado. El CustomComponentManager proporciona un área de superficie de API más pequeña que el API de Glimmer VM de ComponentManager completo, que permite a los autores de complementos caer en un "pozo del éxito".

Entonces, ¿cómo sabe Ember qué administrador de componentes usar para un componente determinado? La iteración original del RFC de componentes personalizados introdujo el concepto de ComponentDefinition , una estructura de datos que se registró con entusiasmo con Ember y especificó qué administrador de componentes usar.

Uno de los principales beneficios del enfoque ComponentDefinition es que la resolución del administrador de componentes puede ocurrir en el momento de la compilación. Desafortunadamente, eso significa que tenemos que diseñar una API para exactamente cómo se registran, y probablemente significa algún tipo de integración con la canalización de compilación.

En su lugar, proponemos una API para configurar el administrador de un componente en tiempo de ejecución mediante una anotación en la clase del componente. Este paso incremental permite que el trabajo en administradores de componentes personalizados continúe mientras se diseña una solución a más largo plazo.

Descubrimiento del administrador de componentes personalizados

En esta iteración, los componentes deben optar explícitamente por los administradores de componentes alternativos. Lo hacen a través de una función especial componentManager , exportada por Ember, que anota en tiempo de ejecución qué administrador de componentes debe usarse para una clase de componente en particular:

import { componentManager } from '@ember/custom-component-manager';
import EmberObject from '@ember/object';

export default componentManager(EmberObject.extend({
  // ...
}), 'glimmer');

Eventualmente, esto podría convertirse en un decorador de clases:

import { componentManager } from '@ember/custom-component-manager';

export default @componentManager('glimmer') class {
  // ...
}

La primera vez que se invoca este componente, Ember inspecciona la clase para ver si tiene una anotación de administrador de componentes personalizada. Si es así, usa el valor de la cadena para realizar una búsqueda en el contenedor. En el ejemplo anterior, Ember pediría al contenedor el objeto con la clave del contenedor component-manager:glimmer .

Por lo tanto, los complementos pueden usar la semántica de resolución normal para proporcionar administradores de componentes personalizados. Nuestro complemento de componentes Glimmer puede exportar un administrador de componentes desde addon/component-managers/glimmer.js que se descubrirá automáticamente a través de las reglas de resolución normales.

Si bien esta API es detallada y no particularmente ergonómica, las aplicaciones y los complementos pueden abstraerla introduciendo su propia clase base con la anotación. Por ejemplo, si un complemento llamado turbo-component quisiera proporcionar un administrador de componentes personalizado, podría exportar una clase base como esta:

// addon/index.js
import EmberObject from '@ember/object';
import { componentManager } from '@ember/custom-component-manager';

export default componentManager(EmberObject.extend({
  // ...
}), 'turbo');

Los usuarios de este complemento pueden subclasificar la clase base TurboComponent para definir componentes que usen el administrador de componentes correcto:

import TurboComponent from 'turbo-component';

export default TurboComponent.extend({
  didInsertElementQuickly() {
    // ...
  }
});

API de componentes personalizados

Ningún componente es una isla y, por razones de compatibilidad con versiones anteriores, es importante que la introducción de una nueva API de componentes no rompa los componentes existentes.

Un ejemplo de esto es la API de jerarquía de vistas existente. Los componentes Ember pueden inspeccionar su componente principal a través de la propiedad parentView . Incluso si el padre no es Ember.Component , los componentes secundarios de Ember deberían tener una propiedad parentView no nula.

Actualmente, el CurlyComponentManager en Ember es responsable de mantener este estado, así como otro "estado de alcance" ambiental como el objetivo de las acciones.

Para evitar que los administradores de componentes mal implementados violen invariantes en el sistema existente, usamos un patrón de composición para personalizar el comportamiento mientras ocultamos las esquinas afiladas de la API subyacente.

import CustomComponentManager from "@ember/custom-component-manager";

export default new CustomComponentManager({
  // major and minor Ember version this manager targets
  version: "3.1",
  create({ ComponentClass, args }) {
    // Responsible for instantiating the component class and passing provided
    // component arguments.
    // The value returned here is passed as `component` in the below hooks.
  },
  getContext(component) {
    // Returns the object that serves as the root scope of the component template.
    // Most implementations should return `component`, so the component's properties
    // are looked up in curly expressions.
  },
  update(component, args) {
    // Called whenever the arguments to a component change.
  },
  destroy(component) {
  }
});

Fase 1: Componentes de Ember Object Glimmer

La clase base Component en Ember admite una larga lista de características, muchas de las cuales ya no se utilizan mucho. Estas funciones pueden imponer un costo de rendimiento, incluso cuando no se utilizan.

Como primer paso, queremos proporcionar una forma de participar en la API simplificada del componente Glimmer.js a través del paquete @glimmer/component . Para facilitar la migración, proporcionaremos una implementación de la clase base Glimmer Component que hereda de Ember.Object en @glimmer/component/compat .

A continuación, se muestra un ejemplo de cómo se ve un "componente Ember-Glimmer":

// src/ui/components/user-profile/component.js
import Component from '@glimmer/component/compat';
import { computed } from '@ember/object';

export default Component({
  fullName: computed('args.firstName', 'args.lastName', function() {
    let { firstName, lastName } = this.args
    return `${firstName} ${lastName}`;
  })

  isAdmin: false,

  toggleAdmin() {
    this.set('isAdmin', !this.isAdmin);
  }
});
{{!-- src/ui/components/user-profile/template.hbs --}}
<h1>{{fullName}}</h1>
<p>
  Welcome back, {{@firstName}}!
  {{#if isAdmin}}
    <strong>You are an admin.</strong>
  {{/if}}
</p>
<button {{action toggleAdmin}}>Toggle Admin Status</button>

Características destacables de estos componentes:

  • Las plantillas son HTML externo . Debido a que la plantilla de ejemplo anterior no tiene un solo elemento raíz, esto se representa como un "componente sin etiquetas".
  • Las acciones son solo funciones en el componente y no necesitan estar anidadas en el hash actions .
  • Los argumentos se establecen en la propiedad args lugar de establecer propiedades individuales en el componente directamente.
  • Usan el modelo de objetos Ember . Esto significa que las herramientas como las propiedades calculadas y los mixins con los que los desarrolladores de Ember ya están familiarizados continúan funcionando.

Igual de importante es lo que no está incluido:

  • @tracked propiedades
  • No hay propiedades layout o template en el componente.
  • No hay métodos o propiedades relacionados con la jerarquía de vistas, como childViews , parentView , nearestWithProperty , etc.
  • No tagName , attributeBindings u otro DSL JavaScript personalizado para modificar el elemento raíz.
  • No send o sendAction para enviar eventos.
  • No hay ember-view clase obligatoria o ID de elemento guid generado automáticamente.
  • Sin método rerender() manual.
  • Sin propiedad attrs (use args lugar).
  • Los argumentos aprobados son "unidireccionales" y no crean enlaces bidireccionales.
  • Los argumentos pasados ​​no se establecen como propiedades en la instancia del componente, lo que evita la posibilidad de colisiones de nombres difíciles de depurar.
  • No this.$() para crear un objeto jQuery para el elemento del componente.
  • No hay appendTo de componentes manuales en el DOM.
  • No hay soporte para los siguientes eventos del ciclo de vida:

    • willInsertElement

    • didRender

    • willRender

    • willClearRender

    • willUpdate

    • didReceiveAttrs

    • didUpdateAttrs

    • parentViewDidChange

  • Sin detector de eventos on() para los eventos del ciclo de vida de los componentes; los ganchos deben implementarse como métodos.

Un efecto secundario interesante de este conjunto de características es que encaja con el esfuerzo de habilitar clases de JavaScript . Junto con el diseño propuesto en el RFC de clases de ES , podemos proporcionar una implementación alternativa del componente anterior:

// src/ui/components/user-profile/component.js
import Component from '@glimmer/component/compat';
import { computed } from 'ember-decorators/object';

export default class extends Component {
  isAdmin = false;

  @computed('args.firstName', 'args.lastName')
  get fullName() {
    let { firstName, lastName } = this.args;
    return `${firstName} ${lastName}`;
  })

  toggleAdmin() {
    this.set('isAdmin', !this.isAdmin);
  }
});

Fase 2 - Sintaxis de corchetes angulares

La fase 2 permite invocar componentes mediante corchetes angulares ( <UserAvatar @user={{currentUser}} /> ) además de curlies ( {{my-component user=currentUser}} ). Debido a que esta sintaxis elimina la ambigüedad entre los argumentos de los componentes y los atributos HTML, esta función también permite "distribuir" los atributos pasados ​​en la plantilla del componente a través de …attributes .

{{! src/ui/components/UserAvatar/template.hbs }}
<div ...attributes> {{! <-- attributes will be inserted here }}
  <h1>Hello, {{@firstName}}!</h1>
</div>
<UserAvatar @user={{currentUser}} aria-expanded={{isExpanded}} />

Esto generaría una salida similar a la siguiente:

<div aria-expanded="true">
  <h1>Hello, Steven!</h1>
</div>

Fase 3 - Propiedades rastreadas

La fase 3 habilita las propiedades rastreadas a través del decorador @tracked en Ember. Se están resolviendo los detalles de la interoperabilidad entre el modelo de objetos de Ember y las propiedades rastreadas. Una vez que las propiedades rastreadas aterrizan, los usuarios podrán soltar el módulo @glimmer/component/compat y usar la clase base del componente normal, que no es Ember.Object.

Junto con la función "autotrack" recientemente fusionada (que infiere las dependencias de propiedades calculadas automáticamente), esto debería resultar en una mayor simplificación del código de la aplicación:

import Component, { tracked } from '@glimmer/component';

export default class extends Component {
  <strong i="24">@tracked</strong> isAdmin = false;

  <strong i="25">@tracked</strong> get fullName() {
    let { firstName, lastName } = this.args;
    return `${firstName} ${lastName}`;
  }

  toggleAdmin() {
    this.isAdmin = !this.isAdmin;
  }
}

Preguntas y respuestas

¿Puedo volver a agregar cosas como el nombre de clase ember-view o el atributo id generado automáticamente a los componentes de Glimmer para que sean compatibles con CSS existente?

Si. Ejemplo:

<div class="ember-view" id="{{uniqueId}}">
  Component content goes here.
</div>
import Component from '@glimmer/component';
import { guidFor } from '@ember/object/internals';

export default class extends Component {
  get uniqueId() {
    return guidFor(this);
  }
}

Recursos

Tareas

Usaremos las listas a continuación para realizar un seguimiento del trabajo en curso. A medida que aprendamos más durante la implementación, agregaremos o eliminaremos elementos de la lista.

API de administrador de componentes personalizados

  • [x] Actualice el RFC del componente personalizado para reflejar los cambios anteriores (@chancancode)
  • [x] Agregar glimmer-custom-component-manager marca
  • [x] Implementar la función componentManager

    • [x] Exponer como { componentManager } from '@ember/custom-component-manager'

  • [x] El solucionador necesita detectar clases anotadas
  • [x] El solucionador debe buscar el administrador de componentes especificado

    • [] Orientación para los autores de complementos sobre dónde colocar los administradores de componentes para que sean descubiertos

    • [] ¿Cómo funciona esto con la unificación de módulos? (@mixonic)

  • [x] Implementar CustomComponentManager API

    • [x] Definir la interfaz CustomComponentManagerDelegate

    • [x] version

    • [x] create()

    • [x] getContext()

    • [x] update()

    • [x] destroy?()

    • [x] didCreate?()

    • [x] didUpdate?()

    • [x] getView?()

    • [x] Validar la propiedad version al momento de la creación

    • [x] Internos

    • [x] Conservar childViews y parentView en componentes existentes

    • [] Instrumento para el rendimiento de renderizado

    • [] Instrumento de compatibilidad con Ember Inspector

Complemento de componente Glimmer

  • [x] Asistencia con el complemento sparkles-components

    • [x] La clase base debe importar y agregar componentManager anotación cuando se consume desde Ember

    • [x] La implementación de CustomComponentManager debería ser detectable a través del contenedor de Ember

  • [] Admite el uso de @glimmer/component como complemento de Ember

    • [] Conservar el comportamiento existente cuando se consume desde Glimmer.js

    • [] import Component from '@glimmer/component' proporciona una clase base de JavaScript simple

    • [] import Component from '@glimmer/component/compat' proporciona Ember.Object clase base

      buscar

  • [] Ganchos del ciclo de vida

    • [x] estático create(injections)

    • [x] didInsertElement()

    • [x] willDestroy()

    • [x] didUpdate()

    • [x] Los métodos invocados por delegación de eventos ( click() etc.) no deben activarse

  • [] Acceso al elemento

    • [] this.bounds

    • [] this.element alias de propiedad calculado en this.bounds

    • [] API basada en modificadores de elementos para exponer elementos

  • [] Argumentos

    • [x] this.args está disponible en el constructor

    • [x] this.args se actualiza antes de que se llame a didUpdate

    • [] this.args no debería activar un ciclo de renderizado infinito (es necesario verificarlo)

  • [] Documentación

    • [ ] Cómo instalar

    • [] Advertencias

    • [] Solo canario

    • [] Pre-1.0

    • [] Componentes "compatibles" de Ember-Glimmer

    • [] Plantillas HTML externas

    • [] Ganchos del ciclo de vida

    • [] Definición de propiedades calculadas



      • [] Cómo depender de args



    • [] Guía de migración / Componentes de Glimmer para ...

    • Guías para escribir componentes Glimmer eficaces para personas familiarizadas con otras bibliotecas

    • [] Componentes Glimmer para desarrolladores de Ember

    • [] Componentes de Glimmer para desarrolladores de React

    • [] Componentes Glimmer para desarrolladores angulares

    • [] Componentes Glimmer para desarrolladores COBOL

Preguntas abiertas

  1. ¿Cuáles son las mejores prácticas para las acciones? ¿Es esto algo a lo que debemos permitir que los administradores de componentes se conecten?
  2. ¿Cómo se usan las clases de JavaScript desnudas (aquellas sin método create() estático) como clases de componentes? El protocolo para inicializar un componente parece simple pero sorprendentemente complicado.
  3. ¿Cuál es el mejor lugar para documentar "API adicionales" como CustomComponentManager ?
  4. ¿Cómo manejamos el control CustomComponentManager versiones
Quest

Comentario más útil

@dbbk La razón principal por la que no requerimos {{...attributes}} es porque no es sintácticamente ambiguo con un atributo normal, como los otros casos en los que requerimos curlies, y cuatro caracteres menos parecían más fáciles de escribir y menos ruidosos visualmente.

Creo que también puede hacer que la gente asuma que attributes es una propiedad dentro del alcance, pero no lo es; no puede hacer <SomeComponent something={{attributes}} /> , por ejemplo, lo que sugiere esa sintaxis. También implica más fuertemente que la sintaxis de propagación funciona en otras posiciones cuando no lo hace.

Ha habido alguna sugerencia de adoptar @arguments como un enlace de plantilla especial y agregar la sintaxis de extensión ... en Handlebars, pero eso necesita un diseño e implementación que no está en la hoja de ruta inmediata. No me sorprendería del todo si en el futuro reemplazáramos ...attributes por {{...@attributes}} o algo así.

@ Gaurav0 La API del componente Glimmer pasaría por el proceso RFC antes de habilitarse de forma predeterminada en las aplicaciones Ember, en caso de que alguna vez quisiéramos hacerlo. Una cosa buena sobre el enfoque del administrador de componentes personalizados es que no canonizamos una API de "próxima generación", pero podemos permitir que diferentes diseños compitan por sus méritos a través del ecosistema de complementos.

@ Turbo87 Gran sugerencia,

Abordaré el caso de didReceiveAttrs() específicamente desde que lo mencionaste. Por lo general, la gente usa este gancho para inicializar el estado del componente basándose en argumentos pasados, pero en la práctica esto significa que puede hacer un poco de cosas innecesarias en estos ganchos, por ejemplo, para inicializar valores que nunca terminan usándose. Y, por supuesto, la gente termina haciendo cosas impactantes en estos ganchos que están en el camino de la renderización.

La alternativa es cambiar de la inicialización "basada en empuje" a la inicialización "basada en extracción". Por ejemplo, si quisiera hacer algún cálculo para generar o inicializar el valor de una propiedad de componente, en su lugar usaría una propiedad calculada rastreada. Esto garantiza que el trabajo solo se realice para los valores que realmente se utilizan. Ejemplo:

En lugar de esto:

import Component from "@ember/component";
import { computed } from "@ember/object";

export default Component.extend({
  didReceiveAttrs() {
    this.set('firstName', this.attrs.firstName || 'Tobias');
    this.set('lastName', this.attrs.lastName || 'Bieniek');
  },

  fullName: computed('firstName', 'lastName', function() {
    return `${this.firstName} ${this.lastName}`;
  })
});

Hacer esto:

import Component, { tracked } from "@glimmer/component";

export default class extends Component {
  <strong i="27">@tracked</strong> get firstName() {
    return this.args.firstName || 'Tobias';
  }

  <strong i="28">@tracked</strong> get lastName() {
    return this.args.lastName || 'Bieniek';
  }

  <strong i="29">@tracked</strong> get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

Admitiré que era escéptico de esto al principio, pero didReceiveAttrs que no se podría modelar con propiedades calculadas más eficientes.

Todos 23 comentarios

¡Coordinaremos el trabajo en el canal # st-glimmer-components en la comunidad de Ember Slack si estás interesado en ayudar! Los merodeadores también son bienvenidos si solo tienes curiosidad por saber cómo se hace la salchicha.

Con respecto a este;

{{! src/ui/components/UserAvatar/template.hbs }}
<div ...attributes> {{! <-- attributes will be inserted here }}
  <h1>Hello, {{@firstName}}!</h1>
</div>

No entiendo por qué se usan llaves para denotar variables dinámicas ( {{@firstName}} ), pero no los atributos salpicados. Para mí, visualmente parece que <div ...attributes> es lo que se va a renderizar. Por coherencia, ¿no sería mejor que <div {{...attributes}}> ?

¿Existen RFC para componentes brillantes en los que podamos discutir cómo deberían verse y funcionar?

@tomdale, el problema anterior menciona la documentación del modo de compatibilidad de componentes, pero no parece mencionar la documentación de una guía de migración. por ejemplo, me sorprendió bastante que didReceiveAttrs() ya no existiera, y sería genial tener alguna documentación que muestre cómo migrar casos de uso comunes a nuevos patrones de mejores prácticas.

@dbbk La razón principal por la que no requerimos {{...attributes}} es porque no es sintácticamente ambiguo con un atributo normal, como los otros casos en los que requerimos curlies, y cuatro caracteres menos parecían más fáciles de escribir y menos ruidosos visualmente.

Creo que también puede hacer que la gente asuma que attributes es una propiedad dentro del alcance, pero no lo es; no puede hacer <SomeComponent something={{attributes}} /> , por ejemplo, lo que sugiere esa sintaxis. También implica más fuertemente que la sintaxis de propagación funciona en otras posiciones cuando no lo hace.

Ha habido alguna sugerencia de adoptar @arguments como un enlace de plantilla especial y agregar la sintaxis de extensión ... en Handlebars, pero eso necesita un diseño e implementación que no está en la hoja de ruta inmediata. No me sorprendería del todo si en el futuro reemplazáramos ...attributes por {{...@attributes}} o algo así.

@ Gaurav0 La API del componente Glimmer pasaría por el proceso RFC antes de habilitarse de forma predeterminada en las aplicaciones Ember, en caso de que alguna vez quisiéramos hacerlo. Una cosa buena sobre el enfoque del administrador de componentes personalizados es que no canonizamos una API de "próxima generación", pero podemos permitir que diferentes diseños compitan por sus méritos a través del ecosistema de complementos.

@ Turbo87 Gran sugerencia,

Abordaré el caso de didReceiveAttrs() específicamente desde que lo mencionaste. Por lo general, la gente usa este gancho para inicializar el estado del componente basándose en argumentos pasados, pero en la práctica esto significa que puede hacer un poco de cosas innecesarias en estos ganchos, por ejemplo, para inicializar valores que nunca terminan usándose. Y, por supuesto, la gente termina haciendo cosas impactantes en estos ganchos que están en el camino de la renderización.

La alternativa es cambiar de la inicialización "basada en empuje" a la inicialización "basada en extracción". Por ejemplo, si quisiera hacer algún cálculo para generar o inicializar el valor de una propiedad de componente, en su lugar usaría una propiedad calculada rastreada. Esto garantiza que el trabajo solo se realice para los valores que realmente se utilizan. Ejemplo:

En lugar de esto:

import Component from "@ember/component";
import { computed } from "@ember/object";

export default Component.extend({
  didReceiveAttrs() {
    this.set('firstName', this.attrs.firstName || 'Tobias');
    this.set('lastName', this.attrs.lastName || 'Bieniek');
  },

  fullName: computed('firstName', 'lastName', function() {
    return `${this.firstName} ${this.lastName}`;
  })
});

Hacer esto:

import Component, { tracked } from "@glimmer/component";

export default class extends Component {
  <strong i="27">@tracked</strong> get firstName() {
    return this.args.firstName || 'Tobias';
  }

  <strong i="28">@tracked</strong> get lastName() {
    return this.args.lastName || 'Bieniek';
  }

  <strong i="29">@tracked</strong> get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

Admitiré que era escéptico de esto al principio, pero didReceiveAttrs que no se podría modelar con propiedades calculadas más eficientes.

@tomdale Estoy de acuerdo en que didReceiveAttrs no es necesario para la mayoría de las cosas. Pero a veces, me ha salvado de tener que escribir un observador en uno de los atributos transmitidos por el padre. Por ejemplo, ¿qué pasaría si quisiéramos iniciar una solicitud ajax si uno de los atributos cambia? Odiaría agregar ese tipo de efecto secundario a un captador.

sí, estoy pensando en casos de uso similares. un ejemplo es la animación. digo que quiero iniciar una animación cuando collapsed cambia de true a false y una diferente para la otra dirección. ¿Cómo funcionaría eso con componentes de luz tenue?

@ turbo87 @ Gaurav0 Ah, está bien, entonces el sistema está funcionando porque todos esos son pistolas de alto rendimiento que no deberían ejecutarse en didReceiveAttrs que es sincrónico! Todo lo que tenga efectos secundarios, como animaciones o solicitudes de red, debe programarse en enlaces asíncronos como didInsertElement o didUpdate .

didInsertElement no parece encajar bien en el caso de ejemplo anterior. didUpdate solo parece activarse cuando el componente se vuelve a generar, ¿correcto? pero ¿qué pasa si realmente no uso esa propiedad para renderizar? entonces el componente no se volvería a procesar al cambiar el argumento, por lo que nunca se llamaría didUpdate . 🤔

Además, didInsertElement arroja un error grave en Glimmer si configuramos algo y provocamos una repetición.

@ Turbo87 Puede haber una idea errónea sobre cuándo se llama a didUpdate . Este gancho se invoca cuando una propiedad rastreada usada en la plantilla _o un argumento pasado_ cambia. Vea este ejemplo: http://tinyurl.com/y7osxpy2. Cuando el componente padre pasa un argumento, el componente hijo recibe su gancho didUpdate llamado incluso si nunca "usó" ese argumento.

@ Gaurav0 Estás moviendo los postes de la portería. ;) Su ejemplo estaba iniciando una solicitud ajax en respuesta a un cambio de argumento, que no provocaría que se lanzara un error si sucediera en didInsertElement . Si desea establecer una propiedad sincrónicamente al mismo tiempo, debería poder modelar el mismo comportamiento con captadores perezosos. Si necesita establecer una propiedad de forma asincrónica después del ajax, por ejemplo, eso no activará el error porque sucederá en un bucle de eventos diferente.

bien, eso me parece suficientemente bueno. No quería ampliar este tema específico de todos modos, solo quería resaltar que se necesitarán guías de migración completas :)

@tomdale, ¿esto todavía está actualizado? parece estar inactivo durante algún tiempo.

@tomdale Ok, el escenario es que necesitamos iniciar una solicitud ajax cuando un atributo en particular ha cambiado, y solo ese atributo. No queremos escribir un observador. ¿Todavía podemos hacer esto en didInsertElement?

@ Gaurav0 ¿Qué está provocando que el atributo cambie? Siguiendo a DDAU, creo que cualquier código que sea responsable de cambiar los datos que hacen que el atributo cambie también debería ser responsable de iniciar la solicitud AJAX.

El atributo está cambiando del componente principal / controlador. DDAU.

@ Gaurav0 @tomdale Otro ejemplo de este caso de uso es un conjunto de componentes (en su mayoría) "sin DOM" que aplican cambios a algún otro "objeto". Un ejemplo actual sería ember-leaflet / ember-composability-tools, que permite construir declarativamente el contenido de un mapa de folletos. Para mí, esto no parece posible en los componentes de Glimmer en este momento debido a la falta de cualquier tipo de funcionalidad didReceiveAttrs o observer . ¿Cómo se abordaría o se podría abordar esto?

@nickschot Creo que puedo tomar este, de hecho hicimos una auditoría durante el proceso de diseño de varios complementos de la comunidad y casos de uso, y cubrimos específicamente ember-leaflet .

Si profundiza en ember-leaflet / ember-composability-tools , en realidad encontrará que nunca usa didReceiveAttrs o didUpdate o cualquier gancho de ciclo de vida como este . En realidad, solo necesita los ganchos init y willDestroy , para que los componentes se registren / cancelen el registro en su padre (y, en última instancia, en el padre de representación raíz). Esto se puede hacer en los ganchos constructor y willDestroy en los componentes de Glimmer. El componente principal raíz _si_ necesita la capacidad de usar didInsertElement , pero puede usarlo a través del modificador {{did-insert}} .

En el caso general, puede usar {{did-insert}} / {{did-update}} para reflejar cualquier número de atributos en elementos DOM. Estos se rastrean automáticamente, por lo que consumir un valor en ellos hará que se vuelvan a ejecutar si los valores cambian. También puede escribir sus propios modificadores personalizados si lo desea, y ellos también podrán hacerlo.

Hay un caso de uso que está algo relacionado, que no es posible con los componentes de Glimmer: un "detector de renderizado" como este en el complemento ember-google-maps . Esto es algo que es mucho menos común (solo encontramos este uso), y que se puede abordar a través de MutationObserver para ver el DOM, o mediante un administrador de componentes personalizados que opta por la capacidad de ejecutar los ganchos didUpdate / didRender . De hecho, creo que un componente <RenderDetector> propósito general sería una buena manera de manejar todos estos casos de uso, ya que aislaría la capacidad de observar el subárbol del componente para las actualizaciones en un solo componente fácil de encontrar.

@pzuraq ha pasado un tiempo, pero una pequeña actualización en esta área. Para mi caso de uso, no puedo usar modificadores ya que no hay DOM dentro de estos componentes. Lo que he hecho actualmente es implementar un administrador de componentes personalizado con semántica glimmer que vuelve a habilitar el gancho didUpdate.

@nickschot nuestra recomendación es utilizar un ayudante. Los ayudantes no pueden devolver nada y efectos secundarios, similar a useEffect en React si está familiarizado (mientras que los modificadores son más similares a useLayoutEffect ).

La API de ayuda basada en clases necesita algo de trabajo, idealmente debería coincidir con la API modificadora de cara al usuario final, creo (además, necesitamos introducir administradores de ayuda para que sea posible rediseñarla), pero la API actual debería darte todo las habilidades que necesita para lograr los efectos secundarios que busca.

@pzuraq ¿Cómo funciona? ¿Necesitamos crear un ayudante personalizado o cualquier ayudante servirá?

¿Es la sintaxis así?

{{concat (did-insert this.myAction)}}

Se siente como pasar un argumento y no "modificar".

¿O estás sugiriendo que deberíamos usar ayudantes en lugar de modificadores? Algo como:

{{my-did-insert this.myAction}}

En caso afirmativo, publique algunos enlaces a documentación y ejemplos. La API pública actual del asistente de clase solo tiene compute y recompute , nada sobre el ciclo de vida del componente.

Disculpe las preguntas estúpidas, pero para los usuarios habituales de Ember este asunto es bastante críptico, la documentación es escasa y está dispersa.

¿O estás sugiriendo que deberíamos usar ayudantes en lugar de modificadores?

Exactamente. Puede ver un ejemplo de esto en ember-render-helpers, por ejemplo, que imita la API @ember/render-modifiers .

En general, compute activa la primera vez que se inserta un ayudante en la plantilla o se calcula, y se vuelve a activar cada vez que cambia alguno de sus argumentos. También realiza un seguimiento automático en 3.13+.

Si necesita algo como {{did-insert}} , por ejemplo, puede hacer:

export default class didInsert extends Helper {
  compute(fn) {
    if (!this.rendered) {
      fn();
      this.rendered = true;
    }
  }
}

Y no se preocupe, entiendo que puede parecer un poco frustrante y críptico. Esa es la naturaleza de estar a la vanguardia / el lado canario de las cosas, aún no hemos documentado todos los nuevos patrones y estamos trabajando en ello 🙂

Creo que este problema se puede cerrar ahora que Octane salió 😄

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