React Labs: View Transitions, Activity e altro

23 aprile 2025 di Ricky Hanlon


Nota bene

Questa pagina è stata tradotta automaticamente e supervisionata da un maintainer. Un’ulteriore revisione da parte della community sarebbe comunque utile. Migliora questa traduzione.

Nei post React Labs scriviamo dei progetti in ricerca e sviluppo attivi. In questo post condividiamo due nuove funzionalità sperimentali pronte da provare oggi e aggiornamenti su altre aree su cui stiamo lavorando.

Oggi siamo entusiasti di pubblicare la documentazione per due nuove funzionalità sperimentali pronte per i test:

Condividiamo anche aggiornamenti su nuove funzionalità attualmente in sviluppo:


Nuove funzionalità sperimentali

Nota bene

<Activity /> è disponibile in react@19.2.

<ViewTransition /> e addTransitionType sono ora disponibili in react@canary.

View Transitions e Activity sono ora pronte per i test in react@experimental. Queste funzionalità sono state testate in produzione e sono stabili, ma l’API finale potrebbe ancora cambiare man mano che incorporiamo feedback.

Puoi provarle aggiornando i pacchetti React alla versione sperimentale più recente:

  • react@experimental
  • react-dom@experimental

Continua a leggere per scoprire come usare queste funzionalità nella tua app, oppure consulta la documentazione appena pubblicata:

  • <ViewTransition>: Un componente che ti permette di attivare un’animazione per una Transizione.
  • addTransitionType: Una funzione che ti permette di specificare la causa di una Transizione.
  • <Activity>: Un componente che ti permette di nascondere e mostrare parti dell’UI.

View Transitions

Le View Transitions di React sono una nuova funzionalità sperimentale che rende più semplice aggiungere animazioni alle transizioni UI nella tua app. Sotto il cofano, queste animazioni usano la nuova API startViewTransition disponibile nella maggior parte dei browser moderni.

Per attivare l’animazione di un elemento, avvolgilo nel nuovo componente <ViewTransition>:

// "cosa" animare.
<ViewTransition>
<div>animami</div>
</ViewTransition>

Questo nuovo componente ti permette di definire in modo dichiarativo “cosa” animare quando un’animazione viene attivata.

Puoi definire “quando” animare usando uno di questi tre trigger per una View Transition:

// "quando" animare.

// Transizioni
startTransition(() => setState(...));

// Valori differiti
const deferred = useDeferredValue(value);

// Suspense
<Suspense fallback={<Fallback />}>
<div>Caricamento...</div>
</Suspense>

Per impostazione predefinita, queste animazioni usano le animazioni CSS predefinite per le View Transitions (tipicamente un cross-fade fluido). Puoi usare i pseudo-selettori view transition per definire “come” l’animazione viene eseguita. Per esempio, puoi usare * per cambiare l’animazione predefinita per tutte le transizioni:

// "come" animare.
::view-transition-old(*) {
animation: 300ms ease-out fade-out;
}
::view-transition-new(*) {
animation: 300ms ease-in fade-in;
}

Quando il DOM si aggiorna a causa di un trigger di animazione—come startTransition, useDeferredValue o un fallback Suspense che passa al contenuto—React userà euristiche dichiarative per determinare automaticamente quali componenti <ViewTransition> attivare per l’animazione. Il browser eseguirà quindi l’animazione definita in CSS.

Se conosci la View Transition API del browser e vuoi sapere come React la supporta, consulta Come funziona <ViewTransition> nella documentazione.

In questo post, diamo un’occhiata ad alcuni esempi di come usare le View Transitions.

Partiamo da questa app, che non anima nessuna delle seguenti interazioni:

  • Clicca un video per vedere i dettagli.
  • Clicca “indietro” per tornare al feed.
  • Digita nella lista per filtrare i video.
import TalkDetails from './Details'; import Home from './Home'; import {useRouter} from './router';

export default function App() {
  const {url} = useRouter();

  // 🚩Questa versione non include ancora animazioni
  return url === '/' ? <Home /> : <TalkDetails />;
}

Nota bene

Le View Transitions non sostituiscono le animazioni CSS e JS

Le View Transitions sono pensate per transizioni UI come navigazione, espansione, apertura o riordino. Non sono pensate per sostituire tutte le animazioni della tua app.

Nella nostra app di esempio sopra, nota che ci sono già animazioni quando clicchi il pulsante “mi piace” e nel glimmer del fallback Suspense. Sono buoni casi d’uso per animazioni CSS perché animano un elemento specifico.

Animare le navigazioni

La nostra app include un router abilitato a Suspense, con transizioni di pagina già marcate come Transizioni, il che significa che le navigazioni vengono eseguite con startTransition:

function navigate(url) {
startTransition(() => {
go(url);
});
}

startTransition è un trigger di View Transition, quindi possiamo aggiungere <ViewTransition> per animare tra le pagine:

