React useLayoutEffect 在组件呈现之前运行的门户子项中
React useLayoutEffect in a child of a portal runs before the component renders
我正在使用门户组件创建门户并将其添加到文档中。
某些组件(工具提示,在我的实际用例中)是门户的子项,需要读取其宽度(在我的例子中,检查工具提示是否在 window 之外,并在必要时重新定位) ,然后使用 useLayoutEffect
.
重新渲染自身
我的问题是在渲染元素之前调用了 useLayoutEffect 的回调,因此当我执行类似 getBoundingClientRect()
的操作时,宽度仍然为 0。如果我将组件移出门户,则它工作正常。
https://codesandbox.io/s/divine-snowflake-071dbw
相关部分:
const Portal = ({ children }) => {
const [container] = useState(() => {
const el = document.createElement("div");
return el;
});
useEffect(() => {
document.body.appendChild(container);
return () => {
document.body.removeChild(container);
};
}, [container]);
return createPortal(children, container);
};
const MyComponent = ({ name }) => {
const ref = useRef(null);
useLayoutEffect(() => {
if (ref.current) {
const rect = ref.current.getBoundingClientRect();
// For the component inside the portal,
// the width/height/etc is all 0
console.log("rect of", name, rect.width);
}
}, [name]);
return <div ref={ref}>{name}</div>;
};
const App = () => {
return (
<>
<Portal>
<MyComponent name="component inside portal" />
</Portal>
<MyComponent name="outside" />
</>
);
}
日志
rect of component inside portal 0
rect of outside 770
这个钩子的正常行为是先渲染。
useLayoutEffect 是同步的,useEffect 是异步的。
既然是这样,那真的取决于你把那个钩子放在哪里。
The portal 是在 DOM 树的最顶端呈现组件,不依赖于门户组件的父组件。这就是为什么它最初无法计算通常取决于容器(父组件)的大小的原因。
要获取门户中的元素大小,您可以使用setTimeout
that will help you to execute your logic after all other call stacks完成(在您的情况下,调用堆栈用于初始化门户)
import { useState, useEffect, useLayoutEffect, useRef } from "react";
import { createPortal } from "react-dom";
const Portal = ({ children }) => {
const [container] = useState(() => {
const el = document.createElement("div");
return el;
});
useEffect(() => {
document.body.appendChild(container);
return () => {
document.body.removeChild(container);
};
}, [container]);
return createPortal(children, container);
};
const MyComponent = ({ name }) => {
const ref = useRef(null);
useLayoutEffect(() => {
if (ref.current) {
//delay the execution till the portal initialization completed
setTimeout(() => {
const rect = ref.current.getBoundingClientRect();
console.log("rect of", name, rect.width);
})
}
}, [name]);
return <div ref={ref}>{name}</div>;
};
export default function App() {
return (
<>
<Portal>
<MyComponent name="component inside portal" />
</Portal>
<MyComponent name="outside" />
</>
);
}
您可以查看 this sandbox 进行测试。
我正在使用门户组件创建门户并将其添加到文档中。
某些组件(工具提示,在我的实际用例中)是门户的子项,需要读取其宽度(在我的例子中,检查工具提示是否在 window 之外,并在必要时重新定位) ,然后使用 useLayoutEffect
.
我的问题是在渲染元素之前调用了 useLayoutEffect 的回调,因此当我执行类似 getBoundingClientRect()
的操作时,宽度仍然为 0。如果我将组件移出门户,则它工作正常。
https://codesandbox.io/s/divine-snowflake-071dbw
相关部分:
const Portal = ({ children }) => {
const [container] = useState(() => {
const el = document.createElement("div");
return el;
});
useEffect(() => {
document.body.appendChild(container);
return () => {
document.body.removeChild(container);
};
}, [container]);
return createPortal(children, container);
};
const MyComponent = ({ name }) => {
const ref = useRef(null);
useLayoutEffect(() => {
if (ref.current) {
const rect = ref.current.getBoundingClientRect();
// For the component inside the portal,
// the width/height/etc is all 0
console.log("rect of", name, rect.width);
}
}, [name]);
return <div ref={ref}>{name}</div>;
};
const App = () => {
return (
<>
<Portal>
<MyComponent name="component inside portal" />
</Portal>
<MyComponent name="outside" />
</>
);
}
日志
rect of component inside portal 0
rect of outside 770
这个钩子的正常行为是先渲染。
useLayoutEffect 是同步的,useEffect 是异步的。 既然是这样,那真的取决于你把那个钩子放在哪里。
The portal 是在 DOM 树的最顶端呈现组件,不依赖于门户组件的父组件。这就是为什么它最初无法计算通常取决于容器(父组件)的大小的原因。
要获取门户中的元素大小,您可以使用setTimeout
that will help you to execute your logic after all other call stacks完成(在您的情况下,调用堆栈用于初始化门户)
import { useState, useEffect, useLayoutEffect, useRef } from "react";
import { createPortal } from "react-dom";
const Portal = ({ children }) => {
const [container] = useState(() => {
const el = document.createElement("div");
return el;
});
useEffect(() => {
document.body.appendChild(container);
return () => {
document.body.removeChild(container);
};
}, [container]);
return createPortal(children, container);
};
const MyComponent = ({ name }) => {
const ref = useRef(null);
useLayoutEffect(() => {
if (ref.current) {
//delay the execution till the portal initialization completed
setTimeout(() => {
const rect = ref.current.getBoundingClientRect();
console.log("rect of", name, rect.width);
})
}
}, [name]);
return <div ref={ref}>{name}</div>;
};
export default function App() {
return (
<>
<Portal>
<MyComponent name="component inside portal" />
</Portal>
<MyComponent name="outside" />
</>
);
}
您可以查看 this sandbox 进行测试。