| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657 |
- 'use client';
- /*React v19 团队现在更倾向于:
- 如果一个值能在 render 阶段计算出来,就不要放进 state。
- 这就是为什么如果你在useEffect/useLayoutEffect中setState会eslint不通过的原因。
- 以Portal组件为例,在react v19之前Portal的标准写法是:
- ```react
- 'use client';
- import { useEffect, useState } from 'react';
- import { createPortal } from 'react-dom';
- export default function Portal({ children }: { children: React.ReactNode }) {
- const [mounted, setMounted] = useState(false);
- // useEffect 只会在客户端执行,服务端渲染时不会运行(Next.js 在服务器端会跳过所有 useEffect / useLayoutEffect)。
- useEffect(() => {
- setMounted(true); // 组件挂载后,把标志位置为 true
- }, []); // 空依赖数组,仅执行一次
- if (!mounted) return null; // SSR 时不渲染,避免 hydration 错误
- return createPortal(children, document.body);
- }
- ```
- 但是在react v19 中 认为mounted是一个「派生状态(derived state)」,
- 完全可以在 render 时计算出来(使用document 来判断),不需要专门声明一个state在副作用里更改状态(引起二次渲染),所以eslint报错。
- */
- import {useRef, useEffect} from 'react';
- import { createPortal } from 'react-dom';
- export default function Portal({
- children,
- }: {
- children: React.ReactNode;
- }) {
- const mountedRef = useRef(false);
- useEffect(() => {
- mountedRef.current = true;
- // 无需任何清理 (cleanup) 函数
- }, []); // 依赖数组完全为空
- // 服务端和客户端首次渲染时都返回 null
- if (!mountedRef.current) {
- return null;
- }
- const portalRoot = document.getElementById('modal-root');
- if (!portalRoot) {
- return null;
- }
- return createPortal(children, portalRoot);
- }
|