// "cosa" animare
<ViewTransition key={url}>
{url === '/' ? <Home /> : <TalkDetails />}
</ViewTransition>

Quando l’url cambia, il <ViewTransition> e la nuova route vengono renderizzati. Poiché il <ViewTransition> è stato aggiornato dentro startTransition, il <ViewTransition> viene attivato per un’animazione.

Per impostazione predefinita, le View Transitions includono l’animazione cross-fade predefinita del browser. Aggiungendola al nostro esempio, ora abbiamo un cross-fade ogni volta che navighiamo tra le pagine:

import {ViewTransition} from 'react'; import Details from './Details';
import Home from './Home'; import {useRouter} from './router';

export default function App() {
  const {url} = useRouter();

  // Usa ViewTransition per animare tra le pagine.
  // Nessun CSS aggiuntivo necessario per impostazione predefinita.
  return (
    <ViewTransition>
      {url === '/' ? <Home /> : <Details />}
    </ViewTransition>
  );
}

Poiché il nostro router aggiorna già la route usando startTransition, questa modifica di una riga per aggiungere <ViewTransition> si attiva con l’animazione cross-fade predefinita.

Se ti chiedi come funziona, consulta la documentazione per Come funziona <ViewTransition>?

Nota bene

Escludere le animazioni di <ViewTransition>

In questo esempio, avvolgiamo la radice dell’app in <ViewTransition> per semplicità, ma questo significa che tutte le transizioni nell’app saranno animate, il che può portare ad animazioni inaspettate.

Per risolvere, avvolgiamo i figli della route con "none" così ogni pagina può controllare la propria animazione:

// Layout.js
<ViewTransition default="none">
{children}
</ViewTransition>

In pratica, le navigazioni dovrebbero essere gestite tramite le props “enter” e “exit”, oppure usando i Transition Types.

Personalizzare le animazioni

Per impostazione predefinita, <ViewTransition> include il cross-fade predefinito del browser.

Per personalizzare le animazioni, puoi fornire props al componente <ViewTransition> per specificare quali animazioni usare, in base a come si attiva <ViewTransition>.

Per esempio, possiamo rallentare l’animazione cross-fade default:

<ViewTransition default="slow-fade">
<Home />
</ViewTransition>

E definire slow-fade in CSS usando le classi view transition:

::view-transition-old(.slow-fade) {
animation-duration: 500ms;
}

::view-transition-new(.slow-fade) {
animation-duration: 500ms;
}

Ora il cross-fade è più lento:

import { ViewTransition } from "react";
import Details from "./Details";
import Home from "./Home";
import { useRouter } from "./router";

export default function App() {
  const { url } = useRouter();

  // Definisci un'animazione predefinita .slow-fade.
  // Vedi animations.css per la definizione dell'animazione.
  return (
    <ViewTransition default="slow-fade">
      {url === '/' ? <Home /> : <Details />}
    </ViewTransition>
  );
}

Consulta Styling View Transitions per una guida completa sullo styling di <ViewTransition>.

Shared Element Transitions

Quando due pagine includono lo stesso elemento, spesso vuoi animarlo da una pagina all’altra.

Per farlo puoi aggiungere un name univoco al <ViewTransition>:

<ViewTransition name={`video-${video.id}`}>
<Thumbnail video={video} />
</ViewTransition>

Ora la miniatura del video si anima tra le due pagine:

import { useState, ViewTransition } from "react"; import LikeButton from "./LikeButton"; import { useRouter } from "./router"; import { PauseIcon, PlayIcon } from "./Icons"; import { startTransition } from "react";

export function Thumbnail({ video, children }) {
  // Aggiungi un name per animare con una shared element transition.
  // Usa l'animazione predefinita, nessun css aggiuntivo necessario.
  return (
    <ViewTransition name={`video-${video.id}`}>
      <div
        aria-hidden="true"
        tabIndex={-1}
        className={`thumbnail ${video.image}`}
      >
        {children}
      </div>
    </ViewTransition>
  );
}

export function VideoControls() {
  const [isPlaying, setIsPlaying] = useState(false);

  return (
    <span
      className="controls"
      onClick={() =>
        startTransition(() => {
          setIsPlaying((p) => !p);
        })
      }
    >
      {isPlaying ? <PauseIcon /> : <PlayIcon />}
    </span>
  );
}

export function Video({ video }) {
  const { navigate } = useRouter();

  return (
    <div className="video">
      <div
        className="link"
        onClick={(e) => {
          e.preventDefault();
          navigate(`/video/${video.id}`);
        }}
      >
        <Thumbnail video={video}></Thumbnail>

        <div className="info">
          <div className="video-title">{video.title}</div>
          <div className="video-description">{video.description}</div>
        </div>
      </div>
      <LikeButton video={video} />
    </div>
  );
}

