diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml index 87dcfdc73..2a905a0df 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/analyze.yml @@ -11,10 +11,10 @@ jobs: analyze: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: '20.x' @@ -22,7 +22,7 @@ jobs: uses: bahmutov/npm-install@v1.7.10 - name: Restore next build - uses: actions/cache@v2 + uses: actions/cache@v3 id: restore-build-cache env: cache-name: cache-next-build @@ -41,7 +41,7 @@ jobs: run: npx -p nextjs-bundle-analysis@0.5.0 report - name: Upload bundle - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: .next/analyze/__bundle_analysis.json name: bundle_analysis.json @@ -73,7 +73,7 @@ jobs: run: ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare - name: Upload analysis comment - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: analysis_comment.txt path: .next/analyze/__bundle_analysis_comment.txt @@ -82,7 +82,7 @@ jobs: run: echo ${{ github.event.number }} > ./pr_number - name: Upload PR number - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: pr_number path: ./pr_number diff --git a/src/components/Layout/Footer.tsx b/src/components/Layout/Footer.tsx index 5bcf9df98..4e19039ed 100644 --- a/src/components/Layout/Footer.tsx +++ b/src/components/Layout/Footer.tsx @@ -283,7 +283,7 @@ export function Footer() {
- ©{new Date().getFullYear()} + Copyright © Meta Platforms, Inc
{ const options = createOptions(); - const connection = createConnection(); + const connection = createConnection(options); connection.connect(); // ... ``` @@ -722,7 +722,7 @@ function ChatRoom({ roomId }) { ```js {6} useEffect(() => { const options = createOptions(); - const connection = createConnection(); + const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [createOptions]); // 🔴 Проблема: Эта зависимость изменяется при каждом рендере @@ -744,7 +744,7 @@ function ChatRoom({ roomId }) { useEffect(() => { const options = createOptions(); - const connection = createConnection(); + const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [createOptions]); // ✅ Изменяется только при изменении createOptions @@ -766,7 +766,7 @@ function ChatRoom({ roomId }) { } const options = createOptions(); - const connection = createConnection(); + const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]); // ✅ Изменяется только при изменении roomId diff --git a/src/content/reference/react/useMemo.md b/src/content/reference/react/useMemo.md index 8551bf2c8..c2004abca 100644 --- a/src/content/reference/react/useMemo.md +++ b/src/content/reference/react/useMemo.md @@ -1055,6 +1055,82 @@ label { --- +### Слишком частые вызовы эффекта {/*preventing-an-effect-from-firing-too-often*/} + +Время от времени вам могут понадобиться значения внутри [эффекта:](/learn/synchronizing-with-effects) + +```js {4-7,10} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + const options = { + serverUrl: 'https://localhost:1234', + roomId: roomId + } + + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + // ... +``` + +Это создаёт проблему. [Каждое реактивное значение должно быть объявлено как зависимость вашего эффекта.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) Но если вы добавите `options` как зависимость, ваш эффект начнёт постоянно переподключаться к чату: + + +```js {5} + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); // 🔴 Проблема: эта зависимость меняется при каждом рендере + // ... +``` + +Решение – обернуть необходимый в эффекте объект в `useMemo`: + +```js {4-9,16} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + const options = useMemo(() => { + return { + serverUrl: 'https://localhost:1234', + roomId: roomId + }; + }, [roomId]); // ✅ Меняется только тогда, когда меняется roomId + + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); // ✅ Вызывается только тогда, когда меняется options + // ... +``` + +Это гарантирует, что объект `options` один и тот же между повторными рендерами, если `useMemo` возвращает закешированный объект. + +Однако `useMemo` является оптимизацией производительности, а не семантической гарантией. React может отбросить закешированное значение, если [возникнет условие для этого](#caveats). Это также спровоцирует перезапуск эффекта, **так что ещё лучше будет избавиться от зависимости,** переместив ваш объект *внутрь* эффекта: + +```js {5-8,13} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const options = { // ✅ Нет необходимости для useMemo или зависимостей объекта! + serverUrl: 'https://localhost:1234', + roomId: roomId + } + + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ Меняется только тогда, когда меняется roomId + // ... +``` + +Теперь ваш код стал проще и не требует `useMemo`. [Узнайте больше об удалении зависимостей эффекта.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) + + ### Мемоизация зависимостей других хуков {/*memoizing-a-dependency-of-another-hook*/} Предположим, что у нас есть вычисления, зависящие от объекта, создаваемого внутри компонента: diff --git a/src/content/reference/react/useReducer.md b/src/content/reference/react/useReducer.md index 8cfa818d1..d339631b2 100644 --- a/src/content/reference/react/useReducer.md +++ b/src/content/reference/react/useReducer.md @@ -52,6 +52,7 @@ function MyComponent() { #### Замечания {/*caveats*/} * `useReducer -- это хук, поэтому вызывайте его только **на верхнем уровне компонента** или собственных хуков. useReducer нельзя вызвать внутри циклов или условий. Если это нужно, создайте новый компонент и переместите состояние в него. +* Функция `dispatch` стабильна между повторными рендерами, поэтому вы увидите, что её часто пропускают в списке зависимостей эффекта, но и её включение не вызовет перезапуск эффекта. Если линтер позволяет вам пропускать зависимости без ошибок, то вы можете делать это без опаски. [Узнайте больше об удалении зависимостей эффекта.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) * В строгом режиме React будет **вызывать редюсер и инициализатор дважды**, [чтобы помочь обнаружить случайные побочные эффекты.](#my-reducer-or-initializer-function-runs-twice) Такое поведение проявляется только в режиме разработки и не влияет на продакшен-режим. Логика обновления состояния не изменится, если редюсер и инициализатор – чистые функции (какими они и должны быть). Результат второго вызова проигнорируется. --- diff --git a/src/content/reference/react/useState.md b/src/content/reference/react/useState.md index 48d96f8ee..4aa9d5911 100644 --- a/src/content/reference/react/useState.md +++ b/src/content/reference/react/useState.md @@ -85,6 +85,8 @@ function handleClick() { * React [batches state updates.](/learn/queueing-a-series-of-state-updates) It updates the screen **after all the event handlers have run** and have called their `set` functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use [`flushSync`.](/reference/react-dom/flushSync) +* The `set` function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) + * Calling the `set` function *during rendering* is only allowed from within the currently rendering component. React will discard its output and immediately attempt to render it again with the new state. This pattern is rarely needed, but you can use it to **store information from the previous renders**. [See an example below.](#storing-information-from-previous-renders) * In Strict Mode, React will **call your updater function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your updater function is pure (as it should be), this should not affect the behavior. The result from one of the calls will be ignored. diff --git a/src/content/reference/react/useTransition.md b/src/content/reference/react/useTransition.md index 77c2cdad5..5066fe637 100644 --- a/src/content/reference/react/useTransition.md +++ b/src/content/reference/react/useTransition.md @@ -80,6 +80,8 @@ function TabContainer() { * The function you pass to `startTransition` must be synchronous. React immediately executes this function, marking all state updates that happen while it executes as Transitions. If you try to perform more state updates later (for example, in a timeout), they won't be marked as Transitions. +* The `startTransition` function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) + * A state update marked as a Transition will be interrupted by other state updates. For example, if you update a chart component inside a Transition, but then start typing into an input while the chart is in the middle of a re-render, React will restart the rendering work on the chart component after handling the input update. * Transition updates can't be used to control text inputs. diff --git a/src/content/reference/rsc/server-actions.md b/src/content/reference/rsc/server-actions.md index 06613cb7c..d24e896f0 100644 --- a/src/content/reference/rsc/server-actions.md +++ b/src/content/reference/rsc/server-actions.md @@ -53,7 +53,7 @@ When React renders the `EmptyNote` Server Component, it will create a reference export default function Button({onClick}) { console.log(onClick); // {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'} - return + return } ```