首页 > 解决方案 > 在回调中调用 useRef

问题描述

所以我从钩子之前就没有反应,以前没有使用过threeJS,但我试图用一颗石头击中2只鸟,所以如果这是一个菜鸟的错误,请原谅。

我要做的是在 react 文档正文中渲染 Three.js 场景,我试图通过在其中运行 three.js 代码useEffect()并使用 useRef() 设置对我的 react 文档的引用来做到这一点,但是,显然 useEffect 在文档呈现之前运行,因此会中断,所以我尝试使用这样的 ref 回调

import { useRef, useEffect, useCallback } from "react";
// Packages
import * as THREE from "three";
// Styling
import "./homePage.scss";

function HomePage() {
  // Declare a new mounting reference
  const mountRef = useCallback((node) => {
    if (node !== null) {
      useRef(null);
    }
  }, []);
  // Lifecycle hook
  useEffect(() => {
    console.log(mountRef);
    // === 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.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 ===
  }, []);

  return <div ref={mountRef} />;
}

export default HomePage;

但是,现在我有以下错误 React Hook "useRef" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook

我该如何解决这种情况?谢谢!

标签: javascriptreactjsreact-hooks

解决方案


useRef在组件的顶层使用,而不是在回调中使用(不要使用useCallback它):

const mountRef = useRef(null);

然后在您的useEffect, 中声明mountRef.current为依赖项,并且仅在存在时才使用它,请参阅***注释:

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 ===
}, []);

您可能希望包含一个清理回调(您从中返回的函数)以从on 卸载useEffect中删除三个 DOM 元素:div

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.currentrenderer.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>


推荐阅读