Continuamos publicando la traducción del tutorial de la guía oficial de Ember.js. El tutorial consta de dos partes y esta es la segunda mitad de la primera parte del tutorial. Le recordamos que puede leer la primera mitad en este enlace.
La lista de temas cubiertos en el tutorial sugiere:
- Usando Ember CLI
- Navegación de archivos y carpetas de aplicaciones Ember
- Crea y enlaza páginas
- Plantillas y componentes
- Pruebas automatizadas
- Trabajar con datos del servidor
- Segmentos dinámicos en rutas
- Servicios en Ember
- Biblioteca de datos de ascuas
- Adaptadores y serializadores
- Patrón de componentes del proveedor
Siéntese, abra terminales, encuentre el proyecto en su computadora y sigamos adelante. Y recuerde que si tiene alguna dificultad, siempre puede pedir ayuda en el canal de la comunidad Discord (en el canal ruso # lang-russian ), así como en el canal de telegramas en idioma ruso ember_js
Detalles del componente
Es hora de trabajar finalmente en la lista de alquileres:

Al compilar esta lista de propiedades de alquiler, aprenderá sobre:
- Generación de componentes.
- Organizar código usando componentes de espacio de nombres
- Reenviar atributos HTML con
...attributes
- Determinar la cantidad adecuada de cobertura de prueba
Generación de componentes
Comencemos creando el componente <Rental>
. Esta vez usaremos el generador de componentes para crear una plantilla y un archivo de prueba para nosotros:
$ ember generate component rental installing component create app/components/rental.hbs skip app/components/rental.js tip to add a class, run `ember generate component-class rental` installing component-test create tests/integration/components/rental-test.js
El generador creó dos archivos nuevos para nosotros: la plantilla del componente en app/components/rental.hbs
y el archivo de prueba del componente en tests/integration/components/rental-test.js
.
Comenzaremos editando la plantilla. Codifiquemos los detalles de un objeto arrendado y luego reemplácelo con datos reales del servidor.

Luego, escribiremos una prueba para asegurarnos de que todos los detalles estén presentes. Reemplazaremos la prueba de plantilla generada por nuestros propios equipos, como lo hicimos anteriormente para el componente <Jumbo>
:

Las pruebas deben pasar.

Finalmente, agreguemos un nuevo componente a la plantilla de índice para llenar la página.

En este caso, deberíamos ver el componente <Rental>
mostrando nuestra Grand Old Mansion tres veces en la página:

¡Todo se ve muy bien por un poco de trabajo!
Organizar código usando espacios de nombres
A continuación, agregue una imagen para la propiedad de alquiler. Usaremos el generador de componentes para esto nuevamente:
$ ember generate component rental/image installing component create app/components/rental/image.hbs skip app/components/rental/image.js tip to add a class, run `ember generate component-class rental/image` installing component-test create tests/integration/components/rental/image-test.js
Esta vez teníamos /
en el nombre del componente. Esto condujo a la creación de un componente en app/components/rental/image.hbs
, que se puede llamar como <Rental::Image>
.
Componentes similares se conocen como componentes de espacio de nombres . El espacio de nombres nos permite organizar nuestros componentes en carpetas de acuerdo con su propósito. Esto es completamente opcional, pero conveniente, especialmente cuando está desarrollando una aplicación grande en un equipo.
Reenviar atributos HTML con ...attributes
Editemos la plantilla del componente:

En lugar de establecer valores específicos para los atributos src
y alt
en <img>
, elegimos la palabra clave ...attributes
, que a veces también se llama "splattributes". Esto le permite pasar atributos HTML arbitrarios al llamar a este componente, por ejemplo, así:

Especificamos aquí el atributo HTML src
y alt
, que se pasará al componente y se adjuntará al elemento, donde ...attributes
se aplican en la plantilla del componente. Puede pensar que esto es similar a {{yield}}
, pero solo para los atributos HTML y no para el contenido de visualización. De hecho, ya usamos esta función anteriormente cuando pasamos el atributo de class
a <LinkTo>
.

Por lo tanto, nuestro componente <Rental::Image>
no está asociado con ninguna propiedad de alquiler en particular en el sitio. Por supuesto, todo también está codificado con nosotros, pero trataremos esto pronto. Mientras tanto, restringiremos todo el código duro a un componente para que sea más fácil borrarlo cuando pasamos a extraer datos reales.
En general, es una buena idea agregar ...attributes
al elemento raíz en su componente. Esto proporcionará la máxima flexibilidad, ya que el iniciador puede necesitar pasar clases de estilo o atributos ARIA para mejorar la accesibilidad.
¡Ahora escribamos una prueba para nuestro nuevo componente!

Determinar la cantidad adecuada de cobertura de prueba
Finalmente, también necesitamos actualizar las pruebas para el componente <Rental>
para asegurarnos de que llamamos con éxito a <Rental::Image>
.

Como ya hemos escrito pruebas relacionadas con <Rental::Image>
, aquí podemos omitir los detalles y minimizar la verificación. Por lo tanto, tampoco tenemos que actualizar la prueba <Rental>
cada vez que hacemos cambios en <Rental::Image>
.

Componentes interactivos
En este capítulo, agrega interactividad a la página, lo que permite al usuario hacer clic en la imagen para ampliarla o reducirla:

En este caso, aprenderá sobre:
- Agregar comportamiento a componentes con clases
- Acceder a estados de instancia desde plantillas
- Gestión de estado con propiedades rastreadas
- Uso de sintaxis condicionales en plantillas
- Responder a la interacción del usuario con acciones
- Cómo llamar a modificadores de elementos
- Prueba de interacción del usuario
Agregar comportamiento a componentes con clases
Hasta ahora, todos los componentes que escribimos son puramente de presentación: son solo fragmentos de marcado reutilizable. Esto es, por supuesto, maravilloso, ¡pero en Ember los componentes pueden hacer mucho más!
A veces desea asociar algún comportamiento con sus componentes para que puedan hacer cosas más interesantes. Por ejemplo, <LinkTo>
puede responder a los clics cambiando la URL y yendo a otra página.
¡Aquí vamos a hacer algo similar! Vamos a implementar la funcionalidad de «View Larger»
y «View Smaller»
, lo que permitirá a nuestros usuarios hacer clic en la imagen en el hogar, ver la versión más grande y volver a hacer clic para volver a la versión más pequeña.
En otras palabras, necesitamos una forma de cambiar la imagen entre uno de los dos estados . Para hacer esto, necesitamos una forma para que el componente almacene dos estados posibles y sepa en qué estado se encuentra actualmente.
Ember además nos permite asociar el código JavaScript con un componente específicamente para este propósito. Podemos agregar un archivo JavaScript para nuestro componente <Rental::Image>
ejecutando el generador de componentes:
$ ember generate component-class rental/image installing component-class create app/components/rental/image.js
Este comando generó un archivo JavaScript con el mismo nombre que nuestra plantilla de componente en app/components/rentals/image.js
. Contiene una clase de JavaScript heredada de @glimmer/component
.
Zoe explica ...
@glimmer/component
o Glimmer component es una de varias clases de componentes disponibles para su uso. Son un excelente punto de partida cuando desea agregar comportamiento a sus componentes. En este tutorial, usaremos solo componentes de Glimmer.
En general, los componentes Glimmer deben usarse siempre que sea posible. Sin embargo, también puede ver @ember/components
(componentes clásicos) utilizados en aplicaciones más antiguas. Puede distinguirlos entre sí mirando la ruta para importarlos (lo cual es útil al buscar la documentación adecuada, ya que tienen API diferentes e incompatibles ).
Ember crea una instancia de la clase cada vez que se llama a nuestro componente. Podemos usar esta instancia para almacenar nuestro estado:

Aquí, en el constructor de componentes , inicializamos la variable de instancia this.isLarge
con el valor false
, ya que este es el estado predeterminado que queremos para nuestro componente.
Acceder a estados de instancia desde plantillas
Actualicemos nuestra plantilla para usar este estado que acabamos de agregar:

En la plantilla, tenemos acceso a las variables de instancia del componente. La sintaxis condicional {{#if ...}}...{{else}}...{{/if}}
permite mostrar contenido diferente dependiendo de la condición (en este caso, el valor de la this.isLarge
instancia this.isLarge
). Combinando estas dos funciones, podemos visualizar respectivamente una versión pequeña y una grande de la imagen.
Podemos verificar esto cambiando temporalmente el valor inicial en nuestro archivo JavaScript. Si cambiamos app/components/rental/image.js
para inicializar this.isLarge = true
; en el constructor deberíamos ver una versión grande de la imagen de la propiedad en el navegador. Wow!

Después de verificar, podemos cambiar esto. this.isLarge
nuevo a false
.
Dado que este patrón de inicialización, por ejemplo, las variables en el constructor es bastante común, hay una sintaxis mucho más corta para él:

¡La misma funcionalidad, pero mucho más corta!
Por supuesto, nuestros usuarios no pueden editar nuestro código fuente, por lo que necesitamos una forma de cambiar el tamaño de la imagen desde el navegador. En particular, queremos alternar el valor de this.isLarge
cada vez que un usuario hace clic en nuestro componente.
Administración del estado utilizando propiedades rastreadas
Cambiemos nuestra clase agregando un método para cambiar el tamaño:

Hicimos algunas cosas aquí, así que vamos a resolverlo.
Primero, agregamos el decorador @tracked
a la isLarge
instancia @tracked
. Esta anotación le dice a Ember que haga un seguimiento de esta variable para las actualizaciones. Cada vez que cambia el valor de esta variable, Ember vuelve a dibujar automáticamente las plantillas que dependen de su valor.
En nuestro caso, cada vez que asignemos un nuevo valor a this.isLarge
, la anotación this.isLarge
@tracked
que Ember sobreestime la condición {{#if this.isLarge}}
en nuestra plantilla y cambie entre los dos bloques, respectivamente.
Zoe explica ...
No te preocupes! Si hace referencia a una variable en la plantilla, pero olvidó agregar el decorador @tracked
, en el modo de desarrollo obtendrá un claro error al cambiar su valor.
Procesamos las acciones de los usuarios.
Luego agregamos el método toggleSize
a nuestra clase, que alterna this.isLarge
en lugar de su estado actual ( false
convierte en true
o true
convierte en false
).
Finalmente, agregamos el decorador @action
a nuestro método. Esto le indica a Ember que tenemos la intención de usar este método desde nuestra plantilla. Sin esto, el método no funcionaría correctamente como una función de manejo de eventos (en este caso, un controlador de clics).
Zoe explica ...
Si olvida agregar el decorador @action
, también recibirá un error cuando haga clic en el botón en modo de desarrollo.
Ahora es el momento de usar esto en la plantilla:

Hemos cambiado dos cosas.
Primero, dado que queríamos que nuestro componente fuera interactivo, cambiamos la etiqueta que contiene de <div>
a <button>
(esto es importante por razones de accesibilidad). Usando la etiqueta semántica correcta, también obtendremos enfoque "libre" e interacción con el teclado.
Luego usamos el modificador {{on}}
para adjuntar this.toggleSize
como un controlador de clic de botón.
Por lo tanto, creamos nuestro primer componente interactivo. ¡Prueba cómo funciona en el navegador!

Prueba de interacción del usuario
Finalmente, escriba una prueba para este nuevo comportamiento:


Resultado de la prueba

Limpiemos nuestra plantilla antes de continuar. Agregamos muchos duplicados cuando insertamos expresiones condicionales en la plantilla. Si nos fijamos bien, las únicas cosas que difieren entre estos dos bloques son:
1) La presencia de una clase CSS "large"
en <button>
.
2) El texto «»
y «»
.
Estos cambios están ocultos en muchos códigos duplicados. Podemos reducir la duplicación usando la expresión {{if}}
su lugar:

La versión de la expresión {{if}}
toma dos argumentos. El primer argumento es la condición . El segundo argumento es una expresión, que debe ejecutarse si la condición es verdadera.
Opcionalmente, {{if}}
puede tomar como tercer argumento una expresión que debe ejecutarse si la condición es falsa. Esto significa que podríamos reescribir la etiqueta del botón de la siguiente manera:

Si esto es una mejora en la claridad de nuestro código es cuestión de gustos. En cualquier caso, hemos reducido significativamente la duplicación en nuestro código y hemos hecho que las piezas lógicas importantes sean más visibles.
Ejecute las pruebas por última vez para asegurarse de que nuestra refactorización no rompa nada, ¡y estaremos listos para el próximo desafío!

Reutilizando componentes
La parte restante no realizada en el componente es un mapa que muestra la ubicación de la casa, en la que continuaremos trabajando:

Al agregar un mapa, aprenderá sobre:
- Gestión de configuración de nivel de aplicación
- Parametrización de componentes con argumentos.
- Acceso a argumentos de componentes
- Interpolar valores en plantillas
- Reemplazar atributos HTML en ... atributos
- Refactorización con captadores y seguimiento automático (seguimiento automático)
- Recuperando valores de JavaScript en un contexto de prueba
Gestión de configuración de nivel de aplicación
Utilizaremos la API de Mapbox para crear mapas para nuestras propiedades de alquiler. Puede registrarse de forma gratuita y sin tarjeta de crédito.
Mapbox proporciona una API de imagen de mapa estática que sirve imágenes de mapa en formato PNG. Esto significa que podemos generar la URL apropiada para los parámetros que queremos y mostrar el mapa usando el estándar <img>
. Clase!
Si está interesado, puede explorar las opciones disponibles en Mapbox utilizando un sandbox interactivo .
Una vez que se haya registrado con el servicio, tome su token público (token público predeterminado) y péguelo en config/environment.js
:


Como su nombre lo indica, config/environment.js
usa para configurar nuestra aplicación y almacenar claves API como estas. Se puede acceder a estos valores desde otras partes de nuestra aplicación, y pueden tener valores diferentes según el entorno actual (que puede ser desarrollo, prueba (prueba) o producción (producción)).
Zoe explica ...
Si lo desea, puede crear diferentes tokens de acceso de Mapbox para usar en diferentes entornos. Como mínimo, cada token debe tener un alcance de "estilos: mosaico" para utilizar la API de imagen estática de Mapbox.
Después de guardar los cambios en nuestro archivo de configuración, necesitaremos reiniciar nuestro servidor de desarrollo para recibir estos cambios. A diferencia de los archivos que editamos, config/environment.js
no se reinicia automáticamente.
Puede detener el servidor buscando la ventana de terminal donde se ember server
ejecutando el ember server
, luego presione Ctrl + C Es decir, presionando la tecla "C" en el teclado mientras mantiene presionada la tecla "Ctrl". Una vez que se haya detenido, puede iniciarlo nuevamente utilizando el mismo comando del ember server
.
$ ember server building... Build successful (13286ms) – Serving on http://localhost:4200/
Crear un componente que contenga una clase de componente
Después de agregar la clave API de Mapbox a la aplicación, generemos un nuevo componente para nuestro mapa.
$ ember generate component map --with-component-class installing component create app/components/map.js create app/components/map.hbs installing component-test create tests/integration/components/map-test.js
Dado que no todos los componentes necesariamente tendrán un comportamiento específico asociado, el generador de componentes por defecto no crea un archivo JavaScript para nosotros. Como vimos anteriormente, siempre podemos usar el generador de componentes para agregarlo más tarde.
Sin embargo, en el caso de nuestro componente <Map>
, estamos bastante seguros de que necesitaremos un archivo JavaScript para algunos comportamientos que aún tenemos que definir. Por lo tanto, podemos pasar el indicador --with-component-class
generador de --with-component-class
para que tengamos todo lo que necesitamos desde el principio.
Zoe aconseja ...
¿Demasiado para escribir? Utilice el ember g component map -gc
. El indicador -gc
denota el componente Glimmer (componente g limmer c ) y también genera la clase ( g enerate c lass)
Parametrización de componentes utilizando argumentos
Comencemos con nuestro archivo JavaScript:

Aquí importamos el token de acceso del archivo de configuración y lo devolvemos del token
getter. Esto nos permite acceder a nuestro token como this.token
tanto dentro de la clase MapComponent
como en la plantilla del componente. También es importante codificar el token, en caso de que contenga caracteres especiales que no sean seguros para la URL.
Interpolación de valores en patrones.
Ahora pasemos del archivo JavaScript a la plantilla:

Primero, tenemos un elemento contenedor para el estilo.
Luego tenemos <img>
para solicitar y representar una imagen de mapa estática de Mapbox.
Nuestra plantilla contiene varios valores que aún no existen: @lat
, @lng
, @zoom
, @width
y @height
. Estos son los argumentos del componente <Map>
que le proporcionaremos cuando se lo llame.
Parametrizando nuestro componente con argumentos, creamos un componente reutilizable que se puede llamar desde diferentes partes de la aplicación y personalizar para satisfacer las necesidades de estos contextos específicos. Ya vimos esto en acción cuando usamos el componente <LinkTo>
anteriormente; necesitábamos especificar el argumento @route
que supiera a qué página ir.
Hemos proporcionado un valor predeterminado razonable para el atributo alt
basado en los valores de los @lng
@lat
y @lng
. Puede notar que interpolamos directamente los valores en el valor del atributo alt
. Ember combinará automáticamente estos valores interpolados en un valor de cadena, incluida la ejecución de cualquier HTML de escape necesario.
Anular atributos HTML para ...attributes
Luego usamos ...attributes
para permitir que la persona que llama personalice aún más <img>
, por ejemplo, pase atributos adicionales como class
, y también anule nuestro atributo alt
predeterminado con uno más específico o amigable para los humanos.
El orden es importante! Ember aplica los atributos en el orden en que aparecen. Al asignar al atributo el valor alt
predeterminado primero (antes de aplicar ...attributes
), le brindamos explícitamente a la persona que llama la oportunidad de proporcionar un atributo alt
más especializado de acuerdo con el caso de uso necesario.
Dado que el atributo alt
pasado (si existe) aparecerá después del nuestro, anulará el valor que especificamos. Por otro lado, es importante que asignemos los atributos src
, width
y height
después de ... para que el invocador no los sobrescriba accidentalmente.
El atributo src
interpola todos los parámetros necesarios en el formato de URL para la API de imagen estática de Mapbox, incluido el token seguro de URL this.token
.
Finalmente, dado que estamos usando la imagen @2x
"retina", debemos especificar los atributos de width
y height
. De lo contrario, el <img>
se mostrará el doble de lo que esperábamos.
Hemos agregado bastante código a un componente, ¡así que escribamos algunas pruebas! En particular, debemos asegurarnos de que tenemos una cobertura de prueba para el comportamiento de los atributos HTML anulados, que discutimos anteriormente.



Tenga en cuenta que la hasAttribute
auxiliar hasAttribute
de qunit-dom
admite el uso de expresiones regulares . , , src
https://api.mapbox.com/
( , ). , , .
… .

, ! , ? , <Map>
<Rental>
:

!

...
, , config/environment.js
MAPBOX_ACCESS_TOKEN
. ! , Ember-.
<Rental>
, <Map>
.

- (auto-track)
<Map>
src
<img>
, . — JavaScript .
JavaScript API this.args.*
. , URL .
...
this.args
— API, Glimmer. (, «» ) (legacy) , API JavaScript .


! !

, @tracked
. , , Ember .
, , , . , src
lat
, lng
, width
, height
zoom
this.args
. , , , {{this.src}}
.
Ember , . @tracked
, Ember , (invalidate) , «» . (auto-track). , this.args
( , this.args.*
), @tracked
Glimmer. , (Just works).
JavaScript
, :



API this.setProperties
, .
, . ( !).
this
, render
. «» . .
!

<Rental>
. , :

:
- (model hook)
- (mocking) JSON
- (remote)
{{#each}}
<Rental>
. , , , , . .
, , . , .
, Ember — . , , .
. app/routes/index.js
:

, , . Route . , .
Route IndexRoute, , .
. ? model()
. .
, . Ember , . , , ( ).
, . , async
. await
. ( : , Promise Javascript )
. , , JavaScript ( POJO (Plain Old Javascript Object)).
, , , . @model
. POJO, .
, , title
:

, .

Genial
, , , , , ! <Rental>
, .
.
-, <Rental>
@rental
. <h1>
, , :

@model
<Rental>
@rental
, «Grand Old Mansion» <Rental>
! , @rental
.


, «Grand Old Mansion», , .

, : , .
, , , , .
<Rental>
. , setProperties
, .

, <Rental>
render
@rental
. , !

(mocking) JSON
, , , , , !
, , , API. , API . JSON . , JSON HTTP- — , API, — - . Genial!
? , JSON .zip. public
.
, public
:
public ├── api │ ├── rentals │ │ ├── downtown-charm.json │ │ ├── grand-old-mansion.json │ │ └── urban-living.json │ └── rentals.json ├── assets │ └── images │ └── teaching-tomster.png └── robots.txt 4 directories, 6 files
, , URL http://localhost:4200/api/rentals.json
.

«» JSON. !
(remote)
. , .

?
, Fetch API JSON API /api/rentals.json
, URL, .
, . Fetch API , fetch
async
, . , await
.
Fetch API . , ; , JSON, json()
. , await
.
, , .


JSON:API , , .
-, JSON:API , "data"
, . ; , , , — , .
, , . type
id
, (!). , , attributes
.
, , , , : , , type
, - . type
"Standalone" "Community", , <Rental>
.
JSON:API. .
:

(parsing) JSON attributes
, type
, . , , - .
! .
(helper) {{#each}}
, , — index.hbs
, <Rental>
. @rental
@model
. , @model
— ()! , , .
.

{{#each}}...{{/each}}
, . — — . — <Rental>
, <li>
.
{{rental}}
. rental
? , ! as |rental|
each
. - , as |property|
, {{property}}
.
, .

¡Hurra! , . fetch
. , ?
, !

( 1.1 1.2)
Ember!
Ember, .
#Emberjs. Discord , ember_js
, , , !
UPDATE: MK1301 .