电话咨询
QQ咨询
微信咨询
返回顶部

微信小程序计算属性:3步优化数据逻辑,减少40%冗余代码

微信小程序的开发中,计算属性这个概念其实并不像 Vue 那样有原生内置的 computed 字段。很多从 Vue 转过来的开发者会习惯性地在 WXML 里直接写复杂的表达式,或者在 JS 里用函数返回数据,结果发现性能不佳或者代码难以维护。这里我要讲的计算属性,并不是一个固定的 API,而是一种**数据派生与缓存**的编程思想。在小程序里,我们通过 wxsobservers 以及 setData 的巧妙配合,来实现真正意义上的“计算属性”。

一、理解计算属性的核心:派生数据与缓存

先纠正一个常见误区:觉得在 data 里放一个函数,每次在模板里调用这个函数,就是计算属性。这其实是方法,不是计算属性。计算属性的关键特征是“依赖追踪”和“缓存”。比如在 Vue 中,你修改了 firstNamefullName 会自动更新,且只要 firstName 不变,多次读取 fullName 不会重复执行函数。

在小程序里,原生没有这种机制。但我们可以用 observers 监听数据变化,然后手动更新一个派生字段。举个例子:你有一个商品列表,每个商品有 pricecount,你想在页面上显示总价。最笨的办法是在每次用户点击加号时,遍历列表重新计算,然后把总价 setData 进去。这没问题,但如果页面有 20 个这样的计算字段,代码会变得非常混乱。

更好的做法是:在 data 里声明一个 totalPrice,然后在 observers 里监听 goodsList 的变化,一旦 goodsList 变了(比如数量或价格变了),就自动重新计算 totalPrice 并更新。这样,你只需要维护 goodsList 这一个数据源,所有的派生数据都自动跟着走。

二、利用 observers 实现依赖更新(最像计算属性的方案)

具体操作步骤:

  1. Pagedata 中定义原始数据 goodsList 和计算后的数据 totalPrice(初始值设为 0)。
  2. observers 中监听 goodsList 的变化,注意要用完整路径 'goodsList.**' 来监听内部字段变化,否则只监听数组引用变化。
  3. 在监听回调里,用 this.data.goodsList.reduce 计算总价,然后 this.setData({ totalPrice })

代码示例:

Page({
  data: {
    goodsList: [{ price: 10, count: 2 }, { price: 20, count: 1 }],
    totalPrice: 0
  },
  observers: {
    'goodsList.**': function() {
      const total = this.data.goodsList.reduce((sum, item) => sum + item.price * item.count, 0);
      this.setData({ totalPrice: total });
    }
  }
})

这样,无论你是通过按钮修改 count,还是从后端拉取新列表,只要 goodsList 内部任何数据变动,totalPrice 都会自动更新。而且这个计算只发生在数据变化时,不是每次渲染都执行,性能比在模板里写函数好得多。

三、wxs 计算属性:在视图层直接派生(适合纯展示)

有些场景下,你不想把派生数据存到 data 里,因为可能只用一次,或者数据量很大,存到 data 会增加内存。这时候可以用 wxs 来实现“视图层的计算属性”。wxs 运行在渲染层,与逻辑层隔离,不能调用 setData,但可以接受参数并返回计算结果。

举个例子:你有一个时间戳 timestamp,需要在页面上显示为“刚刚”、“5分钟前”、“昨天”这种相对时间。如果用 observers 计算,每秒钟更新一次会非常消耗性能。但用 wxs 函数,只在页面渲染时计算一次,而且 wxs 函数是纯函数,没有副作用。

操作步骤:

  1. 新建一个 .wxs 文件,比如 utils.wxs,里面写一个 formatTime 函数。
  2. 在 WXML 中引用这个 wxs 模块,然后直接调用 formatTime(timestamp)
  3. 注意:wxs 不支持 ES6 语法,只能用 var 和 function。

代码示例:

// utils.wxs
function formatTime(timestamp) {
  var now = getDate().getTime();
  var diff = now - timestamp;
  if (diff < 60000) return '刚刚';
  if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前';
  if (diff < 86400000) return Math.floor(diff / 3600000) + '小时前';
  return '几天前';
}
module.exports = { formatTime: formatTime };
<wxs src="../../utils/utils.wxs" module="utils" />
<view>{{ utils.formatTime(item.timestamp) }}</view>

