步骤一:先看原来的问题
这个案例来自一个 Rails 后台订单详情页。页面上要展示订单金额、优惠券、物流节点,还要给一段前端脚本用这些数据初始化图表。老写法是在 ERB 里拼一段 script,把 Ruby 变量直接塞进 JS。刚开始确实快,十几行就能跑。
问题出在维护阶段。订单备注里出现引号,脚本报错;金额字段有时是 BigDecimal,有时转成字符串,前端判断混乱;页面局部缓存以后,某些变量还拿到旧值。这个时候再做 gon对比,就不是选一个“更潮”的工具,而是看谁能把数据边界说清楚。
gon对比这件事,不能只看“能不能把后端变量丢给前端”。我用一个 Rails 订单页改造案例讲清楚:gon、data 属性、接口请求、内联 script 各自适合什么场景,哪些看着省事,后面会让你补坑。
这个案例来自一个 Rails 后台订单详情页。页面上要展示订单金额、优惠券、物流节点,还要给一段前端脚本用这些数据初始化图表。老写法是在 ERB 里拼一段 script,把 Ruby 变量直接塞进 JS。刚开始确实快,十几行就能跑。
问题出在维护阶段。订单备注里出现引号,脚本报错;金额字段有时是 BigDecimal,有时转成字符串,前端判断混乱;页面局部缓存以后,某些变量还拿到旧值。这个时候再做 gon对比,就不是选一个“更潮”的工具,而是看谁能把数据边界说清楚。
第一种是内联 script,优点是直接,缺点是转义、安全和可读性都靠人肉把关。第二种是 HTML data 属性,适合少量、跟某个 DOM 节点强绑定的数据,比如 data-order-id、data-status。第三种是前端再调接口,边界最清楚,但页面首屏多一次请求,后台内网页面未必值得。
第四种就是 gon。它的定位很窄:在 Rails controller 或 view 里设置变量,页面渲染时把这些变量安全地挂到 window.gon 上,前端直接读。它不是状态管理,也不是接口替代品,别把它想得太大。
我没有一上来全站替换,只挑订单详情页的图表初始化数据。controller 里写 gon.order_chart = { total: ..., paid: ..., refunded: ... },前端入口只读取 gon.order_chart。改完以后最明显的变化是:ERB 里那段难看的 JS 字符串拼接消失了。
这里有个小窍门:gon 里只放“页面初始化必须用”的数据,不放整张订单对象。比如订单有 40 个字段,图表只用 4 个,就只传 4 个。这样前端拿到的数据干净,后端也不容易不小心泄露手机号、地址、内部备注。
改到一半时,团队有人问:这些数据为什么不用 data 属性?这个问题很关键。我的判断标准是:数据如果服务于某个按钮或组件,用 data 属性;数据如果服务于整个页面初始化,用 gon。比如“取消订单”按钮需要 order_id,用 data-order-id 很合适;图表需要一组统计值,用 gon 更舒服。
这次 gon对比下来,data 属性没有输,它只是边界不同。很多项目乱,是因为把所有后端数据都塞到一个方案里。真正好维护的写法,往往是两三个简单方案各司其职。
最后我们定了三条规则:少量 DOM 相关数据走 data 属性;页面首屏必须有、且结构稍复杂的数据走 gon;会变化、需要分页、需要权限校验的数据走 API。这样一分,前端和后端都少吵很多。
如果你也在做 gon对比,我建议别从“哪个高级”开始想。先拿一个真实页面,把数据按用途拆开。gon 最适合解决的是 Rails 页面渲染时的初始化数据传递,不适合承担长期交互数据源。认清这个边界,它就很好用。