AlpineJS中$watch监听全局Store变量时oldValue与value值相同问题
AlpineJS $watch 中oldValue与value值一致的问题解决办法
问题描述
尝试检测全局Store中变量的变化,按AlpineJS的$watch特性应该能获取到oldValue,但实际触发时,参数value和oldValue的值完全一致。
代码示例
Store 初始化代码
document.addEventListener('alpine:init', () => { Alpine.store('global_data', { products: [ { name: 'batmobile', price_USD: 20, price_INR: 1660, // 20 x 83 }, { name: 'herbie', price_USD: 35, price_INR: 2905, // 35 x 83 }, { name: 'delorean', price_USD: 51, price_INR: 4233, // 51 x 83 }, ], }); });
页面HTML代码
<div x-data="" x-init=" $watch('$store.global_data.products', (value, oldValue) => { console.log('oldValue = ', oldValue); console.log('value = ', value); }) " > <template x-for="(product, index) in $store.global_data.products"> <div> <span x-text="product.name"></span> : <input x-model="product.price_USD" /> USD = <input x-model="product.price_INR" /> INR </div> </template> </div> <script defer src="https://unpkg.com/alpinejs@3.12.3/dist/cdn.min.js"></script>
问题原因
你监听的是引用类型(数组),AlpineJS的$watch默认是浅监听逻辑:只有当被监听对象的引用发生改变时(比如给products重新赋值一个新数组),才会认为值发生变化。而修改数组内部元素的属性时,数组本身的引用没有改变,所以$watch触发时,oldValue和value指向的是同一个数组对象,打印出来的自然是相同的值。
解决方案
方案1:开启深监听
给$watch添加第三个参数{ deep: true },开启后会递归监听数组内部的所有变化,并且会克隆旧值作为oldValue传递(注意:深监听会带来一定性能开销,适合数据量不大的场景)。
修改后的x-init代码:
<div x-data="" x-init=" $watch('$store.global_data.products', (value, oldValue) => { console.log('oldValue = ', oldValue); console.log('value = ', value); }, { deep: true }) " > <!-- 原有页面内容不变 --> </div>
方案2:监听具体属性
如果只需要关注单个产品的价格变化,可以直接监听数组中每个元素的具体属性,结合x-for动态绑定监听目标:
修改后的HTML代码:
<div x-data=""> <template x-for="(product, index) in $store.global_data.products"> <div x-init=" // 监听USD价格变化 $watch(`$store.global_data.products[${index}].price_USD`, (newVal, oldVal) => { console.log(`${product.name}的USD价格:旧值${oldVal} → 新值${newVal}`); }); // 监听INR价格变化 $watch(`$store.global_data.products[${index}].price_INR`, (newVal, oldVal) => { console.log(`${product.name}的INR价格:旧值${oldVal} → 新值${newVal}`); }); "> <span x-text="product.name"></span> : <input x-model="product.price_USD" /> USD = <input x-model="product.price_INR" /> INR </div> </template> </div> <script defer src="https://unpkg.com/alpinejs@3.12.3/dist/cdn.min.js"></script>
方案3:通过Store方法修改数据(推荐)
如果需要实现价格联动(比如USD变化自动计算INR),可以在Store中封装修改数据的方法,统一控制数据变更逻辑,同时也能让$watch更准确地捕获变化:
更新Store代码
document.addEventListener('alpine:init', () => { Alpine.store('global_data', { products: [ { name: 'batmobile', price_USD: 20, price_INR: 1660, // 20 x 83 }, { name: 'herbie', price_USD: 35, price_INR: 2905, // 35 x 83 }, { name: 'delorean', price_USD: 51, price_INR: 4233, // 51 x 83 }, ], // 更新USD价格并同步计算INR updateUSD(index, newUSD) { const exchangeRate = 83; this.products[index].price_USD = Number(newUSD); this.products[index].price_INR = this.products[index].price_USD * exchangeRate; }, // 更新INR价格并同步计算USD updateINR(index, newINR) { const exchangeRate = 83; this.products[index].price_INR = Number(newINR); this.products[index].price_USD = Math.round(this.products[index].price_INR / exchangeRate); } }); });
更新HTML代码
<div x-data="" x-init=" $watch('$store.global_data.products', (value, oldValue) => { console.log('oldValue = ', oldValue); console.log('value = ', value); }, { deep: true }) " > <template x-for="(product, index) in $store.global_data.products"> <div> <span x-text="product.name"></span> : <input x-model="product.price_USD" @input="$store.global_data.updateUSD(index, $event.target.value)" /> USD = <input x-model="product.price_INR" @input="$store.global_data.updateINR(index, $event.target.value)" /> INR </div> </template> </div> <script defer src="https://unpkg.com/alpinejs@3.12.3/dist/cdn.min.js"></script>
内容的提问来源于stack exchange,提问作者anjanesh
相关产品推荐
相关产品推荐