Per impostazione predefinita, React genera automaticamente un name univoco per ogni elemento attivato per una transizione (vedi Come funziona <ViewTransition>). Quando React vede una transizione in cui un <ViewTransition> con un name viene rimosso e un nuovo <ViewTransition> con lo stesso name viene aggiunto, attiverà una shared element transition.

Per maggiori informazioni, consulta la documentazione per Animating a Shared Element.

Animare in base alla causa

A volte, potresti voler animare gli elementi in modo diverso in base a come è stata attivata la transizione. Per questo caso d’uso, abbiamo aggiunto una nuova API chiamata addTransitionType per specificare la causa di una transizione:

function navigate(url) {
startTransition(() => {
// Transition type per la causa "nav forward"
addTransitionType('nav-forward');
go(url);
});
}
function navigateBack(url) {
startTransition(() => {
// Transition type per la causa "nav backward"
addTransitionType('nav-back');
go(url);
});
}

Con i transition type, puoi fornire animazioni personalizzate tramite props a <ViewTransition>. Aggiungiamo una shared element transition all’header per “6 video” e “Indietro”:

<ViewTransition
name="nav"
share={{
'nav-forward': 'slide-forward',
'nav-back': 'slide-back',
}}>
{heading}
</ViewTransition>

Qui passiamo una prop share per definire come animare in base al transition type. Quando la share transition si attiva da nav-forward, viene applicata la view transition class slide-forward. Quando proviene da nav-back, si attiva l’animazione slide-back. Definiamole in CSS:

::view-transition-old(.slide-forward) {
/* quando si scorre in avanti, la pagina "old" dovrebbe uscire verso sinistra. */
animation: ...
}

::view-transition-new(.slide-forward) {
/* quando si scorre in avanti, la pagina "new" dovrebbe entrare da destra. */
animation: ...
}

::view-transition-old(.slide-back) {
/* quando si scorre indietro, la pagina "old" dovrebbe uscire verso destra. */
animation: ...
}

::view-transition-new(.slide-back) {
/* quando si scorre indietro, la pagina "new" dovrebbe entrare da sinistra. */
animation: ...
}

Ora possiamo animare l’header insieme alla miniatura in base al tipo di navigazione:

import {ViewTransition} from 'react'; import { useIsNavPending } from "./router";

export default function Page({ heading, children }) {
  const isPending = useIsNavPending();
  return (
    <div className="page">
      <div className="top">
        <div className="top-nav">
          {/* Classi personalizzate in base al transition type. */}
          <ViewTransition
            name="nav"
            share={{
              'nav-forward': 'slide-forward',
              'nav-back': 'slide-back',
            }}>
            {heading}
          </ViewTransition>
          {isPending && <span className="loader"></span>}
        </div>
      </div>
      {/* Escludi ViewTransition per il contenuto. */}
      {/* Il contenuto può definire la propria ViewTransition. */}
      <ViewTransition default="none">
        <div className="bottom">
          <div className="content">{children}</div>
        </div>
      </ViewTransition>
    </div>
  );
}

Animare i boundary Suspense

Anche Suspense attiverà le View Transitions.

Per animare il passaggio dal fallback al contenuto, possiamo avvolgere Suspense con <ViewTransition>:

<ViewTransition>
<Suspense fallback={<VideoInfoFallback />}>
<VideoInfo />
</Suspense>
</ViewTransition>

Aggiungendolo, il fallback farà cross-fade nel contenuto. Clicca un video e guarda le info del video animarsi:

import { use, Suspense, ViewTransition } from "react"; import { fetchVideo, fetchVideoDetails } from "./data"; import { Thumbnail, VideoControls } from "./Videos"; import { useRouter } from "./router"; import Layout from "./Layout"; import { ChevronLeft } from "./Icons";

function VideoDetails({ id }) {
  // Cross-fade dal fallback al contenuto.
  return (
    <ViewTransition default="slow-fade">
      <Suspense fallback={<VideoInfoFallback />}>
          <VideoInfo id={id} />
      </Suspense>
    </ViewTransition>
  );
}

function VideoInfoFallback() {
  return (
    <div>
      <div className="fit fallback title"></div>
      <div className="fit fallback description"></div>
    </div>
  );
}

export default function Details() {
  const { url, navigateBack } = useRouter();
  const videoId = url.split("/").pop();
  const video = use(fetchVideo(videoId));

  return (
    <Layout
      heading={
        <div
          className="fit back"
          onClick={() => {
            navigateBack("/");
          }}
        >
          <ChevronLeft /> Indietro
        </div>
      }
    >
      <div className="details">
        <Thumbnail video={video} large>
          <VideoControls />
        </Thumbnail>
        <VideoDetails id={video.id} />
      </div>
    </Layout>
  );
}

