React 19 升級指南
2024年4月25日 作者:Ricky Hanlon
React 19 新增的改進需要一些重大更改,但我們已努力使升級儘可能順利,並且我們預計這些更改不會影響大多數應用程式。
在這篇文章中,我們將指導您完成升級到 React 19 的步驟
如果您想幫助我們測試 React 19,請按照此升級指南中的步驟操作,並報告您遇到的任何問題。有關新增到 React 19 的新功能列表,請參閱 React 19 發行文章。
安裝
安裝最新版本的 React 和 React DOM
npm install --save-exact react@^19.0.0 react-dom@^19.0.0
或者,如果您使用的是 Yarn
yarn add --exact react@^19.0.0 react-dom@^19.0.0
如果您使用的是 TypeScript,則還需要更新型別。
npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0
或者,如果您使用的是 Yarn
yarn add --exact @types/react@^19.0.0 @types/react-dom@^19.0.0
我們還包含了一個程式碼修改工具,用於最常見的替換。請參見下面的 TypeScript 更改。
程式碼修改工具
為了幫助進行升級,我們與 codemod.com 團隊合作,釋出了程式碼修改工具,這些程式碼修改工具將自動將您的程式碼更新到 React 19 中的許多新 API 和模式。
所有程式碼修改工具都可以在 react-codemod
倉庫 中找到,並且 Codemod 團隊也參與了程式碼修改工具的維護。要執行這些程式碼修改工具,我們建議使用 codemod
命令而不是 react-codemod
,因為它執行速度更快,處理更復雜的程式碼遷移,並提供更好的 TypeScript 支援。
包含程式碼修改的更改包括以下命令。
有關所有可用程式碼修改的列表,請參閱react-codemod
程式碼庫。
重大更改
渲染錯誤不再重新丟擲
在之前的React版本中,渲染過程中丟擲的錯誤會被捕獲並重新丟擲。在開發環境中,我們還會記錄到console.error
,導致錯誤日誌重複。
在React 19中,我們改進了錯誤處理方式,透過不重新丟擲錯誤來減少重複。
- 未捕獲的錯誤:未被錯誤邊界捕獲的錯誤將報告給
window.reportError
。 - 已捕獲的錯誤:被錯誤邊界捕獲的錯誤將報告給
console.error
。
此更改不應影響大多數應用程式,但如果您的生產錯誤報告依賴於重新丟擲的錯誤,則可能需要更新您的錯誤處理。為了支援這一點,我們在createRoot
和hydrateRoot
中添加了新的錯誤處理方法。
const root = createRoot(container, {
onUncaughtError: (error, errorInfo) => {
// ... log error report
},
onCaughtError: (error, errorInfo) => {
// ... log error report
}
});
更多資訊,請參閱createRoot
和hydrateRoot
的文件。
已移除棄用的React API
已移除:函式的propTypes
和defaultProps
PropTypes
已於2017年4月(v15.5.0)棄用。
在React 19中,我們從React包中移除了propType
檢查,使用它們將被靜默忽略。如果您正在使用propTypes
,我們建議遷移到TypeScript或其他型別檢查解決方案。
我們還將函式元件中的defaultProps
替換為ES6預設引數。類元件將繼續支援defaultProps
,因為沒有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>;
}
已移除:使用contextTypes
和getChildContext
的舊版上下文
舊版上下文已於2018年10月(v16.6.0)棄用。
舊版上下文僅在類元件中使用contextTypes
和getChildContext
API可用,並被contextType
取代,原因是存在一些容易被忽略的細微錯誤。在React 19中,我們移除了舊版上下文,使React更小更快。
如果您仍在類元件中使用舊版上下文,則需要遷移到新的contextType
API。
// 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>;
}
}
已移除:字串ref
字串ref已於2018年3月(v16.3.0)棄用。
類元件在被ref回撥替換之前支援字串ref,原因是存在多個缺點。在React 19中,我們移除了字串ref,使React更簡單易懂。
如果您仍在類元件中使用字串ref,則需要遷移到ref回撥。
// 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} />;
}
}
已移除:模組模式工廠
模組模式工廠已於 2019年8月 (v16.9.0) 被棄用。
這種模式很少使用,並且支援它會導致 React 比必要的大一點且慢一點。在 React 19 中,我們將移除對模組模式工廠的支援,您需要遷移到常規函式。
// Before
function FactoryComponent() {
return { render() { return <div />; } }
}
// After
function FactoryComponent() {
return <div />;
}
已移除: React.createFactory
createFactory
已於 2020年2月 (v16.13.0) 被棄用。
在廣泛支援 JSX 之前,使用 createFactory
很常見,但如今很少使用,可以用 JSX 代替。在 React 19 中,我們將移除 createFactory
,您需要遷移到 JSX。
// Before
import { createFactory } from 'react';
const button = createFactory('button');
// After
const button = <button />;
已移除: react-test-renderer/shallow
在 React 18 中,我們更新了 react-test-renderer/shallow
以重新匯出 react-shallow-renderer。在 React 19 中,我們將移除 react-test-render/shallow
,建議直接安裝該軟體包。
npm install react-shallow-renderer --save-dev
- import ShallowRenderer from 'react-test-renderer/shallow';
+ import ShallowRenderer from 'react-shallow-renderer';
已移除:已棄用的 React DOM API
已移除: react-dom/test-utils
我們已將 act
從 react-dom/test-utils
移動到 react
包中。
ReactDOMTestUtils.act
已被棄用,建議使用 React.act
。從 react
而不是 react-dom/test-utils
匯入 act
。更多資訊請參見 https://reactjs.org.tw/warnings/react-dom-test-utils。要修復此警告,您可以從 react
匯入 act
。
- import {act} from 'react-dom/test-utils'
+ import {act} from 'react';
所有其他 test-utils
函式均已移除。這些實用程式不常用,並且使其過於容易依賴於元件和 React 的底層實現細節。在 React 19 中,呼叫這些函式將導致錯誤,並且它們的匯出將在未來的版本中被移除。
請參閱 警告頁面 以瞭解替代方案。
已移除: ReactDOM.render
ReactDOM.render
已於 2022年3月 (v18.0.0) 被棄用。在 React 19 中,我們將移除 ReactDOM.render
,您需要遷移到使用 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 />);
已移除:ReactDOM.hydrate
ReactDOM.hydrate
已於 2022年3月 (v18.0.0) 被棄用。在 React 19 中,我們將移除 ReactDOM.hydrate
,您需要遷移到使用 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 />);
已移除:ReactDOM.unmountComponentAtNode
ReactDOM.unmountComponentAtNode
已於 2022年3月 (v18.0.0) 被棄用。在 React 19 中,您需要遷移到使用 root.unmount()
。
// Before
unmountComponentAtNode(document.getElementById('root'));
// After
root.unmount();
更多資訊請參見 createRoot
和 hydrateRoot
的 root.unmount()
。
已移除:ReactDOM.findDOMNode
ReactDOM.findDOMNode
已於 2018年10月 (v16.6.0) 被棄用。
我們移除 findDOMNode
是因為它是一個遺留的應急方案,執行速度慢,難以重構,只返回第一個子節點,並破壞了抽象級別(更多資訊請參見 這裡)。您可以使用 DOM refs 來替換 ReactDOM.findDOMNode
。
// 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" />
}
新的棄用警告
已棄用:element.ref
React 19 支援 ref
作為屬性,因此我們棄用 element.ref
,並用 element.props.ref
替代。
訪問 element.ref
將會發出警告。
已棄用:react-test-renderer
我們棄用 react-test-renderer
是因為它實現了與使用者使用的環境不匹配的渲染器環境,促進了測試實現細節,並依賴於對 React 內部機制的內省。
測試渲染器是在更多可行的測試策略(如 React Testing Library)可用之前建立的,我們現在建議改用現代測試庫。
在 React 19 中,react-test-renderer
將記錄棄用警告,並已切換到併發渲染。我們建議將您的測試遷移到 @testing-library/react 或 @testing-library/react-native 以獲得現代且良好支援的測試體驗。
顯著變化
StrictMode 改進
React 19 包含若干針對 Strict Mode 的修復和改進。
在開發環境下,當 Strict Mode 進行雙重渲染時,useMemo
和 useCallback
將在第二次渲染期間重用第一次渲染時記憶化的結果。已經相容 Strict Mode 的元件應該不會注意到行為上的差異。
與所有 Strict Mode 行為一樣,這些功能旨在在開發過程中主動發現元件中的錯誤,以便在將它們釋出到生產環境之前修復它們。例如,在開發過程中,StrictMode 會在初始掛載時雙重呼叫 ref 回撥函式,以模擬掛載元件被 Suspense 替代時的行為。
Suspense 的改進
在 React 19 中,當元件掛起時,React 將立即提交最近 Suspense 邊界的回退內容,而無需等待整個同級樹渲染完成。回退內容提交後,React 將安排對掛起同級元件的另一次渲染,以“預熱”樹中其餘部分的延遲請求。


