Guida all'upgrade a React 19

25 aprile 2024 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.

I miglioramenti aggiunti in React 19 richiedono alcune breaking change, ma abbiamo lavorato per rendere l’upgrade il più fluido possibile e non ci aspettiamo che le modifiche impattino la maggior parte delle app.

Nota bene

Anche React 18.3 è stato pubblicato

Per facilitare l’upgrade a React 19, abbiamo pubblicato un rilascio react@18.3 identico a 18.2 ma con warning per API deprecate e altre modifiche necessarie per React 19.

Consigliamo di aggiornare prima a React 18.3 per individuare eventuali problemi prima di passare a React 19.

Per l’elenco delle modifiche in 18.3 consulta le Release Notes.

In questo post ti guidiamo nei passaggi per l’upgrade a React 19:

Se vuoi aiutarci a testare React 19, segui i passaggi in questa guida all’upgrade e segnala eventuali problemi che incontri. Per l’elenco delle nuove funzionalità aggiunte in React 19, consulta il post di rilascio di React 19.


Installazione

Nota bene

La nuova JSX Transform è ora obbligatoria

Abbiamo introdotto una nuova JSX transform nel 2020 per migliorare la dimensione del bundle e usare JSX senza importare React. In React 19 aggiungiamo ulteriori miglioramenti come l’uso di ref come prop e miglioramenti di velocità JSX che richiedono la nuova transform.

Se la nuova transform non è abilitata, vedrai questo warning:

Console
Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance: https://react.dev/link/new-jsx-transform

Ci aspettiamo che la maggior parte delle app non sia interessata poiché la transform è già abilitata nella maggior parte degli ambienti. Per istruzioni manuali su come aggiornare, consulta il post di annuncio.

Per installare l’ultima versione di React e React DOM:

npm install --save-exact react@^19.0.0 react-dom@^19.0.0

Oppure, se usi Yarn:

yarn add --exact react@^19.0.0 react-dom@^19.0.0

Se usi TypeScript, devi anche aggiornare i tipi.

npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0

Oppure, se usi Yarn:

yarn add --exact @types/react@^19.0.0 @types/react-dom@^19.0.0

Includiamo anche un codemod per le sostituzioni più comuni. Vedi Modifiche TypeScript sotto.

Codemod

Per aiutare con l’upgrade, abbiamo collaborato con il team di codemod.com per pubblicare codemod che aggiorneranno automaticamente il tuo codice a molte delle nuove API e pattern in React 19.

Tutti i codemod sono disponibili nel react-codemod repo e il team Codemod ha contribuito a mantenere i codemod. Per eseguire questi codemod, consigliamo di usare il comando codemod invece di react-codemod perché è più veloce, gestisce migrazioni di codice più complesse e offre un supporto TypeScript migliore.

Nota bene

Esegui tutti i codemod React 19

Esegui tutti i codemod elencati in questa guida con la recipe codemod React 19:

npx codemod@latest react/19/migration-recipe

Questo eseguirà i seguenti codemod da react-codemod:

Questo non include le modifiche TypeScript. Vedi Modifiche TypeScript sotto.

Le modifiche che includono un codemod includono il comando sotto.

Per l’elenco di tutti i codemod disponibili, consulta il react-codemod repo.

Breaking change

Gli errori in render non vengono rilanciati

Nelle versioni precedenti di React, gli errori lanciati durante il render venivano catturati e rilanciati. In DEV, registravamo anche in console.error, con log di errore duplicati.

In React 19, abbiamo migliorato la gestione degli errori per ridurre la duplicazione senza rilanciare:

  • Errori non catturati: gli errori non catturati da un contenitore di errori vengono segnalati a window.reportError.
  • Errori catturati: gli errori catturati da un contenitore di errori vengono segnalati a console.error.

Questa modifica non dovrebbe impattare la maggior parte delle app, ma se la segnalazione errori in produzione si basa sul rilancio degli errori, potresti dover aggiornare la gestione errori. Per supportarlo, abbiamo aggiunto nuovi metodi a createRoot e hydrateRoot per la gestione errori personalizzata:

const root = createRoot(container, {
onUncaughtError: (error, errorInfo) => {
// ... log error report
},
onCaughtError: (error, errorInfo) => {
// ... log error report
}
});

Per maggiori informazioni, consulta la documentazione di createRoot e hydrateRoot.

Rimosse API React deprecate

Rimosso: propTypes e defaultProps per le funzioni

PropTypes sono stati deprecati in April 2017 (v15.5.0).

In React 19 rimuoviamo i controlli propType dal pacchetto React e usarli verrà ignorato silenziosamente. Se usi propTypes, consigliamo di migrare a TypeScript o un’altra soluzione di type checking.