function VideoInfo({ id }) {
  const details = use(fetchVideoDetails(id));
  return (
    <div>
      <p className="fit info-title">{details.title}</p>
      <p className="fit info-description">{details.description}</p>
    </div>
  );
}

Possiamo anche fornire animazioni personalizzate usando un exit sul fallback e un enter sul contenuto:

<Suspense
fallback={
<ViewTransition exit="slide-down">
<VideoInfoFallback />
</ViewTransition>
}
>
<ViewTransition enter="slide-up">
<VideoInfo id={id} />
</ViewTransition>
</Suspense>

Ecco come definiamo slide-down e slide-up con CSS:

::view-transition-old(.slide-down) {
/* Fai scorrere il fallback verso il basso */
animation: ...;
}

::view-transition-new(.slide-up) {
/* Fai scorrere il contenuto verso l'alto */
animation: ...;
}

Ora, il contenuto Suspense sostituisce il fallback con un’animazione a scorrimento:

import { use, Suspense, ViewTransition } from "react"; import { fetchVideo, fetchVideoDetails } from "./data"; import { Thumbnail, VideoControls } from "./Videos"; import { useRouter } from "./router"; import Layout from "./Layout"; import { ChevronLeft } from "./Icons";

function VideoDetails({ id }) {
  return (
    <Suspense
      fallback={
        // Anima il fallback verso il basso.
        <ViewTransition exit="slide-down">
          <VideoInfoFallback />
        </ViewTransition>
      }
    >
      {/* Anima il contenuto verso l'alto */}
      <ViewTransition enter="slide-up">
        <VideoInfo id={id} />
      </ViewTransition>
    </Suspense>
  );
}

function VideoInfoFallback() {
  return (
    <>
      <div className="fallback title"></div>
      <div className="fallback description"></div>
    </>
  );
}

export default function Details() {
  const { url, navigateBack } = useRouter();
  const videoId = url.split("/").pop();
  const video = use(fetchVideo(videoId));

  return (
    <Layout
      heading={
        <div
          className="fit back"
          onClick={() => {
            navigateBack("/");
          }}
        >
          <ChevronLeft /> Indietro
        </div>
      }
    >
      <div className="details">
        <Thumbnail video={video} large>
          <VideoControls />
        </Thumbnail>
        <VideoDetails id={video.id} />
      </div>
    </Layout>
  );
}

function VideoInfo({ id }) {
  const details = use(fetchVideoDetails(id));
  return (
    <>
      <p className="info-title">{details.title}</p>
      <p className="info-description">{details.description}</p>
    </>
  );
}

Animare le liste

Puoi anche usare <ViewTransition> per animare liste di elementi mentre si riordinano, come in una lista di elementi ricercabile:

<div className="videos">
{filteredVideos.map((video) => (
<ViewTransition key={video.id}>
<Video video={video} />
</ViewTransition>
))}
</div>

Per attivare la ViewTransition, possiamo usare useDeferredValue:

const [searchText, setSearchText] = useState('');
const deferredSearchText = useDeferredValue(searchText);
const filteredVideos = filterVideos(videos, deferredSearchText);

Ora gli elementi si animano mentre digiti nella barra di ricerca:

import { useId, useState, use, useDeferredValue, ViewTransition } from "react";import { Video } from "./Videos";import Layout from "./Layout";import { fetchVideos } from "./data";import { IconSearch } from "./Icons";

function SearchList({searchText, videos}) {
  // Attiva con useDeferredValue ("when")
  const deferredSearchText = useDeferredValue(searchText);
  const filteredVideos = filterVideos(videos, deferredSearchText);
  return (
    <div className="video-list">
      <div className="videos">
        {filteredVideos.map((video) => (
          // Anima ogni elemento nella lista ("what")
          <ViewTransition key={video.id}>
            <Video video={video} />
          </ViewTransition>
        ))}
      </div>
      {filteredVideos.length === 0 && (
        <div className="no-results">Nessun risultato</div>
      )}
    </div>
  );
}

export default function Home() {
  const videos = use(fetchVideos());
  const count = videos.length;
  const [searchText, setSearchText] = useState('');

  return (
    <Layout heading={<div className="fit">{count} video</div>}>
      <SearchInput value={searchText} onChange={setSearchText} />
      <SearchList videos={videos} searchText={searchText} />
    </Layout>
  );
}

function SearchInput({ value, onChange }) {
  const id = useId();
  return (
    <form className="search" onSubmit={(e) => e.preventDefault()}>
      <label htmlFor={id} className="sr-only">
        Cerca
      </label>
      <div className="search-input">
        <div className="search-icon">
          <IconSearch />
        </div>
        <input
          type="text"
          id={id}
          placeholder="Cerca"
          value={value}
          onChange={(e) => onChange(e.target.value)}
        />
      </div>
    </form>
  );
}

