您在组件的顶层使用useRef,而不是在回调中(不要使用useCallback):
const mountRef = useRef(null);
然后在您的useEffect 中,将mountRef.current 声明为依赖项,并且仅在存在时才使用它,请参阅*** cmets:
useEffect(() => {
// *** If we don't have the DOM element yet, wait for it
if (!mountRef.current) {
return;
}
// === THREE.JS CODE START ===
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// use ref as a mount point of the Three.js scene instead of the document.body
mountRef.current.appendChild(renderer.domElement);
// ^^^^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− *** Use the DOM element
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
var animate = function () {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
// === THREE.JS CODE END ===
}, []);
您可能希望包含一个清理回调(从 useEffect 返回的函数)以在卸载时从 div 中删除三个 DOM 元素:
useEffect(() => {
// ***
const { current } = mountRef;
if (!current) {
return;
}
// === THREE.JS CODE START ===
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// use ref as a mount point of the Three.js scene instead of the document.body
// ***
const {domElement} = renderer;
current.appendChild(domElement);
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
var animate = function () {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
// === THREE.JS CODE END ===
// ***
return () => {
current.removeChild(domElement);
};
}, []);
请注意我是如何将mountRef.current 和renderer.domElement 抓取到本地常量的。这使得清理回调更加可靠,因为这些属性可以在 useEffect 回调的上下文之外更改。
现场示例:
const {useRef, useEffect} = React;
function HomePage() {
// Declare a new mounting reference
const mountRef = useRef(null);
// Lifecycle hook
useEffect(() => {
const { current } = mountRef;
console.log("current", current);
// If we don't have the DOM element yet, wait for it
if (!current) {
return;
}
// === THREE.JS CODE START ===
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// use ref's DOM ELEMENT as a mount point of the Three.js scene instead of the document.body
const { domElement } = renderer;
current.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
var animate = function () {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
// === THREE.JS CODE END ===
// Cleanup callback on component unmount
return () => {
current.removeChild(domElement);
};
}, []);
return <div ref={mountRef} />;
}
ReactDOM.render(
<HomePage/>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r124/three.min.js"></script>