Componentes Anidados
Livewire le permite anidar componentes Livewire adicionales dentro de un componente padre. Esta característica es inmensamente poderosa, ya que le permite reutilizar y encapsular el comportamiento dentro de los componentes Livewire que se comparten a través de su aplicación.
Consulte nuestro examen técnico en profundidad del anidamiento de componentes Livewire para obtener más información sobre el rendimiento, las implicaciones de uso y las limitaciones de los componentes Livewire anidados.
Anidando un componente
Para anidar un componente Livewire dentro de un componente padre, simplemente inclúyalo en la vista Blade del componente padre. A continuación se muestra un ejemplo de un componente padre Dashboard
que contiene un componente TodoList
anidado:
<?php
namespace App\Livewire;
use Livewire\Component;
class Dashboard extends Component{ public function render() { return view('livewire.dashboard'); }}
<div> <h1>Dashboard</h1>
<livewire:todo-list /></div>
En el renderizado inicial de esta página, el componente Dashboard
encontrará <livewire:todo-list />
y lo renderizará en su lugar. En una petición de red posterior a Dashboard
, el componente anidado todo-list
omitirá la renderización porque ahora es su propio componente independiente en la página. Para obtener más información sobre los conceptos técnicos que subyacen a la anidación y la renderización, consulte nuestra documentación sobre por qué los componentes anidados son «islas».
Para más información sobre la sintaxis de los componentes de renderizado, consulte nuestra documentación sobre Componentes de renderizado.
Pasar accesorios(props) a los hijos
Pasar datos de un componente padre a un componente hijo es muy sencillo. De hecho, es muy parecido a pasar props a un componente Blade típico.
Por ejemplo, veamos un componente TodoList
que pasa una colección de $todos
a un componente hijo llamado TodoCount
:
<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;use Livewire\Component;
class TodoList extends Component{ public function render() { return view('livewire.todo-list', [ 'todos' => Auth::user()->todos, ]); }}
<div> <livewire:todo-count :todos="$todos" />
<!-- ... --></div>
Como puede ver, estamos pasando $todo
s a todo-count
con la sintaxis: :todos="$todos"
.
Ahora que $todos
ha sido pasado al componente hijo, puedes recibir esos datos a través del método mount()
del componente hijo:
<?php
namespace App\Livewire;
use Livewire\Component;use App\Models\Todo;
class TodoCount extends Component{ public $todos;
public function mount($todos) { $this->todos = $todos; }
public function render() { return view('livewire.todo-count', [ 'count' => $this->todos->count(), ]); }}
Pasando accesorios (props) estáticos
En el ejemplo anterior, pasamos props a nuestro componente hijo usando la sintaxis de props dinámicas de Livewire, que soporta expresiones PHP como esta:
<livewire:todo-count :todos="$todos" />
Sin embargo, a veces es posible que desee pasar a un componente un valor estático simple, como una cadena. En estos casos, puede omitir los dos puntos al principio de la sentencia:
<livewire:todo-count :todos="$todos" label="Todo Count:" />
Se pueden proporcionar valores booleanos a los componentes especificando únicamente la clave. Por ejemplo, para pasar una variable $inline
con un valor de true
a un componente, podemos simplemente colocar inline
en la etiqueta del componente:
<livewire:todo-count :todos="$todos" inline />
Sintaxis abreviada de atributos
Cuando se pasan variables PHP a un componente, el nombre de la variable y el nombre del atributo son a menudo el mismo. Para evitar escribir el nombre dos veces, Livewire le permite simplemente anteponer dos puntos a la variable:
<!-- Sin abreviar --><livewire:todo-count :todos="$todos" />
<!-- Abreviado --><livewire:todo-count :$todos />
Renderización de hijos en un bucle
Al renderizar un componente hijo dentro de un bucle, debe incluir un valor key
para cada iteración.
Las claves de los componentes son la forma en que Livewire rastrea cada componente en renderizaciones posteriores, especialmente si un componente ya ha sido renderizado o si se han reorganizado varios componentes en la página.
Puede especificar la clave del componente especificando una proposición :key
en el componente hijo:
<div> <h1>Todos</h1>
@foreach ($todos as $todo) <livewire:todo-item :$todo :key="$todo->id" /> @endforeach</div>
Como puede ver, cada componente hijo tendrá una clave única establecida en el ID de cada $todo
. Esto asegura que la clave será única y rastreada si los todos son reordenados.
Atributos(props) reactivos
Los desarrolladores nuevos en Livewire esperan que los props sean «reactivos» por defecto. En otras palabras, esperan que cuando un padre cambie el valor de una proposición pasada a un componente hijo, el componente hijo se actualice automáticamente. Sin embargo, por defecto, los props de Livewire no son reactivos.
Cuando se utiliza Livewire, cada componente es una isla. Esto significa que cuando se activa una actualización en el componente padre y se envía una petición de red, sólo el estado del componente padre se envía al servidor para ser renderizado - no el del componente hijo. La intención detrás de este comportamiento es sólo enviar la cantidad mínima de datos de ida y vuelta entre el servidor y el cliente, haciendo que las actualizaciones tengan el mayor rendimiento posible.
Pero, si desea o necesita que una prop sea reactiva, puede activar fácilmente este comportamiento utilizando el parámetro de atributo #[Reactive]
.
Por ejemplo, abajo está la plantilla de un componente TodoList
padre. Dentro, está renderizando un componente TodoCount
y pasando la lista actual de todos:
<div> <h1>Todos:</h1>
<livewire:todo-count :$todos />
<!-- ... --></div>
Ahora vamos a añadir #[Reactive]
a la propiedad $todos
en el componente TodoCount
. Una vez que lo hayamos hecho, cualquier todos que se añada o elimine dentro del componente padre activará automáticamente una actualización dentro del componente TodoCount
:
<?php
namespace App\Livewire;
use Livewire\Attributes\Reactive;use Livewire\Component;use App\Models\Todo;
class TodoCount extends Component{ #[Reactive] public $todos;
public function render() { return view('livewire.todo-count', [ 'count' => $this->todos->count(), ]); }}
Las propiedades reactivas son una característica increíblemente poderosa, haciendo Livewire más similar a las bibliotecas de componentes frontales como Vue y React. Pero, es importante entender las implicaciones de rendimiento de esta característica y sólo añadir #[Reactive]
cuando tenga sentido para su escenario particular.
Vinculación a datos hijo mediante wire:model
Otro poderoso patrón para compartir estado entre componentes padre e hijo es usar wire:model
directamente en un componente hijo a través de la característica Modelable
de Livewire.
Este comportamiento es muy comúnmente necesario cuando se extrae un elemento de entrada en un componente Livewire dedicado mientras se sigue accediendo a su estado en el componente padre.
A continuación se muestra un ejemplo de un componente padre TodoList
que contiene una propiedad $todo
que rastrea la tarea actual a punto de ser añadida por un usuario:
<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;use Livewire\Component;use App\Models\Todo;
class TodoList extends Component{ public $todo = '';
public function add() { Todo::create([ 'content' => $this->pull('todo'), ]); }
public function render() { return view('livewire.todo-list', [ 'todos' => Auth::user()->todos, ]); }}
Como puedes ver en la plantilla TodoList
, wire:model
está siendo usado para enlazar la propiedad $todo
directamente a un componente anidado TodoInput
:
<div> <h1>Todos</h1>
<livewire:todo-input wire:model="todo" />
<button wire:click="add">Nueva Tarea</button>
<div> @foreach ($todos as $todo) <livewire:todo-item :$todo :key="$todo->id" /> @endforeach </div></div>
Livewire proporciona un atributo #[Modelable]
que puedes añadir a cualquier propiedad de un componente hijo para hacerlo modelable desde un componente padre.
A continuación se muestra el componente TodoInput
con el atributo #[Modelable]
añadido sobre la propiedad $value
para indicar a Livewire que si wire:model
es declarado en el componente por un padre debe vincularse a esta propiedad:
<?php
namespace App\Livewire;
use Livewire\Component;use Livewire\Attributes\Modelable;
class TodoInput extends Component{ #[Modelable] public $value = '';
public function render() { return view('livewire.todo-input'); }}
<div> <input type="text" wire:model="value" ></div>
Ahora el componente TodoList
padre puede tratar a TodoInput
como cualquier otro elemento de entrada y vincularlo directamente a su valor usando wire:model
.
Escuchando los eventos de los hijos
Otra potente técnica de comunicación entre componentes padre-hijo es el sistema de eventos de Livewire, que permite enviar un evento al servidor o al cliente que puede ser interceptado por otros componentes.
Nuestra documentación completa sobre el sistema de eventos de Livewire proporciona información más detallada sobre los eventos, pero a continuación vamos a discutir un ejemplo sencillo de cómo utilizar un evento para desencadenar una actualización en un componente padre.
Considere un componente TodoList
con funcionalidad para mostrar y eliminar todos:
<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;use Livewire\Component;use App\Models\Todo;
class TodoList extends Component{ public function remove($todoId) { $todo = Todo::find($todoId);
$this->authorize('delete', $todo);
$todo->delete(); }
public function render() { return view('livewire.todo-list', [ 'todos' => Auth::user()->todos, ]); }}
<div> @foreach ($todos as $todo) <livewire:todo-item :$todo :key="$todo->id" /> @endforeach</div>
Para llamar a remove()
desde dentro de los componentes TodoItem
hijos, puedes añadir un evento a TodoList
a través del atributo #[On]
:
<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;use Livewire\Component;use App\Models\Todo;use Livewire\Attributes\On;
class TodoList extends Component{ #[On('remove-todo')] public function remove($todoId) { $todo = Todo::find($todoId);
$this->authorize('delete', $todo);
$todo->delete(); }
public function render() { return view('livewire.todo-list', [ 'todos' => Auth::user()->todos, ]); }}
Una vez que el atributo ha sido añadido a la acción, puedes enviar el evento remove-todo
desde el componente hijo TodoList
:
<?php
namespace App\Livewire;
use Livewire\Component;use App\Models\Todo;
class TodoItem extends Component{ public Todo $todo;
public function remove() { $this->dispatch('remove-todo', todoId: $this->todo->id); }
public function render() { return view('livewire.todo-item'); }}
<div> <span>{{ $todo->content }}</span>
<button wire:click="remove">Eliminar</button></div>
Ahora cuando el botón «Eliminar» es pulsado dentro de un TodoItem
, el componente padre TodoList
interceptará el evento y realizará la eliminación.
Después de que la tarea sea eliminada en el componente padre, la lista será renderizada de nuevo y el componente hijo que envió el evento remove-todo
será eliminado de la página.
Mejora del rendimiento mediante el envío del lado del cliente
Aunque el ejemplo anterior funciona, se necesitan dos peticiones de red para realizar una sola acción:
- La primera petición de red desde el componente
TodoItem
activa la acciónremove
, enviando el eventoremove-todo
. - La segunda petición de red es después de que el evento
remove-todo
se envíe al cliente y es interceptada porTodoList
para llamar a su acciónremove
.
Puedes evitar la primera petición enviando el evento remove-todo
directamente desde el cliente. Abajo se muestra un componente TodoItem
actualizado que no dispara una petición de red al enviar el evento remove-todo
:
<?php
namespace App\Livewire;
use Livewire\Component;use App\Models\Todo;
class TodoItem extends Component{ public Todo $todo;
public function render() { return view('livewire.todo-item'); }}
<div> <span>{{ $todo->content }}</span>
<button wire:click="$dispatch('remove-todo', { todoId: {{ $todo->id }} })">Eliminar</button></div>
Como regla general, siempre que sea posible, prefiera despachar(dispatch) del lado del cliente.
Acceso directo al padre desde el hijo
La comunicación de eventos añade una capa de indirección. Un padre puede escuchar un evento que nunca es enviado desde un hijo, y un hijo puede enviar un evento que nunca es interceptado por un padre.
Esta indirección es a veces deseable; sin embargo, en otros casos puede que prefiera acceder a un componente padre directamente desde el componente hijo.
Livewire le permite lograr esto proporcionando una variable mágica $parent
a su plantilla Blade que puede utilizar para acceder a acciones y propiedades directamente desde el hijo. Aquí está la plantilla TodoItem
reescrita para llamar a la acción remove()
directamente en el padre a través de la variable mágica $parent
:
<div> <span>{{ $todo->content }}</span>
<button wire:click="$parent.remove({{ $todo->id }})">Eliminar</button></div>
Los eventos y la comunicación directa con el padre son algunas de las formas de comunicación entre los componentes padre e hijo. Comprender sus ventajas y desventajas te permitirá tomar decisiones más informadas sobre qué patrón utilizar en un escenario concreto.
Componentes Dinámicos Hijos
A veces, puede que no sepa qué componente hijo debe mostrarse en una página hasta el momento de la ejecución. Por lo tanto, Livewire le permite elegir un componente hijo en tiempo de ejecución mediante <livewire:dynamic-component ...>
, que recibe una prop :is
:
<livewire:dynamic-component :is="$current" />
Los componentes dinámicos hijo son útiles en una variedad de escenarios diferentes, pero a continuación se muestra un ejemplo de representación de diferentes pasos en un formulario de varios pasos utilizando un componente dinámico:
<?php
namespace App\Livewire;
use Livewire\Component;
class Steps extends Component{ public $current = 'step-one';
protected $steps = [ 'step-one', 'step-two', 'step-three', ];
public function next() { $currentIndex = array_search($this->current, $this->steps);
$this->current = $this->steps[$currentIndex + 1]; }
public function render() { return view('livewire.todo-list'); }}
<div> <livewire:dynamic-component :is="$current" :key="$current" />
<button wire:click="next">Siguiente</button></div>
Ahora, si la propiedad $current
del componente Steps
se establece en “step-one”, Livewire mostrará un componente llamado “step-one” de la siguiente manera:
<?php
namespace App\Livewire;
use Livewire\Component;
class StepOne extends Component{ public function render() { return view('livewire.step-one'); }}
Si lo prefiere, puede utilizar la sintaxis alternativa:
<livewire:is :component="$current" :key="$current" />
Componentes recursivos
Aunque la mayoría de las aplicaciones rara vez lo necesitan, los componentes Livewire pueden anidarse recursivamente, lo que significa que un componente padre se renderiza a sí mismo como su hijo.
Imagine una encuesta que contiene un componente SurveyQuestion
que puede tener sub-preguntas adjuntas a sí mismo:
<?php
namespace App\Livewire;
use Livewire\Component;use App\Models\Question;
class SurveyQuestion extends Component{ public Question $question;
public function render() { return view('livewire.survey-question', [ 'subQuestions' => $this->question->subQuestions, ]); }}
<div> Pregunta: {{ $question->content }}
@foreach ($subQuestions as $subQuestion) <livewire:survey-question :question="$subQuestion" :key="$subQuestion->id" /> @endforeach</div>
Forzar la renderización de un componente hijo
Por debajo, Livewire genera una clave para cada componente Livewire anidado en su plantilla.
Por ejemplo, considere el siguiente componente anidado todo-count
:
<div> <livewire:todo-count :$todos /></div>
Livewire adjunta internamente una clave de cadena aleatoria al componente de esta manera:
<div> <livewire:todo-count :$todos key="lska" /></div>
Cuando el componente padre está renderizando y encuentra un componente hijo como el anterior, almacena la clave en una lista de hijos adjunta al padre:
'children' => ['lska'],
Livewire utiliza esta lista como referencia en posteriores renderizaciones para detectar si un componente hijo ya ha sido renderizado en una petición anterior. Si ya se ha renderizado, el componente se omite. Recuerde que los componentes anidados son islas. Sin embargo, si la clave hija no está en la lista, lo que significa que aún no ha sido renderizada, Livewire creará una nueva instancia del componente y lo renderizará en su lugar.
Estos matices son todos comportamientos entre bastidores que la mayoría de los usuarios no necesitan conocer; sin embargo, el concepto de establecer una clave en un hijo es una poderosa herramienta para controlar el renderizado de los hijos.
Usando este conocimiento, si quieres forzar a un componente a volver a renderizarse, puedes simplemente cambiar su clave.
A continuación se muestra un ejemplo en el que podríamos querer destruir y reinicializar el componente todo-count
si los $todos
que se pasan al componente cambian:
<div> <livewire:todo-count :todos="$todos" :key="$todos->pluck('id')->join('-')" /></div>
Como puede ver arriba, estamos generando una cadena :key
dinámica basada en el contenido de $todos
. De esta forma, el componentetodo-count
se mostrará y existirá normalmente hasta que los $todos
cambien. En ese momento, el componente se reiniciará completamente desde cero, y el componente antiguo será descartado.