function filterVideos(videos, query) {
  const keywords = query
    .toLowerCase()
    .split(" ")
    .filter((s) => s !== "");
  if (keywords.length === 0) {
    return videos;
  }
  return videos.filter((video) => {
    const words = (video.title + " " + video.description)
      .toLowerCase()
      .split(" ");
    return keywords.every((kw) => words.some((w) => w.includes(kw)));
  });
}

Risultato finale

Aggiungendo pochi componenti <ViewTransition> e poche righe di CSS, siamo riusciti ad aggiungere tutte le animazioni sopra nel risultato finale.

Siamo entusiasti delle View Transitions e pensiamo che ti permetteranno di portare le tue app a un livello superiore. Sono pronte per essere provate oggi nel canale sperimentale delle release React.

Rimuoviamo il slow fade e diamo un’occhiata al risultato finale:

import {ViewTransition} from 'react'; import Details from './Details'; import Home from './Home'; import {useRouter} from './router';

export default function App() {
  const {url} = useRouter();

  // Anima con un cross-fade tra le pagine.
  return (
    <ViewTransition key={url}>
      {url === '/' ? <Home /> : <Details />}
    </ViewTransition>
  );
}

Se vuoi saperne di più su come funzionano, consulta Come funziona <ViewTransition> nella documentazione.

Per maggiori dettagli su come abbiamo costruito le View Transitions, vedi: #31975, #32105, #32041, #32734, #32797 #31999, #32031, #32050, #32820, #32029, #32028, e #32038 di @sebmarkbage (grazie Seb!).


Activity

Nota bene

<Activity /> è ora disponibile nel canale Canary di React.

Scopri di più sui canali di release di React qui.

In passati aggiornamenti, abbiamo condiviso che stavamo ricercando un’API per permettere ai componenti di essere nascosti visivamente e deprioritizzati, preservando lo state UI con costi di performance ridotti rispetto allo smontaggio o al nascondere con CSS.

Siamo pronti a condividere l’API e come funziona, così puoi iniziare a testarla nelle versioni sperimentali di React.

<Activity> è un nuovo componente per nascondere e mostrare parti dell’UI:

<Activity mode={isVisible ? 'visible' : 'hidden'}>
<Page />
</Activity>

Quando un’Activity è visible viene renderizzata normalmente. Quando un’Activity è hidden viene smontata, ma salverà il suo state e continuerà a renderizzare con priorità più bassa rispetto a qualsiasi cosa visibile sullo schermo.

Puoi usare Activity per salvare lo state per parti dell’UI che l’utente non sta usando, o pre-renderizzare parti che l’utente probabilmente userà dopo.

Diamo un’occhiata ad alcuni esempi che migliorano gli esempi View Transition sopra.

Nota bene

Gli Effetti non si montano quando un’Activity è hidden.

Quando un <Activity> è hidden, gli Effetti vengono smontati. Concettualmente, il componente è smontato, ma React salva lo state per dopo.

In pratica, funziona come previsto se hai seguito la guida You Might Not Need an Effect. Per trovare subito Effetti problematici, ti consigliamo di aggiungere <StrictMode> che eseguirà smontaggi e montaggi di Activity in anticipo per individuare effetti collaterali inaspettati.

Ripristinare lo state con Activity

Quando un utente naviga via da una pagina, è comune smettere di renderizzare la vecchia pagina:

function App() {
const { url } = useRouter();

return (
<>
{url === '/' && <Home />}
{url !== '/' && <Details />}
</>
);
}

Tuttavia, questo significa che se l’utente torna alla vecchia pagina, tutto lo state precedente viene perso. Per esempio, se la pagina <Home /> ha un campo <input>, quando l’utente lascia la pagina l’<input> viene smontato e tutto il testo digitato viene perso.

Activity ti permette di mantenere lo state mentre l’utente cambia pagina, così quando torna può riprendere da dove aveva lasciato. Si fa avvolgendo parte dell’albero in <Activity> e cambiando la mode:

function App() {
const { url } = useRouter();

return (
<>
<Activity mode={url === '/' ? 'visible' : 'hidden'}>
<Home />
</Activity>
{url !== '/' && <Details />}
</>
);
}

Con questa modifica, possiamo migliorare l’esempio View Transitions sopra. Prima, quando cercavi un video, ne selezionavi uno e tornavi indietro, il filtro di ricerca veniva perso. Con Activity, il filtro di ricerca viene ripristinato e puoi riprendere da dove avevi lasciato.

Prova a cercare un video, selezionarlo e cliccare “indietro”:

import { Activity, ViewTransition } from "react"; import Details from "./Details"; import Home from "./Home"; import { useRouter } from "./router";