以前,當元件掛起時,會先渲染掛起的同級元件,然後提交回退內容。


在 React 19 中,當元件掛起時,會先提交回退內容,然後渲染掛起的同級元件。
此更改意味著 Suspense 回退內容顯示速度更快,同時仍然可以預熱掛起樹中的延遲請求。
移除 UMD 構建
過去,UMD 廣泛用作一種方便的方法來載入 React,無需構建步驟。現在,有更現代的替代方案可以在 HTML 文件中將模組載入為指令碼。從 React 19 開始,React 將不再生成 UMD 構建,以降低其測試和釋出流程的複雜性。
要使用 script 標籤載入 React 19,我們建議使用基於 ESM 的 CDN,例如 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>
依賴於 React 內部元件的庫可能會阻止升級
此版本包含對 React 內部元件的更改,這可能會影響忽略我們不要使用內部元件(例如 SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
)的請求的庫。這些更改對於實現 React 19 中的改進是必要的,並且不會破壞遵循我們指南的庫。
根據我們的 版本策略,這些更新未列為重大更改,我們也沒有包含有關如何升級它們的文件。建議是刪除任何依賴於內部元件的程式碼。
為了反映使用內部元件的影響,我們已將 SECRET_INTERNALS
字尾重新命名為
_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
將來,我們將更積極地阻止從 React 訪問內部元件,以阻止其使用並確保使用者不會被阻止升級。
TypeScript 更改
移除已棄用的 TypeScript 型別
我們根據 React 19 中移除的 API 清理了 TypeScript 型別。一些已移除的型別已被移動到更相關的包中,而另一些則不再需要來描述 React 的行為。
檢視 types-react-codemod
以瞭解受支援的替換列表。如果您覺得缺少 codemod,可以在 缺少的 React 19 codemod 列表 中進行跟蹤。
ref
需要清理
此更改包含在 react-19
程式碼修改預設中,作為 no-implicit-ref-callback-return
。
由於引入了 ref 清理函式,從 ref 回撥函式返回任何其他內容現在將被 TypeScript 拒絕。解決方法通常是停止使用隱式返回。
- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />
原始程式碼返回 HTMLDivElement
的例項,TypeScript 將無法知道這是否應該是一個清理函式。
useRef
需要一個引數
此更改包含在 react-19
程式碼修改預設中,作為 refobject-defaults
。
長期以來,TypeScript 和 React 的工作方式一直受到 useRef
的詬病。我們更改了型別,以便 useRef
現在需要一個引數。這大大簡化了它的型別簽名。它現在將表現得更像 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);
現在這也意味著所有 ref 都是可變的。您將不再遇到因為用 null
初始化而無法修改 ref 的問題。
const ref = useRef<number>(null);
// Cannot assign to 'current' because it is a read-only property
ref.current = 1;
MutableRef
現已棄用,取而代之的是單個 RefObject
型別,useRef
將始終返回該型別。
interface RefObject<T> {
current: T
}
declare function useRef<T>: RefObject<T>
useRef
仍然有一個方便的過載用於 useRef<T>(null)
,它會自動返回 RefObject<T | null>
。為了簡化由於 useRef
的必需引數而導致的遷移,添加了一個方便的過載用於 useRef(undefined)
,它會自動返回 RefObject<T | undefined>
。
檢視 [RFC] 使所有 ref 可變,瞭解有關此更改的先前討論。
對 ReactElement
TypeScript 型別的更改
此更改包含在 react-element-default-any-props
程式碼修改中。
如果元素型別為 ReactElement
,則 React 元素的 props
現在預設為 unknown
,而不是 any
。如果您將型別引數傳遞給 ReactElement
,則不會影響您。
type Example2 = ReactElement<{ id: string }>["props"];
// ^? { id: string }
但是,如果您依賴於預設值,則現在必須處理 unknown
。
type Example = ReactElement["props"];
// ^? Before, was 'any', now 'unknown'
只有當您有很多依賴於不安全的元素 props 訪問的遺留程式碼時,才需要它。元素內省只作為一種應急措施存在,您應該透過顯式的 any
明確表示您的 props 訪問是不安全的。
TypeScript 中的 JSX 名稱空間
此更改包含在 react-19
程式碼修改預設中,作為 scoped-jsx
長期以來的一個請求是刪除全域性 JSX
名稱空間,而支援使用 React.JSX
。這有助於防止全域性型別的汙染,從而防止使用 JSX 的不同 UI 庫之間的衝突。
您現在需要將 JSX 名稱空間的模組擴充套件包裝在 `declare module ”…”` 中。
// global.d.ts
+ declare module "react" {
namespace JSX {
interface IntrinsicElements {
"my-element": {
myElementProps: string;
};
}
}
+ }
確切的模組說明符取決於您在 tsconfig.json
的 compilerOptions
中指定的 JSX 執行時。
- 對於
"jsx": "react-jsx"
,它將是react/jsx-runtime
。 - 對於
"jsx": "react-jsxdev"
,對應的包應該是react/jsx-dev-runtime
。 - 對於
"jsx": "react"
和"jsx": "preserve"
,對應的包應該是react
。
改進的 useReducer
型別宣告
useReducer
現在擁有了改進的型別推斷,這要感謝 @mfp22。
然而,這需要一個breaking change,useReducer
不再接受完整的reducer型別作為型別引數,而是需要不傳入任何引數(依賴上下文型別推斷)或者同時傳入狀態和action型別。
新的最佳實踐是 *不要* 向 useReducer
傳遞型別引數。
- useReducer<React.Reducer<State, Action>>(reducer)
+ useReducer(reducer)
在一些邊緣情況下,你可以透過傳入 Action
元組顯式地指定狀態和action型別,但這可能無法正常工作。
- useReducer<React.Reducer<State, Action>>(reducer)
+ useReducer<State, [Action]>(reducer)
如果你內聯定義 reducer,我們建議你改為註釋函式引數。
- useReducer<React.Reducer<State, Action>>((state, action) => state)
+ useReducer((state: State, action: Action) => state)
如果你將 reducer 移到 useReducer
呼叫之外,也必須這樣做。
const reducer = (state: State, action: Action) => state;
變更日誌
其他breaking change
- react-dom:
src
和href
屬性中的 JavaScript URL 導致錯誤 #26507 - react-dom:從
onRecoverableError
中移除errorInfo.digest
#28222 - react-dom:移除
unstable_flushControlled
#26397 - react-dom:移除
unstable_createEventHandle
#28271 - react-dom:移除
unstable_renderSubtreeIntoContainer
#28271 - react-dom:移除
unstable_runWithPriority
#28271 - react-is:從
react-is
中移除已棄用的方法 28224
其他值得注意的更改
- react:批次同步、預設和連續lane #25700
- react:不要預渲染已掛起的元件的同級元件 #26380
- react:檢測由渲染階段更新引起的無限更新迴圈 #26625
- react-dom:popstate中的過渡現在是同步的 #26025
- react-dom:在伺服器端渲染期間移除佈局效果警告 #26395
- react-dom:發出警告,並且不要為src/href設定空字串(錨點標籤除外) #28124
要檢視完整的更改列表,請參閱 變更日誌。
感謝 Andrew Clark,Eli White,Jack Pope,Jan Kassens,Josh Story,Matt Carroll,Noah Lemen,Sophie Alpert 和 Sebastian Silbermann 對本文的審閱和編輯。