Rimuoviamo anche defaultProps dai componenti funzione in favore dei parametri default ES6. I componenti classe continueranno a supportare defaultProps poiché non esiste alternativa ES6.

// Before
import PropTypes from 'prop-types';

function Heading({text}) {
return <h1>{text}</h1>;
}
Heading.propTypes = {
text: PropTypes.string,
};
Heading.defaultProps = {
text: 'Hello, world!',
};
// After
interface Props {
text?: string;
}
function Heading({text = 'Hello, world!'}: Props) {
return <h1>{text}</h1>;
}

Nota bene

Codemod propTypes verso TypeScript con:

npx codemod@latest react/prop-types-typescript

Rimosso: Legacy Context con contextTypes e getChildContext

Legacy Context è stato deprecato in October 2018 (v16.6.0).

Legacy Context era disponibile solo nei componenti classe usando le API contextTypes e getChildContext, ed è stato sostituito con contextType per bug sottili facili da non notare. In React 19 rimuoviamo Legacy Context per rendere React leggermente più piccolo e veloce.

Se usi ancora Legacy Context nei componenti classe, dovrai migrare alla nuova API contextType:

// Before
import PropTypes from 'prop-types';

class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};

getChildContext() {
return { foo: 'bar' };
}

render() {
return <Child />;
}
}

class Child extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
};

render() {
return <div>{this.context.foo}</div>;
}
}
// After
const FooContext = React.createContext();

class Parent extends React.Component {
render() {
return (
<FooContext value='bar'>
<Child />
</FooContext>
);
}
}

class Child extends React.Component {
static contextType = FooContext;

render() {
return <div>{this.context}</div>;
}
}

Rimosso: string refs

Le string refs sono state deprecate in March, 2018 (v16.3.0).

I componenti classe supportavano string refs prima di essere sostituite da ref callback per vari svantaggi. In React 19 rimuoviamo le string refs per rendere React più semplice e comprensibile.

Se usi ancora string refs nei componenti classe, dovrai migrare alle ref callback:

// Before
class MyComponent extends React.Component {
componentDidMount() {
this.refs.input.focus();
}

render() {
return <input ref='input' />;
}
}
// After
class MyComponent extends React.Component {
componentDidMount() {
this.input.focus();
}

render() {
return <input ref={input => this.input = input} />;
}
}

Nota bene

Codemod delle string refs con ref callback:

npx codemod@latest react/19/replace-string-ref

Rimosso: module pattern factories

Le module pattern factories sono state deprecate in August 2019 (v16.9.0).

Questo pattern era raramente usato e supportarlo rende React leggermente più grande e lento del necessario. In React 19 rimuoviamo il supporto per module pattern factories e dovrai migrare a funzioni regolari:

// Before
function FactoryComponent() {
return { render() { return <div />; } }
}
// After
function FactoryComponent() {
return <div />;
}

Rimosso: React.createFactory

createFactory è stato deprecato in February 2020 (v16.13.0).

Usare createFactory era comune prima del supporto diffuso di JSX, ma oggi è raramente usato e può essere sostituito con JSX. In React 19 rimuoviamo createFactory e dovrai migrare a JSX:

// Before
import { createFactory } from 'react';

const button = createFactory('button');
// After
const button = <button />;

Rimosso: react-test-renderer/shallow

In React 18 abbiamo aggiornato react-test-renderer/shallow per re-esportare react-shallow-renderer. In React 19 rimuoviamo react-test-render/shallow preferendo installare il pacchetto direttamente:

npm install react-shallow-renderer --save-dev
- import ShallowRenderer from 'react-test-renderer/shallow';
+ import ShallowRenderer from 'react-shallow-renderer';

Nota bene

Riconsidera lo shallow rendering

Lo shallow rendering dipende dagli internals di React e può bloccare futuri upgrade. Consigliamo di migrare i test a @testing-library/react o @testing-library/react-native.

Rimosse API React DOM deprecate

Rimosso: react-dom/test-utils

Abbiamo spostato act da react-dom/test-utils al pacchetto react:

Console
ReactDOMTestUtils.act is deprecated in favor of React.act. Import act from react instead of react-dom/test-utils. See https://react.dev/warnings/react-dom-test-utils for more info.

Per correggere questo warning, puoi importare act da react:

- import {act} from 'react-dom/test-utils'
+ import {act} from 'react';

Tutte le altre funzioni test-utils sono state rimosse. Queste utility erano poco comuni e rendevano troppo facile dipendere da dettagli di implementazione a basso livello dei componenti e di React. In React 19, queste funzioni daranno errore quando chiamate e le loro export verranno rimosse in una versione futura.