export default function App() {
  const { url } = useRouter();

  return (
    // Le View Transitions conoscono Activity
    <ViewTransition>
      {/* Renderizza Home in Activity così non perdiamo lo state */}
      <Activity mode={url === '/' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
      {url !== '/' && <Details />}
    </ViewTransition>
  );
}

Pre-renderizzare con Activity

A volte, potresti voler preparare in anticipo la prossima parte dell’UI che l’utente probabilmente userà, così è pronta quando ne ha bisogno. È particolarmente utile se la prossima route deve sospendere sui dati necessari per renderizzare, perché puoi assicurarti che i dati siano già stati recuperati prima che l’utente navighi.

Per esempio, la nostra app attualmente deve sospendere per caricare i dati di ogni video quando ne selezioni uno. Possiamo migliorare renderizzando tutte le pagine in un <Activity> hidden finché l’utente non naviga:

<ViewTransition>
<Activity mode={url === '/' ? 'visible' : 'hidden'}>
<Home />
</Activity>
<Activity mode={url === '/details/1' ? 'visible' : 'hidden'}>
<Details id={id} />
</Activity>
<Activity mode={url === '/details/1' ? 'visible' : 'hidden'}>
<Details id={id} />
</Activity>
<ViewTransition>

Con questo aggiornamento, se il contenuto della pagina successiva ha tempo per pre-renderizzare, si animerà senza il fallback Suspense. Clicca un video e nota che il titolo e la descrizione del video nella pagina Details vengono renderizzati immediatamente, senza fallback:

import { Activity, ViewTransition, use } from "react"; import Details from "./Details"; import Home from "./Home"; import { useRouter } from "./router"; import {fetchVideos} from './data';

export default function App() {
  const { url } = useRouter();
  const videoId = url.split("/").pop();
  const videos = use(fetchVideos());

  return (
    <ViewTransition>
      {/* Renderizza i video in Activity per pre-renderizzarli */}
      {videos.map(({id}) => (
        <Activity key={id} mode={videoId === id ? 'visible' : 'hidden'}>
          <Details id={id}/>
        </Activity>
      ))}
      <Activity mode={url === '/' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
    </ViewTransition>
  );
}

Server-Side Rendering con Activity

Quando usi Activity su una pagina che usa server-side rendering (SSR), ci sono ottimizzazioni aggiuntive.

Se parte della pagina viene renderizzata con mode="hidden", non sarà inclusa nella risposta SSR. Invece, React programmerà una renderizzazione client per il contenuto dentro Activity mentre il resto della pagina si idrata, dando priorità al contenuto visibile sullo schermo.

Per le parti dell’UI renderizzate con mode="visible", React deprioritizzerà l’hydration del contenuto dentro Activity, simile a come il contenuto Suspense viene idratato con priorità più bassa. Se l’utente interagisce con la pagina, daremo priorità all’hydration dentro il boundary se necessario.

Sono casi d’uso avanzati, ma mostrano i benefici aggiuntivi considerati con Activity.

Modalità future per Activity

In futuro, potremmo aggiungere altre modalità ad Activity.

Per esempio, un caso d’uso comune è renderizzare un modal, dove la pagina “inattiva” precedente è visibile dietro la vista modal “attiva”. La modalità “hidden” non funziona per questo caso d’uso perché non è visibile e non è inclusa nell’SSR.

Invece, stiamo considerando una nuova modalità che manterrebbe il contenuto visibile—e incluso nell’SSR—ma lo terrebbe smontato e deprioritizzerebbe gli aggiornamenti. Questa modalità potrebbe anche dover “mettere in pausa” gli aggiornamenti DOM, poiché può distrarre vedere contenuto in background aggiornarsi mentre un modal è aperto.

Un’altra modalità che stiamo considerando per Activity è la possibilità di distruggere automaticamente lo state per le Activity hidden se viene usata troppa memoria. Poiché il componente è già smontato, potrebbe essere preferibile distruggere lo state per le parti hidden meno recentemente usate dell’app piuttosto che consumare troppe risorse.

Sono aree che stiamo ancora esplorando e condivideremo di più man mano che progrediamo. Per maggiori informazioni su cosa include Activity oggi, consulta la documentazione.


Funzionalità in sviluppo

Stiamo anche sviluppando funzionalità per aiutare a risolvere i problemi comuni sotto.

Mentre iteriamo sulle possibili soluzioni, potresti vedere alcune API potenziali che stiamo testando condivise in base alle PR che stiamo mergiando. Tieni presente che mentre proviamo idee diverse, spesso cambiamo o rimuoviamo soluzioni diverse dopo averle testate.

Quando le soluzioni su cui stiamo lavorando vengono condivise troppo presto, possono creare churn e confusione nella community. Per bilanciare trasparenza e limitare la confusione, condividiamo i problemi per cui stiamo attualmente sviluppando soluzioni, senza condividere una soluzione particolare che abbiamo in mente.

Man mano che queste funzionalità progrediscono, le annunceremo sul blog con documentazione inclusa così puoi provarle.

React Performance Tracks

Stiamo lavorando a un nuovo set di track personalizzate per i profiler di performance usando API browser che permettono di aggiungere track personalizzate per fornire più informazioni sulla performance della tua app React.

Questa funzionalità è ancora in corso, quindi non siamo pronti a pubblicare documentazione per rilasciarla completamente come funzionalità sperimentale. Puoi avere un’anteprima usando una versione sperimentale di React, che aggiungerà automaticamente le performance track ai profili:

Ci sono alcuni problemi noti che prevediamo di affrontare come la performance, e la scheduler track che non sempre “collega” il lavoro tra alberi Suspended, quindi non è ancora pronta da provare. Stiamo anche raccogliendo feedback dagli early adopter per migliorare design e usabilità delle track.

Una volta risolti questi problemi, pubblicheremo documentazione sperimentale e condivideremo che è pronta da provare.


Dipendenze automatiche degli Effetti

Quando abbiamo rilasciato gli hooks, avevamo tre motivazioni:

  • Condividere codice tra componenti: gli hooks hanno sostituito pattern come render props e higher-order component per permetterti di riutilizzare logica con state senza cambiare la gerarchia dei componenti.
  • Pensare in termini di funzione, non di lifecycle: gli hooks ti permettono di dividere un componente in funzioni più piccole in base a quali pezzi sono correlati (come impostare una sottoscrizione o recuperare dati), piuttosto che forzare una divisione basata sui metodi lifecycle.
  • Supportare la compilazione ahead-of-time: gli hooks sono stati progettati per supportare la compilazione ahead-of-time con meno insidie che causano de-ottimizzazioni involontarie causate dai metodi lifecycle e dalle limitazioni delle classi.

Dal loro rilascio, gli hooks hanno avuto successo nel condividere codice tra componenti. Gli hooks sono ora il modo preferito per condividere logica tra componenti, e ci sono meno casi d’uso per render props e higher order component. Gli hooks hanno anche avuto successo nel supportare funzionalità come Fast Refresh che non erano possibili con i componenti classe.

Gli Effetti possono essere difficili

Purtroppo, alcuni hooks sono ancora difficili da pensare in termini di funzione invece che di lifecycle. Gli Effetti in particolare sono ancora difficili da capire e sono il pain point più comune che sentiamo dagli sviluppatori. L’anno scorso, abbiamo dedicato molto tempo a ricercare come venivano usati gli Effetti e come quei casi d’uso potessero essere semplificati e resi più facili da capire.

Abbiamo scoperto che spesso la confusione deriva dall’usare un Effetto quando non ne hai bisogno. La guida You Might Not Need an Effect copre molti casi in cui gli Effetti non sono la soluzione giusta. Tuttavia, anche quando un Effetto è adatto a un problema, gli Effetti possono essere ancora più difficili da capire rispetto ai lifecycle dei componenti classe.

Crediamo che una delle ragioni della confusione sia che gli sviluppatori pensano agli Effetti dalla prospettiva del componente (come un lifecycle), invece che dal punto di vista degli Effetti (cosa fa l’Effetto).

Diamo un’occhiata a un esempio dalla documentazione:

useEffect(() => {
// Il tuo Effetto si connette alla stanza specificata con roomId...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
// ...finché non si disconnette
connection.disconnect();
};
}, [roomId]);

Molti utenti leggerebbero questo codice come “al mount, connettiti al roomId. ogni volta che roomId cambia, disconnettiti dalla vecchia stanza e ricrea la connessione”. Tuttavia, questo è pensare dalla prospettiva del lifecycle del componente, il che significa che dovrai pensare a ogni fase del lifecycle del componente per scrivere l’Effetto correttamente. Può essere difficile, quindi è comprensibile che gli Effetti sembrino più difficili dei lifecycle di classe quando usi la prospettiva del componente.

Effetti senza dipendenze

Invece, è meglio pensare dalla prospettiva dell’Effetto. L’Effetto non conosce i lifecycle del componente. Descrive solo come avviare la sincronizzazione e come fermarla. Quando gli utenti pensano agli Effetti in questo modo, i loro Effetti tendono ad essere più facili da scrivere e più resilienti ad essere avviati e fermati quante volte serve.

Abbiamo dedicato del tempo a ricercare perché gli Effetti vengono pensati dalla prospettiva del componente, e pensiamo che una delle ragioni sia l’array di dipendenze. Poiché devi scriverlo, è lì davanti a te ricordandoti a cosa stai “reagendo” e spingendoti verso il modello mentale di ‘fai questo quando questi valori cambiano’.

Quando abbiamo rilasciato gli hooks, sapevamo di poterli rendere più facili da usare con la compilazione ahead-of-time. Con React Compiler, ora puoi evitare di scrivere useCallback e useMemo da solo nella maggior parte dei casi. Per gli Effetti, il compiler può inserire le dipendenze per te:

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}); // dipendenze inserite dal compiler.

