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

@preact/signals-react v2 是否支持React类组件?

问题

Signals的React集成文档仅提及了与函数组件的集成方式,我尝试在类组件中渲染signal值,但完全不具备响应性。

测试代码如下:

"dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "@preact/signals-react": "^2.0.0",
    "@preact/signals-react-transform": "^0.3.0",
}
import React from "react";
import { createRoot } from "react-dom/client";
import { signal } from "@preact/signals-react";

const count = signal(0);

// 不会重新渲染
class CountClass extends React.Component {
  render() {
    return (
      <h1
        onClick={() => {
          count.value = Math.random();
        }}
      >
        title{count.value}
      </h1>
    );
  }
}

// 可以正常重新渲染
const CountFunc = () => {
  return (
    <h1
      onClick={() => {
        count.value = Math.random();
      }}
    >
      title{count.value}
    </h1>
  );
};

const App = () => {
  return (
    <div>
      <CountClass />
      <CountFunc />
    </div>
  )
}

createRoot(document.querySelector("#root")).render(<App />);

用Babel转换后,类组件部分未生成任何与signal相关的转换代码:

//...
const count = (0, _signalsReact.signal)(0);

// 不会重新渲染
class CountClass extends _react.default.Component {
  render() {
    return /*#__PURE__*/_react.default.createElement("h1", {
      onClick: () => {
        count.value = Math.random();
      }
    }, "title", count.value);
  }
}

// 可以正常重新渲染
const CountFunc = () => {
  var _effect = (0, _runtime.useSignals)(1);
  try {
    return /*#__PURE__*/_react.default.createElement("h1", {
      onClick: () => {
        count.value = Math.random();
      }
    }, "title", count.value);
  } finally {
    _effect.f();
  }
};
//...
解决方案

核心原因

Signals的React集成(包括Babel转换插件)默认仅对函数组件生效,会自动注入useSignals钩子追踪signal依赖并触发重渲染。类组件没有这种自动处理机制,直接访问count.value只会获取当前快照值,不会订阅更新,因此signal变化时类组件不会重新渲染。

可行解决方法

方法1:手动订阅signal更新触发重渲染

在类组件生命周期中手动订阅signal变化,通过setState触发组件重渲染:

class CountClass extends React.Component {
  componentDidMount() {
    // 订阅signal变化,更新时调用setState触发重渲染
    this.unsubscribe = count.subscribe(() => this.setState({}));
  }

  componentWillUnmount() {
    // 卸载时取消订阅,避免内存泄漏
    this.unsubscribe();
  }

  render() {
    return (
      <h1
        onClick={() => {
          count.value = Math.random();
        }}
      >
        title{count.value}
      </h1>
    );
  }
}

方法2:用函数组件做中间层

将类组件包裹在函数组件内部,借助函数组件的自动追踪能力,把signal值通过props传递给类组件:

const CountClassWrapper = () => {
  // 函数组件内访问signal会自动触发依赖追踪
  const currentCount = count.value;
  return <CountClass count={currentCount} />;
};

class CountClass extends React.Component {
  render() {
    return (
      <h1
        onClick={() => {
          count.value = Math.random();
        }}
      >
        title{this.props.count}
      </h1>
    );
  }
}

方法3:封装高阶组件(HOC)复用订阅逻辑

编写通用HOC,给类组件添加signal订阅能力:

function withSignals(WrappedComponent) {
  return class extends React.Component {
    state = {};
    unsubscribes = [];

    componentDidMount() {
      // 批量订阅传入的signal列表
      this.props.signals.forEach(signal => {
        this.unsubscribes.push(signal.subscribe(() => this.setState({})));
      });
    }

    componentWillUnmount() {
      // 清理所有订阅
      this.unsubscribes.forEach(unsubscribe => unsubscribe());
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

// 使用HOC包裹类组件
const CountClassWithSignals = withSignals(CountClass);

// 使用时传入需要订阅的signal
<CountClassWithSignals signals={[count]} />

方法4:迁移到函数组件(官方推荐)

如果项目允许,直接将类组件转换为函数组件,即可直接使用Signals的自动响应式能力,这也是官方文档推荐的集成方式。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 20:01:12