这种方式的优势是:不占用逻辑层数据,适合高频渲染的派生数据。但缺点也很明显:wxs 函数无法响应数据变化,每次重新渲染都会重新执行,没有缓存。所以它更适合那些依赖不变或者变化不频繁的数据(比如固定格式的文本)。

四、对比:什么时候用 observers,什么时候用 wxs?

很多开发者会纠结这个问题。我直接给一个判断标准:

  • 如果派生数据会被多次使用(比如在多个地方显示总价),或者需要被其他计算属性依赖(比如总价再打折后的价格),就用 observers 把它存到 data 里。这样其他计算属性可以直接读取,不用重复计算。
  • 如果派生数据只在一个地方展示,而且计算逻辑不复杂(比如格式化时间、拼接字符串),就用 wxs。这样可以减少 data 中的冗余字段,避免 setData 的额外开销。

还有一种特殊情况:你需要根据多个数据源计算一个值,但这些数据源变化频率不同。比如一个页面同时显示“商品总数”和“已选商品总价”。如果都用 observers,你需要写两个监听器,而且可能一个变化导致另一个也连锁更新。这时候可以用 computed 的模拟思路:在 observers 里监听多个字段,然后一次性更新多个计算属性。

五、扩展:如何实现计算属性的缓存(类似 Vue 的 computed)

小程序原生没有缓存机制,但我们可以手动实现一个简单的缓存。比如在 observers 里,每次计算前先判断依赖是否真的变了。如果依赖没变,就不执行计算。听起来有点多余,因为 observers 本身就是变化才触发,但有一种情况:你监听的是对象,但对象内部某个字段变了,整个对象引用没变,observers 可能不会触发(取决于你用的路径)。

更实用的缓存场景是:页面有多个计算属性,它们依赖相同的数据源。比如 totalPriceaveragePrice 都依赖 goodsList。你可以在 observers 里一次计算两个值,然后一起 setData。这样比分别监听两次要高效。

代码示例:

observers: {
  'goodsList.**': function() {
    const list = this.data.goodsList;
    const total = list.reduce((sum, item) => sum + item.price * item.count, 0);
    const count = list.reduce((sum, item) => sum + item.count, 0);
    this.setData({
      totalPrice: total,
      averagePrice: count ? (total / count).toFixed(2) : 0
    });
  }
}

这样,一次数据变化,两个计算属性都更新了。而且因为 setData 是合并的,不会触发两次渲染。

六、一个容易踩的坑:observers 的无限循环

新手最容易犯的错误是在 observers 里修改了被监听的数据本身。比如监听 goodsList,然后在回调里又 setData({ goodsList: newList }),这会导致无限循环。解决方法:要么确保修改的是派生字段(不是监听字段本身),要么用 if 判断数据是否真的变了再更新。

另一个坑是:observers 在页面初始化时就会执行一次,如果计算逻辑里有异步操作(比如请求接口),要小心初始数据可能为空。建议在 onLoad 里先设置好初始数据,再让 observers 自动触发。

七、实际项目中的最佳实践

我参与的一个电商小程序里,购物车页面有 5 个计算属性:总数量、总价、优惠后价格、运费、实付金额。我们全部用 observers 实现,监听 cartListcouponInfo 两个数据源。每次用户修改数量或选择优惠券,所有计算属性自动更新。这样做的好处是:业务逻辑集中在 observers 里,页面模板只负责展示,setData 只更新派生字段,逻辑清晰且性能可控。

而像商品详情页的“已售 xx 件”这种展示,我们用的 wxs 函数,因为只需要格式一下数字(比如 1234 变成“1.2千”),而且只在初次渲染时用一次,没必要存到 data 里。

最后提醒一点:不要为了用计算属性而用。如果你的数据源只有一条,或者计算逻辑极其简单(比如 {{ a + b }}),直接在模板里写表达式即可。小程序的模板引擎虽然不支持复杂运算,但简单的加减乘除和三元运算符是没问题的。过度设计反而会增加代码复杂度。

上一篇
想搞个小程序赚钱,却发现能做的功能都被大厂包圆了,普通人还有哪些真正能落地、不踩坑的玩法?
下一篇
做了半天内容,卡在上传小程序这步?手把手教你避坑!