Consulta la pagina dei warning per le alternative.

Nota bene

Codemod ReactDOMTestUtils.act verso React.act:

npx codemod@latest react/19/replace-act-import

Rimosso: ReactDOM.render

ReactDOM.render è stato deprecato in March 2022 (v18.0.0). In React 19 rimuoviamo ReactDOM.render e dovrai migrare a ReactDOM.createRoot:

// Before
import {render} from 'react-dom';
render(<App />, document.getElementById('root'));

// After
import {createRoot} from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Nota bene

Codemod ReactDOM.render verso ReactDOMClient.createRoot:

npx codemod@latest react/19/replace-reactdom-render

Rimosso: ReactDOM.hydrate

ReactDOM.hydrate è stato deprecato in March 2022 (v18.0.0). In React 19 rimuoviamo ReactDOM.hydrate e dovrai migrare a ReactDOM.hydrateRoot,

// Before
import {hydrate} from 'react-dom';
hydrate(<App />, document.getElementById('root'));

// After
import {hydrateRoot} from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);

Nota bene

Codemod ReactDOM.hydrate verso ReactDOMClient.hydrateRoot:

npx codemod@latest react/19/replace-reactdom-render

Rimosso: unmountComponentAtNode

ReactDOM.unmountComponentAtNode è stato deprecato in March 2022 (v18.0.0). In React 19 dovrai migrare a root.unmount().

// Before
unmountComponentAtNode(document.getElementById('root'));

// After
root.unmount();

Per maggiori informazioni su root.unmount() consulta createRoot e hydrateRoot.

Nota bene

Codemod unmountComponentAtNode verso root.unmount:

npx codemod@latest react/19/replace-reactdom-render

Rimosso: ReactDOM.findDOMNode

ReactDOM.findDOMNode è stato deprecato nell’ottobre 2018 (v16.6.0).

Rimuoviamo findDOMNode perché era un legacy escape hatch lento da eseguire, fragile alla rifattorizzazione, restituiva solo il primo figlio e rompeva i livelli di astrazione (maggiori info qui). Puoi sostituire ReactDOM.findDOMNode con DOM ref:

// Before
import {findDOMNode} from 'react-dom';

function AutoselectingInput() {
useEffect(() => {
const input = findDOMNode(this);
input.select()
}, []);

return <input defaultValue="Hello" />;
}
// After
function AutoselectingInput() {
const ref = useRef(null);
useEffect(() => {
ref.current.select();
}, []);

return <input ref={ref} defaultValue="Hello" />
}

Nuove deprecazioni

Deprecato: element.ref

React 19 supporta ref as a prop, quindi depreciamo element.ref in favore di element.props.ref.

Accedere a element.ref genererà un warning:

Console
Accessing element.ref is no longer supported. ref is now a regular prop. It will be removed from the JSX Element type in a future release.

Deprecato: react-test-renderer

Deprechiamo react-test-renderer perché implementa un proprio ambiente renderer che non corrisponde a quello usato dagli utenti, promuove il test dei dettagli di implementazione e si basa sull’introspezione degli internals di React.

Il test renderer è stato creato prima che fossero disponibili strategie di test più valide come React Testing Library, e ora consigliamo di usare una libreria di test moderna.

In React 19, react-test-renderer registra un warning di deprecazione ed è passato al concurrent rendering. Consigliamo di migrare i test a @testing-library/react o @testing-library/react-native per un’esperienza di test moderna e ben supportata.

Modifiche rilevanti

Modifiche a StrictMode

React 19 include diversi fix e miglioramenti a Strict Mode.

Quando si fa double rendering in Strict Mode in development, useMemo e useCallback riutilizzeranno i risultati memoizzati del primo render durante il secondo render. I componenti già compatibili con Strict Mode non dovrebbero notare differenze nel comportamento.

Come per tutti i comportamenti di Strict Mode, queste funzionalità sono progettate per far emergere proattivamente bug nei componenti durante lo development, così puoi correggerli prima del deploy in produzione. Ad esempio, durante lo development, Strict Mode invocherà due volte le ref callback al mount iniziale, per simulare cosa succede quando un componente montato viene sostituito da un fallback Suspense.

Miglioramenti a Suspense

In React 19, quando un componente sospende, React committa immediatamente il fallback del boundary Suspense più vicino senza aspettare che l’intero albero sibling venga renderizzato. Dopo il commit del fallback, React pianifica un altro render per i sibling sospesi per “pre-riscaldare” le richieste lazy nel resto dell’albero:

Diagramma che mostra un albero di tre componenti, un genitore etichettato Accordion e due figli etichettati Panel. Entrambi i componenti Panel contengono isActive con valore false.
Diagramma che mostra un albero di tre componenti, un genitore etichettato Accordion e due figli etichettati Panel. Entrambi i componenti Panel contengono isActive con valore false.

In precedenza, quando un componente sospendeva, i sibling sospesi venivano renderizzati e poi il fallback veniva committato.

Lo stesso diagramma del precedente, con isActive del primo componente figlio Panel evidenziato indicando un click con isActive impostato a true. Il secondo componente Panel contiene ancora valore false.
Lo stesso diagramma del precedente, con isActive del primo componente figlio Panel evidenziato indicando un click con isActive impostato a true. Il secondo componente Panel contiene ancora valore false.

In React 19, quando un componente sospende, il fallback viene committato e poi i sibling sospesi vengono renderizzati.

Questa modifica significa che i fallback Suspense vengono visualizzati più velocemente, continuando a pre-riscaldare le richieste lazy nell’albero sospeso.

Rimosse build UMD

UMD era ampiamente usato in passato come modo conveniente per caricare React senza un passo di build. Ora esistono alternative moderne per caricare moduli come script nei documenti HTML. A partire da React 19, React non produrrà più build UMD per ridurre la complessità del processo di test e rilascio.

Per caricare React 19 con un tag script, consigliamo di usare una CDN basata su ESM come esm.sh.

<script type="module">
import React from "https://esm.sh/react@19/?dev"
import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev"
...
</script>

Le librerie che dipendono dagli internals di React possono bloccare gli upgrade

Questo rilascio include modifiche agli internals di React che possono impattare librerie che ignorano le nostre richieste di non usare internals come SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED. Queste modifiche sono necessarie per far atterrare i miglioramenti in React 19 e non romperanno librerie che seguono le nostre linee guida.

In base alla nostra Versioning Policy, questi aggiornamenti non sono elencati come breaking change e non includiamo documentazione su come aggiornarli. La raccomandazione è rimuovere qualsiasi codice che dipende dagli internals.

Per riflettere l’impatto dell’uso degli internals, abbiamo rinominato il suffisso SECRET_INTERNALS in:

_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE

In futuro bloccheremo più aggressivamente l’accesso agli internals da React per scoraggiarne l’uso e assicurare che gli utenti non siano bloccati dall’upgrade.

Modifiche TypeScript

Rimossi tipi TypeScript deprecati

Abbiamo ripulito i tipi TypeScript in base alle API rimosse in React 19. Alcuni tipi rimossi sono stati spostati in pacchetti più pertinenti e altri non servono più per descrivere il comportamento di React.

Nota bene

Abbiamo pubblicato types-react-codemod per migrare la maggior parte delle breaking change relative ai tipi:

npx types-react-codemod@latest preset-19 ./path-to-app

Se hai molti accessi unsound a element.props, puoi eseguire questo codemod aggiuntivo:

npx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files

Consulta types-react-codemod per l’elenco delle sostituzioni supportate. Se ritieni che manchi un codemod, può essere tracciato nell’elenco dei codemod React 19 mancanti.

Richieste ref cleanup

Questa modifica è inclusa nel preset codemod react-19 come no-implicit-ref-callback-return.

A causa dell’introduzione delle ref cleanup function, restituire qualsiasi altra cosa da una ref callback verrà ora rifiutato da TypeScript. La correzione di solito consiste nel smettere di usare return impliciti:

- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />

Il codice originale restituiva l’istanza dell’HTMLDivElement e TypeScript non sapeva se doveva essere una cleanup function o no.

useRef richiede un argomento

Questa modifica è inclusa nel preset codemod react-19 come refobject-defaults.

Una lamentela di lunga data su come TypeScript e React lavorano insieme riguardava useRef. Abbiamo cambiato i tipi così che useRef ora richiede un argomento. Questo semplifica significativamente la sua firma di tipo. Ora si comporterà più come createContext.

// @ts-expect-error: Expected 1 argument but saw none
useRef();
// Passes
useRef(undefined);
// @ts-expect-error: Expected 1 argument but saw none
createContext();
// Passes
createContext(undefined);

Questo significa anche che tutte le ref sono mutabili. Non avrai più il problema per cui non puoi mutare una ref perché l’hai inizializzata con null:

const ref = useRef<number>(null);

// Cannot assign to 'current' because it is a read-only property
ref.current = 1;

MutableRef è ora deprecato in favore di un unico tipo RefObject che useRef restituirà sempre:

interface RefObject<T> {
current: T
}

declare function useRef<T>: RefObject<T>

