diff --git a/.env.production b/.env.production index d25eb7dd4b..e403f96b68 100644 --- a/.env.production +++ b/.env.production @@ -1 +1 @@ -NEXT_PUBLIC_GA_TRACKING_ID = 'UA-41298772-4' \ No newline at end of file +NEXT_PUBLIC_GA_TRACKING_ID = 'G-B1E83PJ3RT' \ No newline at end of file diff --git a/package.json b/package.json index 472ef79c91..b5e07d70a8 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "classnames": "^2.2.6", "date-fns": "^2.16.1", "debounce": "^1.2.1", - "ga-lite": "^2.1.4", "github-slugger": "^1.3.0", "next": "^13.4.1", "next-remote-watch": "^1.0.0", diff --git a/src/components/Layout/Feedback.tsx b/src/components/Layout/Feedback.tsx index 94e3a81a70..66ab928963 100644 --- a/src/components/Layout/Feedback.tsx +++ b/src/components/Layout/Feedback.tsx @@ -4,7 +4,6 @@ import {useState} from 'react'; import {useRouter} from 'next/router'; -import {ga} from '../../utils/analytics'; export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) { const {asPath} = useRouter(); @@ -48,14 +47,12 @@ const thumbsDownIcon = ( function sendGAEvent(isPositive: boolean) { // Fragile. Don't change unless you've tested the network payload // and verified that the right events actually show up in GA. - ga( - 'send', - 'event', - 'button', - 'feedback', - window.location.pathname, - isPositive ? '1' : '0' - ); + // @ts-ignore + gtag('event', 'feedback', { + event_category: 'button', + event_label: window.location.pathname, + value: isPositive ? 1 : 0, + }); } function SendFeedback({onSubmit}: {onSubmit: () => void}) { diff --git a/src/components/MDX/Sandpack/Preview.tsx b/src/components/MDX/Sandpack/Preview.tsx index 8d83d98678..059645550c 100644 --- a/src/components/MDX/Sandpack/Preview.tsx +++ b/src/components/MDX/Sandpack/Preview.tsx @@ -52,6 +52,12 @@ export function Preview({ rawError = null; } + // When throwing a new Error in Sandpack - we want to disable the dev error dialog + // to show the Error Boundary fallback + if (rawError && rawError.message.includes(`throw Error('Example error')`)) { + rawError = null; + } + // Memoized because it's fed to debouncing. const firstLintError = useMemo(() => { if (lintErrors.length === 0) { diff --git a/src/content/learn/describing-the-ui.md b/src/content/learn/describing-the-ui.md index 8ac9e66fdb..a0ee098f3c 100644 --- a/src/content/learn/describing-the-ui.md +++ b/src/content/learn/describing-the-ui.md @@ -533,13 +533,21 @@ React 使用树形关系建模以展示组件和模块之间的关系。 React 渲染树是组件之间父子关系的表示。 -示例的 React 渲染树 + + +示例的 React 渲染树 + + 位于树顶部、靠近根组件的组件被视为顶层组件。没有子组件的组件被称为叶子组件。对组件的这种分类对于理解数据流和渲染性能非常有用。 对 JavaScript 模块之间的关系进行建模是了解应用程序的另一种有用方式。我们将其称为模块依赖树。 -示例的模块依赖树 + + +示例的模块依赖树 + + 构建工具经常使用依赖树来捆绑客户端下载和渲染所需的所有 JavaScript 代码。对于 React 应用程序,打包大小会导致用户体验退化。了解模块依赖树有助于调试此类问题。 diff --git a/src/content/reference/react/experimental_taintUniqueValue.md b/src/content/reference/react/experimental_taintUniqueValue.md index 9d2c30b424..9cf7db206a 100644 --- a/src/content/reference/react/experimental_taintUniqueValue.md +++ b/src/content/reference/react/experimental_taintUniqueValue.md @@ -67,7 +67,8 @@ experimental_taintUniqueValue( #### 注意 {/*caveats*/} -- 从受标记的值派生新值可能会破坏标记保护。通过将受标记的值大写、将受标记的字符串值连接成较大的字符串、将受标记的值转换为 base64、对受标记的值进行子字符串操作以及其他类似的转换来创建的新值,除非明确调用 `taintUniqueValue` 标记这些新创建的值,否则它们不会受到标记。 +* 从受标记的值派生新值可能会破坏标记保护。通过将受标记的值大写、将受标记的字符串值连接成较大的字符串、将受标记的值转换为 base64、对受标记的值进行子字符串操作以及其他类似的转换来创建的新值,除非明确调用 `taintUniqueValue` 标记这些新创建的值,否则它们不会受到标记。 +* Do not use `taintUniqueValue` to protect low-entropy values such as PIN codes or phone numbers. If any value in a request is controlled by an attacker, they could infer which value is tainted by enumerating all possible values of the secret. --- diff --git a/src/content/reference/react/useTransition.md b/src/content/reference/react/useTransition.md index abeecb333e..cdfa651dba 100644 --- a/src/content/reference/react/useTransition.md +++ b/src/content/reference/react/useTransition.md @@ -1,5 +1,6 @@ --- title: useTransition +canary: true --- @@ -151,7 +152,7 @@ export default function TabContainer() { function selectTab(nextTab) { startTransition(() => { - setTab(nextTab); + setTab(nextTab); }); } @@ -823,7 +824,7 @@ function use(promise) { reason => { promise.status = 'rejected'; promise.reason = reason; - }, + }, ); throw promise; } @@ -1017,7 +1018,7 @@ function use(promise) { reason => { promise.status = 'rejected'; promise.reason = reason; - }, + }, ); throw promise; } @@ -1288,7 +1289,7 @@ function use(promise) { reason => { promise.status = 'rejected'; promise.reason = reason; - }, + }, ); throw promise; } @@ -1332,7 +1333,7 @@ function use(promise) { reason => { promise.status = 'rejected'; promise.reason = reason; - }, + }, ); throw promise; } @@ -1379,9 +1380,9 @@ async function getBio() { setTimeout(resolve, 500); }); - return `The Beatles were an English rock band, - formed in Liverpool in 1960, that comprised - John Lennon, Paul McCartney, George Harrison + return `The Beatles were an English rock band, + formed in Liverpool in 1960, that comprised + John Lennon, Paul McCartney, George Harrison and Ringo Starr.`; } @@ -1501,7 +1502,101 @@ main { --- -## 疑难解答 {/*troubleshooting*/} +### Displaying an error to users with a error boundary {/*displaying-an-error-to-users-with-error-boundary*/} + + + +Error Boundary for useTransition is currently only available in React's canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + + +If a function passed to `startTransition` throws an error, you can display an error to your user with an [error boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). To use an error boundary, wrap the component where you are calling the `useTransition` in an error boundary. Once the function passed to `startTransition` errors, the fallback for the error boundary will be displayed. + + + +```js AddCommentContainer.js active +import { useTransition } from "react"; +import { ErrorBoundary } from "react-error-boundary"; + +export function AddCommentContainer() { + return ( + ⚠️Something went wrong

}> + +
+ ); +} + +function addComment(comment) { + // For demonstration purposes to show Error Boundary + if(comment == null){ + throw Error('Example error') + } +} + +function AddCommentButton() { + const [pending, startTransition] = useTransition(); + + return ( + + ); +} +``` + +```js App.js hidden +import { AddCommentContainer } from "./AddCommentContainer.js"; + +export default function App() { + return ; +} +``` + +```js index.js hidden +// TODO: update to import from stable +// react instead of canary once the `use` +// Hook is in a stable release of React +import React, { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +// TODO: update this example to use +// the Codesandbox Server Component +// demo environment once it is created +import App from './App'; + +const root = createRoot(document.getElementById('root')); +root.render( + + + +); +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` +
+ +--- + +## Troubleshooting {/*troubleshooting*/} ### 在 transition 中无法更新输入框内容 {/*updating-an-input-in-a-transition-doesnt-work*/} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 7173759688..5431f87cc9 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -5,7 +5,6 @@ import {useEffect} from 'react'; import {AppProps} from 'next/app'; import {useRouter} from 'next/router'; -import {ga} from '../utils/analytics'; import '@docsearch/css'; import '../styles/algolia.css'; @@ -13,13 +12,13 @@ import '../styles/index.css'; import '../styles/sandpack.css'; if (typeof window !== 'undefined') { - if (process.env.NODE_ENV === 'production') { - ga('create', process.env.NEXT_PUBLIC_GA_TRACKING_ID, 'auto'); - ga('send', 'pageview'); - } const terminationEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; window.addEventListener(terminationEvent, function () { - ga('send', 'timing', 'JS Dependencies', 'unload'); + // @ts-ignore + gtag('event', 'timing', { + event_label: 'JS Dependencies', + event: 'unload', + }); }); } @@ -44,8 +43,10 @@ export default function MyApp({Component, pageProps}: AppProps) { useEffect(() => { const handleRouteChange = (url: string) => { const cleanedUrl = url.split(/[\?\#]/)[0]; - ga('set', 'page', cleanedUrl); - ga('send', 'pageview'); + // @ts-ignore + gtag('event', 'pageview', { + event_label: cleanedUrl, + }); }; router.events.on('routeChangeComplete', handleRouteChange); return () => { diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index d8fb0c6212..823ca0b718 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -11,11 +11,11 @@ const MyDocument = () => {