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

如何实现类React的原生JS版useEffect,支持监听变量值而非变量名?

实现支持直接传入变量的类React useEffect监听功能

问题分析

你当前的代码通过传入变量名字符串(如['s']),利用Object.defineProperty给全局变量添加getter/setter实现赋值监听,但直接传入变量值数组(如[s])时,因为只能拿到变量的当前值,无法反向获取变量标识符,所以无法绑定对应的setter。

要实现类似React的调用方式,需要解决两个核心问题:要么通过某种方式从传入的变量值反向拿到变量名,要么换一种不依赖变量名的监听逻辑。

解决方案

方案1:通过调用栈解析变量名(支持赋值实时触发)

利用Error栈信息获取useEffect调用处的源码,解析出依赖数组中的变量名,再给对应全局变量绑定getter/setter。这种方式可以实现和原代码一样的实时赋值监听,且支持[s]的调用格式。

修改后的代码:

const app = document.getElementById('app');

// 从调用栈解析依赖数组中的变量名
function getDepVarNames() {
  const stack = new Error().stack.split('\n');
  // 定位到useEffect调用的栈行(不同浏览器可能需要调整索引,这里以Chrome为例)
  const callLine = stack.find(line => line.includes('useEffect'));
  if (!callLine) return [];
  // 提取依赖数组部分
  const depMatch = callLine.match(/useEffect\s*\([^,]+,\s*(\[.+?\])\)/);
  if (!depMatch) return [];
  // 拆分变量名
  return depMatch[1].slice(1, -1).split(',').map(name => name.trim());
}

const useEffect = (handleFn, deps) => {
  const varNames = getDepVarNames();
  
  varNames.forEach(name => {
    // 保存变量初始值
    const originalValue = window[name];
    Object.defineProperty(window, name, {
      get() {
        return this._currentValue;
      },
      set(v) {
        if (v === this._currentValue) return;
        this._currentValue = v;
        handleFn(v);
      },
      configurable: true,
    });
    // 初始化当前值
    window[name]._currentValue = originalValue;
  });
};

const IncreaseButton = document.querySelector('p.increase');
IncreaseButton.addEventListener('click', IncreaseButtonHandler);

let s = 0;

// 现在可以直接传入[s]
useEffect(
  function(e) {
    console.log('变量更新后的值:', e);
  },
  [s]
);

function IncreaseButtonHandler() {
  s = s + 1;
}

注意:这种方法依赖浏览器的栈信息格式,在生产环境代码被压缩混淆后会失效,适合开发环境使用。

方案2:轮询对比值变化(无变量名依赖)

如果不需要实时监听赋值操作,而是像React useEffect那样,当变量值变化时触发回调,可以采用定时轮询对比的方式。这种方式不依赖变量名,完全通过值的对比实现,兼容性更好。

代码示例:

const app = document.getElementById('app');

const useEffect = (handleFn, deps) => {
  let prevDeps = [...deps];
  
  // 定时检查值变化,间隔可根据需求调整
  setInterval(() => {
    // 浅对比依赖值是否变化
    const hasChanged = deps.some((val, idx) => val !== prevDeps[idx]);
    if (hasChanged) {
      handleFn(deps);
      prevDeps = [...deps];
    }
  }, 100);
};

const IncreaseButton = document.querySelector('p.increase');
IncreaseButton.addEventListener('click', IncreaseButtonHandler);

let s = 0;

useEffect(
  function([newVal]) {
    console.log('变量更新后的值:', newVal);
  },
  [s]
);

function IncreaseButtonHandler() {
  s = s + 1;
}

注意:这种方式是轮询触发,不是实时响应赋值;如果监听引用类型变量,需要实现深对比逻辑才能准确检测变化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 00:55:23