<Fragment> (<>...</>)
<Fragment>, spesso usato tramite la sintassi <>...</>, ti permette di raggruppare elementi senza un nodo wrapper.
I Fragment possono anche accettare ref, che consentono di interagire con i nodi DOM sottostanti senza aggiungere elementi wrapper.
<>
<OneChild />
<AnotherChild />
</>- Reference
- Usage
- Returning multiple elements
- Assigning multiple elements to a variable
- Grouping elements with text
- Rendering a list of Fragments
- Adding event listeners without a wrapper element
- Managing focus across a group of elements
- Scrolling a group of elements into view
- Observing visibility without a wrapper element
- Caching a global IntersectionObserver
Reference
<Fragment>
Avvolgi elementi in <Fragment> per raggrupparli insieme in situazioni in cui ti serve un singolo elemento. Raggruppare elementi in un Fragment non ha effetto sul DOM risultante; è come se gli elementi non fossero raggruppati. Il tag JSX vuoto <></> è una scorciatoia per <Fragment></Fragment> nella maggior parte dei casi.
Props
- optional
key: I Fragment dichiarati con la sintassi esplicita<Fragment>possono avere delle key. - optional
ref: Un oggetto ref (ad esempio dauseRef) o una funzione callback. React fornisce unFragmentInstancecome valore del ref che implementa metodi per interagire con i nodi DOM avvolti dal Fragment.
Caveats
-
Se vuoi passare
keya un Fragment, non puoi usare la sintassi<>...</>. Devi importare esplicitamenteFragmentda'react'e renderizzare<Fragment key={yourKey}>...</Fragment>. -
React non reimposta lo state quando passi dal renderizzare
<><Child /></>a[<Child />]o viceversa, o quando passi dal renderizzare<><Child /></>a<Child />e viceversa. Questo funziona solo a un singolo livello di profondità: ad esempio, passare da<><><Child /></></>a<Child />reimposta lo state. Vedi la semantica precisa qui. -
Se vuoi passare
refa un Fragment, non puoi usare la sintassi<>...</>. Devi importare esplicitamenteFragmentda'react'e renderizzare<Fragment ref={yourRef}>...</Fragment>.
FragmentInstance
Quando passi un ref a un Fragment, React fornisce un oggetto FragmentInstance. Implementa metodi per interagire con i figli DOM di primo livello avvolti dal Fragment.
addEventListenereremoveEventListenergestiscono i listener di eventi su tutti i figli DOM di primo livello.dispatchEventesegue il dispatch di un evento sul Fragment, che può propagarsi al genitore DOM.focus,focusLasteblurgestiscono il focus su tutti i figli annidati in profondità (depth-first).observeUsingeunobserveUsingcollegano e scollegano istanze diIntersectionObserveroResizeObserver.getClientRectsrestituisce i rettangoli di delimitazione di tutti i figli DOM di primo livello.getRootNoderestituisce il nodo root del genitore del Fragment.compareDocumentPositionconfronta la posizione del Fragment con un altro nodo.scrollIntoViewscorre i figli del Fragment nella vista.
addEventListener(type, listener, options?)
Aggiunge un listener di eventi a tutti i figli DOM di primo livello del Fragment.
fragmentRef.current.addEventListener('click', handleClick);Parameters
type: Una stringa che rappresenta il tipo di evento da ascoltare (ad esempio'click','focus').listener: La funzione gestore di eventi.- optional
options: Un oggetto options o un booleano per capture, corrispondente all’API DOMaddEventListener.
Returns
addEventListener non restituisce nulla (undefined).
removeEventListener(type, listener, options?)
Rimuove un listener di eventi da tutti i figli DOM di primo livello del Fragment.
fragmentRef.current.removeEventListener('click', handleClick);Parameters
type: La stringa del tipo di evento.listener: La funzione gestore di eventi da rimuovere.- optional
options: Un oggetto options o un booleano, corrispondente all’API DOMremoveEventListener.
Returns
removeEventListener non restituisce nulla (undefined).
dispatchEvent(event)
Esegue il dispatch di un evento sul Fragment. I listener di eventi aggiunti vengono chiamati e l’evento può propagarsi al genitore DOM del Fragment.
fragmentRef.current.dispatchEvent(new Event('custom', { bubbles: true }));Parameters
event: Un oggettoEventda inviare. Sebubblesètrue, l’evento si propaga al nodo DOM genitore del Fragment.
Returns
true se l’evento non è stato annullato, false se è stato chiamato preventDefault().
focus(options?)
Imposta il focus sul primo nodo DOM focusabile nel Fragment. A differenza di chiamare element.focus() su un elemento DOM, questo metodo cerca tutti i figli annidati in profondità (depth-first) finché non trova un elemento focusabile — non solo l’elemento stesso o i suoi figli diretti.
fragmentRef.current.focus();Parameters
- optional
options: Un oggettoFocusOptions(ad esempio{ preventScroll: true }).
Returns
focus non restituisce nulla (undefined).
focusLast(options?)
Imposta il focus sull’ultimo nodo DOM focusabile nel Fragment. Cerca i figli annidati in profondità (depth-first), poi itera in ordine inverso.
fragmentRef.current.focusLast();Parameters
- optional
options: Un oggettoFocusOptions.
Returns
focusLast non restituisce nulla (undefined).
blur()
Rimuove il focus dall’elemento attivo se si trova all’interno del Fragment. Se document.activeElement non è all’interno del Fragment, blur non fa nulla.
fragmentRef.current.blur();Returns
blur non restituisce nulla (undefined).
observeUsing(observer)
Inizia a osservare tutti i figli DOM di primo livello del Fragment con l’observer fornito.
const observer = new IntersectionObserver(callback, options);
fragmentRef.current.observeUsing(observer);Parameters
observer: Un’istanza diIntersectionObserveroResizeObserver.
Returns
observeUsing non restituisce nulla (undefined).
unobserveUsing(observer)
Interrompe l’osservazione dei figli DOM del Fragment con l’observer specificato.
fragmentRef.current.unobserveUsing(observer);Parameters
observer: La stessa istanza diIntersectionObserveroResizeObserverprecedentemente passata aobserveUsing.
Returns
unobserveUsing non restituisce nulla (undefined).
getClientRects()
Restituisce un array flat di oggetti DOMRect che rappresentano i rettangoli di delimitazione di tutti i figli DOM di primo livello.
const rects = fragmentRef.current.getClientRects();Returns
Un Array<DOMRect> contenente i rettangoli di delimitazione di tutti i figli.
getRootNode(options?)
Restituisce il nodo root che contiene il nodo DOM genitore del Fragment, corrispondendo al comportamento di Node.getRootNode().
const root = fragmentRef.current.getRootNode();Parameters
- optional
options: Un oggetto con una proprietà booleanacomposed, corrispondente all’API DOMgetRootNode.
Returns
Un Document, ShadowRoot, o il FragmentInstance stesso se non c’è un nodo DOM genitore.
compareDocumentPosition(otherNode)
Confronta la posizione nel documento del Fragment con un altro nodo, restituendo una bitmask corrispondente al comportamento di Node.compareDocumentPosition().
const position = fragmentRef.current.compareDocumentPosition(otherElement);Parameters
otherNode: Il nodo DOM con cui confrontare.
Returns
Una bitmask di flag di posizione. I Fragment vuoti e i Fragment con figli renderizzati tramite un portal includono Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC nel risultato.
scrollIntoView(alignToTop?)
Scorre i figli del Fragment nella vista. Quando alignToTop è true o omesso, scorre per allineare il primo figlio in alto rispetto all’antenato scrollabile. Quando alignToTop è false, scorre per allineare l’ultimo figlio in basso.
fragmentRef.current.scrollIntoView();Parameters
- optional
alignToTop: Un booleano. Setrue(il default), scorre il primo figlio in alto nell’area scrollabile. Sefalse, scorre l’ultimo figlio in basso. A differenza diElement.scrollIntoView(), questo metodo non accetta un oggettoScrollIntoViewOptions.
Returns
scrollIntoView non restituisce nulla (undefined).
Caveats
scrollIntoViewnon accetta un oggetto options. Passarne uno genera un errore. Usa il booleanoalignToTopal suo posto.- Quando il Fragment non ha figli,
scrollIntoViewscorre il fratello o genitore più vicino nella vista come fallback.
FragmentInstance Caveats
- I metodi che targettano i figli (come
addEventListener,observeUsingegetClientRects) operano sui figli host (DOM) di primo livello del Fragment. Non targettano direttamente i figli annidati all’interno di un altro elemento DOM. focusefocusLastcercano i figli annidati in profondità (depth-first) per trovare elementi focusabili, a differenza dei metodi per eventi e observer che targettano solo i figli host di primo livello.observeUsingnon funziona sui nodi di testo. React registra un warning in development se il Fragment contiene solo figli di testo.- React non applica i listener di eventi aggiunti tramite
addEventListeneragli alberi<Activity>nascosti. Quando un confineActivitypassa da nascosto a visibile, i listener vengono applicati automaticamente. - Ogni figlio DOM di primo livello di un Fragment con un
refottiene una proprietàreactFragments— unSet<FragmentInstance>contenente tutte le istanze Fragment che possiedono l’elemento. Questo consente di memorizzare nella cache un observer condiviso tra più Fragment.
Usage
Returning multiple elements
Usa Fragment, o l’equivalente sintassi <>...</>, per raggruppare più elementi insieme. Puoi usarlo per mettere più elementi in qualsiasi punto in cui può andare un singolo elemento. Ad esempio, un componente può restituire solo un elemento, ma usando un Fragment puoi raggruppare più elementi e restituirli come gruppo:
function Post() {
return (
<>
<PostTitle />
<PostBody />
</>
);
}I Fragment sono utili perché raggruppare elementi con un Fragment non ha effetto su layout o stili, a differenza di quando avvolgi gli elementi in un altro contenitore come un elemento DOM. Se ispezioni questo esempio con gli strumenti del browser, vedrai che tutti i nodi DOM <h1> e <article> appaiono come fratelli senza wrapper attorno a loro:
export default function Blog() { return ( <> <Post title="An update" body="It's been a while since I posted..." /> <Post title="My new blog" body="I am starting a new blog!" /> </> ) } function Post({ title, body }) { return ( <> <PostTitle title={title} /> <PostBody body={body} /> </> ); } function PostTitle({ title }) { return <h1>{title}</h1> } function PostBody({ body }) { return ( <article> <p>{body}</p> </article> ); }
Approfondimento
L’esempio sopra è equivalente a importare Fragment da React:
import { Fragment } from 'react';
function Post() {
return (
<Fragment>
<PostTitle />
<PostBody />
</Fragment>
);
}Di solito non ne avrai bisogno a meno che tu non debba passare una key al tuo Fragment.
Assigning multiple elements to a variable
Come qualsiasi altro elemento, puoi assegnare elementi Fragment a variabili, passarli come props e così via:
function CloseDialog() {
const buttons = (
<>
<OKButton />
<CancelButton />
</>
);
return (
<AlertDialog buttons={buttons}>
Are you sure you want to leave this page?
</AlertDialog>
);
}Grouping elements with text
Puoi usare Fragment per raggruppare testo insieme a componenti:
function DateRangePicker({ start, end }) {
return (
<>
From
<DatePicker date={start} />
to
<DatePicker date={end} />
</>
);
}Rendering a list of Fragments
Ecco una situazione in cui devi scrivere Fragment esplicitamente invece di usare la sintassi <></>. Quando renderizzi più elementi in un loop, devi assegnare una key a ogni elemento. Se gli elementi all’interno del loop sono Fragment, devi usare la normale sintassi degli elementi JSX per fornire l’attributo key:
function Blog() {
return posts.map(post =>
<Fragment key={post.id}>
<PostTitle title={post.title} />
<PostBody body={post.body} />
</Fragment>
);
}Puoi ispezionare il DOM per verificare che non ci siano elementi wrapper attorno ai figli del Fragment:
import { Fragment } from 'react'; const posts = [ { id: 1, title: 'An update', body: "It's been a while since I posted..." }, { id: 2, title: 'My new blog', body: 'I am starting a new blog!' } ]; export default function Blog() { return posts.map(post => <Fragment key={post.id}> <PostTitle title={post.title} /> <PostBody body={post.body} /> </Fragment> ); } function PostTitle({ title }) { return <h1>{title}</h1> } function PostBody({ body }) { return ( <article> <p>{body}</p> </article> ); }
Adding event listeners without a wrapper element
I ref dei Fragment ti permettono di aggiungere listener di eventi a un gruppo di elementi senza aggiungere un nodo DOM wrapper. Usa una ref callback per collegare e ripulire i listener:
import { Fragment, useState, useRef, useEffect } from 'react'; function ClickableFragment({ children, onClick }) { const fragmentRef = useRef(null); useEffect(() => { const fragmentInstance = fragmentRef.current; if (fragmentInstance === null) { return; } fragmentInstance.addEventListener('click', onClick); return () => { fragmentInstance.removeEventListener( 'click', onClick ); }; }, [onClick]) return ( <Fragment ref={fragmentRef}> {children} </Fragment> ); } export default function App() { const [clicks, setClicks] = useState(0); return ( <> <p>Total clicks: {clicks}</p> <ClickableFragment onClick={() => { setClicks(c => c + 1); }}> <button>Button A</button> <button>Button B</button> <button>Button C</button> </ClickableFragment> </> ); }
La chiamata addEventListener applica il listener a ogni figlio DOM di primo livello del Fragment. Quando i figli vengono aggiunti o rimossi dinamicamente, il FragmentInstance aggiunge o rimuove automaticamente il listener.
Approfondimento
Un FragmentInstance targetta i figli host (DOM) di primo livello del Fragment. Considera questo albero:
<Fragment ref={ref}>
<div id="A" />
<Wrapper>
<div id="B">
<div id="C" />
</div>
</Wrapper>
<div id="D" />
</Fragment>Wrapper è un componente React, quindi il FragmentInstance lo attraversa per trovare i nodi DOM. I figli targettati sono A, B e D. C non è targettato perché è annidato all’interno dell’elemento DOM B.
Metodi come addEventListener, observeUsing e getClientRects operano su questi figli DOM di primo livello. focus e focusLast sono diversi — cercano tutti i figli annidati in profondità (depth-first) per trovare elementi focusabili.
Managing focus across a group of elements
I ref dei Fragment forniscono i metodi focus, focusLast e blur che operano su tutti i nodi DOM all’interno del Fragment:
import { Fragment, useRef } from 'react'; function FormFields({ children }) { const fragmentRef = useRef(null); return ( <> <div className="buttons"> <button onClick={() => { fragmentRef.current.focus(); }}> Focus first </button> <button onClick={() => { fragmentRef.current.focusLast(); }}> Focus last </button> <button onClick={() => { fragmentRef.current.blur(); }}> Blur </button> </div> <Fragment ref={fragmentRef}> {children} </Fragment> </> ); } // Anche se gli input sono profondamente annidati, // focus() li cerca in profondità (depth-first) per trovarli. export default function App() { return ( <FormFields> <fieldset> <legend>Shipping</legend> <label> Street: <input name="street" /> </label> <label> City: <input name="city" /> </label> </fieldset> </FormFields> ); }
Chiamare focus() imposta il focus sull’input street — anche se è annidato all’interno di un <fieldset> e un <label>. focus() cerca in profondità (depth-first) attraverso tutti i figli annidati, non solo i figli diretti del Fragment. focusLast() fa lo stesso in ordine inverso, e blur() rimuove il focus se l’elemento attualmente focalizzato si trova all’interno del Fragment.
Scrolling a group of elements into view
Usa scrollIntoView per scorrere i figli di un Fragment nella vista senza un elemento wrapper. Passa true (o ometti l’argomento) per scorrere il primo figlio in alto. Passa false per scorrere l’ultimo figlio in basso:
import { Fragment, useRef } from 'react'; function ScrollableSection({ children }) { const fragmentRef = useRef(null); return ( <> <div className="buttons"> <button onClick={() => { fragmentRef.current.scrollIntoView(); }}> Scroll to top </button> <button onClick={() => { fragmentRef.current.scrollIntoView(false); }}> Scroll to bottom </button> </div> <div className="container"> <Fragment ref={fragmentRef}> {children} </Fragment> </div> </> ); } const items = []; for (let i = 1; i <= 25; i++) { items.push('Item ' + i); } export default function App() { return ( <ScrollableSection> <h3>Section Start</h3> {items.map((item) => ( <p key={item}>{item}</p> ))} <h3>Section End</h3> </ScrollableSection> ); }
Observing visibility without a wrapper element
Usa observeUsing per collegare un IntersectionObserver a tutti i figli DOM di primo livello di un Fragment. Questo ti permette di tracciare la visibilità senza richiedere ai componenti figli di esporre ref o aggiungere un elemento wrapper:
import { Fragment, useRef, useLayoutEffect, useState, } from 'react'; import Card from './Card'; function VisibleGroup({ onVisibilityChange, children }) { const fragmentRef = useRef(null); useLayoutEffect(() => { const visibleElements = new Set(); const observer = new IntersectionObserver( (entries) => { entries.forEach(e => { if (e.isIntersecting) { visibleElements.add(e.target); } else { visibleElements.delete(e.target); } }); onVisibilityChange(visibleElements.size > 0); } ); const fragmentInstance = fragmentRef.current; fragmentInstance.observeUsing(observer); return () => { fragmentInstance.unobserveUsing(observer); }; }, [onVisibilityChange]); return ( <Fragment ref={fragmentRef}> {children} </Fragment> ); } export default function App() { const [isVisible, setIsVisible] = useState(true); return ( <div className={isVisible ? 'page visible' : 'page'}> <div className="filler">Scroll down</div> <VisibleGroup onVisibilityChange={setIsVisible}> <Card title="First section" /> <Card title="Second section" /> </VisibleGroup> <div className="filler">Scroll up</div> </div> ); }
Caching a global IntersectionObserver
Un’ottimizzazione delle performance comune per siti con molti observer è condividere un singolo IntersectionObserver per configurazione e instradare le sue entry ai callback corretti in base a quale elemento ha intersecato. I ref dei Fragment supportano lo stesso pattern tramite la proprietà reactFragments.
Ogni figlio DOM di primo livello di un Fragment con un ref ha una proprietà reactFragments: un Set di oggetti FragmentInstance che contengono quell’elemento. Quando l’observer condiviso scatta, puoi usare questa proprietà per cercare quale FragmentInstance possiede l’elemento che interseca ed eseguire i callback corretti.
import { useState, useCallback } from 'react'; import ObservedGroup from './ObservedGroup'; import Card from './Card'; export default function App() { const [bgColor, setBgColor] = useState(null); const onGreen = useCallback((entry) => { if (entry.isIntersecting) { setBgColor('#d4edda'); } }, []); const onBlue = useCallback((entry) => { if (entry.isIntersecting) { setBgColor('#cce5ff'); } }, []); return ( <div className="page" style={{ background: bgColor || 'white', }}> <div className="filler">Scroll down</div> <ObservedGroup onIntersection={onGreen}> <Card title="Green section" className="green" /> </ObservedGroup> <div className="filler" /> <ObservedGroup onIntersection={onBlue}> <Card title="Blue section" className="blue" /> </ObservedGroup> <div className="filler">Scroll up</div> </div> ); }
Più componenti ObservedGroup con le stesse options riutilizzano un singolo IntersectionObserver. Quando una delle sezioni scorre nella vista, l’observer condiviso scatta e usa reactFragments per instradare l’entry al callback corretto.