You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Remix中仅在客户端渲染组件?

在Remix中实现仅客户端渲染的Three.js组件

Remix没有你虚构的useRemixContext钩子,但有几种可靠的方式判断服务端/客户端上下文,进而实现仅客户端渲染的组件,下面是具体方案:

一、判断服务端/客户端的常用方法

1. 通用判断方式:检查window对象

服务端环境不存在window对象,所以可以用typeof window !== 'undefined'来判断,但直接在渲染逻辑里用可能导致 hydration 不匹配,建议结合状态管理:

import { useState, useEffect } from 'react';

const MyComponent = () => {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  if (!isClient) {
    return null; // 服务端渲染时返回null或加载占位符
  }

  return <div>仅客户端渲染内容</div>;
};

2. Remix v2.8+ 内置钩子:useIsClient

从Remix 2.8版本开始,官方提供了useIsClient钩子,能直接判断当前是否处于客户端上下文:

import { useIsClient } from '@remix-run/react';

const MyComponent = () => {
  const isClient = useIsClient();

  if (!isClient) {
    return null;
  }

  return <div>仅客户端渲染内容</div>;
};

二、Three.js组件的完整实现示例

因为Three.js依赖浏览器的WebGL环境,必须确保仅在客户端初始化实例,下面是完整的示例:

import { useState, useEffect, useRef } from 'react';
import * as THREE from 'three';

const ThreeJSCube = () => {
  const [isClient, setIsClient] = useState(false);
  const containerRef = useRef(null);
  // 用ref存储Three.js实例,避免重复初始化
  const rendererRef = useRef(null);
  const sceneRef = useRef(null);
  const cubeRef = useRef(null);

  // 标记是否进入客户端环境
  useEffect(() => {
    setIsClient(true);
  }, []);

  // 初始化和销毁Three.js场景
  useEffect(() => {
    if (!isClient || !containerRef.current) return;

    // 创建场景、相机、渲染器
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColor(0xf0f0f0);
    containerRef.current.appendChild(renderer.domElement);

    // 创建立方体
    const geometry = new THREE.BoxGeometry(2, 2, 2);
    const material = new THREE.MeshNormalMaterial();
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    camera.position.z = 5;

    // 动画循环
    const animate = () => {
      requestAnimationFrame(animate);
      cube.rotation.x += 0.01;
      cube.rotation.y += 0.01;
      renderer.render(scene, camera);
    };
    animate();

    // 保存实例到ref
    rendererRef.current = renderer;
    sceneRef.current = scene;
    cubeRef.current = cube;

    // 清理函数:组件卸载时销毁Three.js资源
    return () => {
      containerRef.current?.removeChild(renderer.domElement);
      renderer.dispose();
      geometry.dispose();
      material.dispose();
    };
  }, [isClient]);

  // 服务端渲染时显示加载占位
  if (!isClient) {
    return <div className="loading">加载3D场景中...</div>;
  }

  return <div ref={containerRef} style={{ width: '100vw', height: '100vh' }} />;
};

export default ThreeJSCube;

三、关键注意事项

  • 绝对不要在服务端执行Three.js的初始化代码,否则会因为缺少浏览器API直接报错。
  • 用useEffect处理Three.js的生命周期,包括初始化和资源清理,防止内存泄漏。
  • 直接在Remix的路由组件中导入这个客户端组件即可,Remix会自动处理服务端渲染时的跳过逻辑。

内容的提问来源于stack exchange,提问作者kemicofa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 22:28:29