Con questo codice, React Compiler può inferire le dipendenze per te e inserirle automaticamente così non devi vederle o scriverle. Con funzionalità come l’estensione IDE e useEffectEvent, possiamo fornire un CodeLens per mostrarti cosa ha inserito il Compiler quando devi fare debug, o per ottimizzare rimuovendo una dipendenza. Questo aiuta a rafforzare il modello mentale corretto per scrivere Effetti, che possono essere eseguiti in qualsiasi momento per sincronizzare lo state del componente o dell’hook con qualcos’altro.

La nostra speranza è che inserire automaticamente le dipendenze non sia solo più facile da scrivere, ma che renda anche gli Effetti più facili da capire costringendoti a pensare in termini di cosa fa l’Effetto, e non in lifecycle del componente.


Compiler IDE Extension

Più avanti nel 2025 abbiamo condiviso il primo rilascio stabile di React Compiler, e continuiamo a investire nel rilasciare altri miglioramenti.

Abbiamo anche iniziato a esplorare modi per usare React Compiler per fornire informazioni che possono migliorare la comprensione e il debug del tuo codice. Un’idea che abbiamo iniziato a esplorare è una nuova estensione IDE React sperimentale basata su LSP alimentata da React Compiler, simile all’estensione usata nel talk di Lauren Tan a React Conf.

