如何实现类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
相关产品推荐
相关产品推荐