useRef ha ancora un overload di convenienza per useRef<T>(null) che restituisce automaticamente RefObject<T | null>. Per facilitare la migrazione dovuta all’argomento obbligatorio per useRef, è stato aggiunto un overload di convenienza per useRef(undefined) che restituisce automaticamente RefObject<T | undefined>.

Consulta [RFC] Make all refs mutable per discussioni precedenti su questa modifica.

Modifiche al tipo TypeScript ReactElement

Questa modifica è inclusa nel codemod react-element-default-any-props.

Le props degli elementi React ora hanno default unknown invece di any se l’elemento è tipizzato come ReactElement. Questo non ti riguarda se passi un type argument a ReactElement:

type Example2 = ReactElement<{ id: string }>["props"];
// ^? { id: string }

Ma se ti basavi sul default, ora devi gestire unknown:

type Example = ReactElement["props"];
// ^? Before, was 'any', now 'unknown'

Dovresti averne bisogno solo se hai molto codice legacy che si basa su accessi unsound alle props degli elementi. L’introspezione degli elementi esiste solo come escape hatch, e dovresti rendere esplicito che l’accesso alle props è unsound tramite un any esplicito.

Il namespace JSX in TypeScript

Questa modifica è inclusa nel preset codemod react-19 come scoped-jsx

Una richiesta di lunga data è rimuovere il namespace globale JSX dai nostri tipi in favore di React.JSX. Questo aiuta a prevenire l’inquinamento dei tipi globali che previene conflitti tra diverse librerie UI che usano JSX.

Ora dovrai avvolgere l’augmentation del modulo del namespace JSX in `declare module ”…”:

// global.d.ts
+ declare module "react" {
namespace JSX {
interface IntrinsicElements {
"my-element": {
myElementProps: string;
};
}
}
+ }

Lo specifier esatto del modulo dipende dalla JSX runtime specificata nelle compilerOptions del tuo tsconfig.json:

  • Per "jsx": "react-jsx" sarebbe react/jsx-runtime.
  • Per "jsx": "react-jsxdev" sarebbe react/jsx-dev-runtime.
  • Per "jsx": "react" e "jsx": "preserve" sarebbe react.

Tipizzazioni migliori per useReducer

useReducer ora ha un type inference migliorato grazie a @mfp22.

Tuttavia, questo ha richiesto una breaking change in cui useReducer non accetta il tipo reducer completo come type parameter ma invece non ne richiede nessuno (e si affida al contextual typing) o richiede sia lo state che il tipo action.

La nuova best practice è non passare type arguments a useReducer.

- useReducer<React.Reducer<State, Action>>(reducer)
+ useReducer(reducer)

Questo potrebbe non funzionare in edge case dove puoi tipizzare esplicitamente lo state e l’action, passando l’Action in una tupla:

- useReducer<React.Reducer<State, Action>>(reducer)
+ useReducer<State, [Action]>(reducer)

Se definisci il reducer inline, incoraggiamo ad annotare i parametri della funzione invece:

- useReducer<React.Reducer<State, Action>>((state, action) => state)
+ useReducer((state: State, action: Action) => state)

Questo è anche ciò che dovresti fare se sposti il reducer fuori dalla chiamata useReducer:

const reducer = (state: State, action: Action) => state;

Changelog

Altre breaking change

  • react-dom: Errore per URL javascript in src e href #26507
  • react-dom: Rimozione di errorInfo.digest da onRecoverableError #28222
  • react-dom: Rimozione di unstable_flushControlled #26397
  • react-dom: Rimozione di unstable_createEventHandle #28271
  • react-dom: Rimozione di unstable_renderSubtreeIntoContainer #28271
  • react-dom: Rimozione di unstable_runWithPriority #28271
  • react-is: Rimozione di metodi deprecati da react-is 28224

Altre modifiche rilevanti

  • react: Raggruppamento di sync, default e continuous lanes #25700
  • react: Nessun prerender dei sibling di un componente sospeso #26380
  • react: Rilevamento di loop di aggiornamento infiniti causati da aggiornamenti in fase di render #26625
  • react-dom: Le Transitions in popstate sono ora sincrone #26025
  • react-dom: Rimozione del warning sugli Effect di layout durante SSR #26395
  • react-dom: Warning e nessuna impostazione di stringa vuota per src/href (eccetto tag anchor) #28124

Per l’elenco completo delle modifiche, consulta il Changelog.


Grazie a Andrew Clark, Eli White, Jack Pope, Jan Kassens, Josh Story, Matt Carroll, Noah Lemen, Sophie Alpert e Sebastian Silbermann per la revisione e l’editing di questo post.