La nostra idea è che possiamo usare l’analisi statica del compiler per fornire più informazioni, suggerimenti e opportunità di ottimizzazione direttamente nel tuo IDE. Per esempio, possiamo fornire diagnostiche per codice che viola le Rules of React, hover per mostrare se componenti e hooks sono stati ottimizzati dal compiler, o un CodeLens per vedere le dipendenze degli Effetti inserite automaticamente.

L’estensione IDE è ancora un’esplorazione iniziale, ma condivideremo i progressi negli aggiornamenti futuri.


Fragment Refs

Molte API DOM come quelle per la gestione degli eventi, il posizionamento e il focus sono difficili da comporre quando scrivi con React. Questo spesso porta gli sviluppatori a usare Effetti, gestendo più Ref, usando API come findDOMNode (rimossa in React 19).

Stiamo esplorando l’aggiunta di ref ai Fragment che puntino a un gruppo di elementi DOM, piuttosto che a un singolo elemento. La nostra speranza è che questo semplifichi la gestione di più figli e renda più facile scrivere codice React componibile quando chiami API DOM.

I fragment ref sono ancora in fase di ricerca. Condivideremo di più quando saremo più vicini ad avere l’API finale completata.


Animazioni gesture

Stiamo anche ricercando modi per migliorare le View Transitions per supportare animazioni gesture come scorrere per aprire un menu o scorrere un carosello di foto.

I gesture presentano nuove sfide per alcuni motivi:

  • I gesture sono continui: mentre scorri, l’animazione è legata al posizionamento del dito nel tempo, piuttosto che attivarsi ed eseguirsi fino al completamento.
  • I gesture non si completano: quando rilasci il dito, le animazioni gesture possono eseguirsi fino al completamento o tornare al loro state originale (come quando apri solo parzialmente un menu) a seconda di quanto vai avanti.
  • I gesture invertono old e new: mentre animi, vuoi che la pagina da cui stai animando resti “viva” e interattiva. Questo inverte il modello View Transition del browser dove lo state “old” è un’istantanea e lo state “new” è il DOM live.

Crediamo di aver trovato un approccio che funziona bene e potremmo introdurre una nuova API per attivare gesture transition. Per ora, siamo concentrati sul rilasciare <ViewTransition>, e torneremo sui gesture dopo.


Concurrent Stores

Quando abbiamo rilasciato React 18 con concurrent rendering, abbiamo anche rilasciato useSyncExternalStore così le librerie store esterne che non usavano React state o context potevano supportare concurrent rendering forzando una renderizzazione sincrona quando lo store viene aggiornato.

Usare useSyncExternalStore ha però un costo, poiché forza un bail out dalle funzionalità concurrent come le transizioni, e forza il contenuto esistente a mostrare fallback Suspense.

Ora che React 19 è stato rilasciato, stiamo rivisitando questo spazio problematico per creare un primitivo che supporti completamente store esterni concurrent con l’API use:

const value = use(store);

Il nostro obiettivo è permettere di leggere state esterno durante la renderizzazione senza tearing, e funzionare perfettamente con tutte le funzionalità concurrent che React offre.

Questa ricerca è ancora agli inizi. Condivideremo di più, e come saranno le nuove API, quando saremo più avanti.


Grazie a Aurora Scharff, Dan Abramov, Eli White, Lauren Tan, Luna Wei, Matt Carroll, Jack Pope, Jason Bonta, Jordan Brown, Jordan Eldredge, Mofei Zhang, Sebastien Lorber, Sebastian Markbåge, e Tim Yung per la revisione di questo post.