Rails 白话文讲 Stimulus

lanzhiheng · 2021年06月17日 · 最后由 sitoto 回复于 2021年06月25日 · 803 次阅读

上一篇文章以白话文的形式简单讲了一下 Turbo Streams,这篇文章简单讲讲 Stimulus,我会针对一些较为常见的应用场景,提供代码示例。原文链接 https://www.lanzhiheng.com/posts/stimulus-simple-talking

Turbo 的问题

上一篇文章简单分析了一下 Turbo Streams,相比于 Turbolinks,Turbo无疑改善了许多。它的三个模块 Turbo Drive, Turbo Frames, Turbo Streams 各司其职,对传统的 Rails 页面来说,依赖这三者就能够写出交互性较好的页面,却不需要依赖任何 JavaScript 代码。

  1. Turbo Drive 通过页面拦截的方式,让原来的全页面刷新变成异步获取页面数据,并进行页面 “合并”,大大提升了交互效果。然而每次都获取全页面数据太过于耗费网络资源,如果想要对页面进行更细粒度的控制,则需要依赖 Turbo Frames。
  2. Turbo Frames 可以把页面分割成一个个的区域,默认情况下,区域内触发的动作只会影响区域内的内容。Turbo Frames 可以根据后端返回的 HTML 数据对区域的内容进行替换。对于局部交互而言十分便利,然而你如果想要控制区域外的内容,则需要通过data-turbo-frame="_top"这类属性来做 Turbo Drive 所干的事情,进行全页面获取并内容合并。如果想要更好地控制其他区域的内容,则需要依赖 Turbo Streams。
  3. Turbo Streams 可以根据响应返回的 HTML 数据,在页面中它可以对所需要作用的对象进行append, prepend, replace, update, remove五种操作。有点声明式编程的感觉,它能够满足我们日常生活中对 DOM 的大部分操作需求。

Turbo Drive -> Turbo Frames -> Turbo Streams,它们对页面的操控能力一步步增强,或者这也是渐进性增强页面的开发模式吧。先作出一个可用的网站,然后通过上述技术逐步提升它们的交互水准,而不是像许多现代前端框架那样一开始就采用分离政策。

然而有些场景我们还真的必须要用 JavaScript 来解决,这个时候社区祭出了Stimulus

什么是 Stimulus

好吧,这里不得不说Stimulus也是前端大多数框架中的其中一个,只不过它的野心没那么大。

Stimulus is a JavaScript framework with modest ambitions.

如今绝大多数的前端框架都有自己的模板引擎比如 React 的 JSX,Vue 的模板语法,反正各看各的不顺眼,现在 Vue 还能叫渐进式 JavaScript 框架吗?我表示怀疑。不过 Stimulus 它没有自己的模板引擎,它始终都在操作 DOM。说白了它的模板始终都是最最最原始的 HTML。它只是提供了一些功能让我们操作 DOM 的时候能够更加得心应手。

Stimulus’s use of data attributes helps separate content from behavior in the same way CSS separates content from presentation. Further, Stimulus’s conventions naturally encourage you to group related code by name.

就是你在 HTML 的元素上定义了class="btn",然后就能够通过对应的 CSS 代码来为这个元素添加样式。而你在 HTML 元素上定义了data-controller='hello',然后就能通过对应的 JS 控制器来管理这个元素以及它里面的东西。

<div data-controller="hello" id='me'>
  <input type="text">
  <button>Greet</button>
</div>
// src/controllers/hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  connect() {
    console.log("Hello, Stimulus!", this.element) // this.element对应了id='me'那个元素
  }
}

要说复杂,其实它也没有多复杂,以前我们要用 JavaScript 来捕获一个元素,并对它进行操作可能要这样写代码吧

document.addEventListener('turbo:load', function() {
  const element = document.querySelector('[data-controller="hello"]')
  // 各种骚操作
})

用了 Stimulus,让我们避免这一堆document.addEventListener散落在各个文件中。代码更加模块化。另外我们还能够对controler里面关键的元素打target以及index,操作起来会更加便利。

<div data-controller="hello" id='me' data-hello-index-value='4444'>
  <input type="text" value='12345' data-hello-target='input' />
  <button>Greet</button>
</div>
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = ['input']
  static values = { index: Number }

  connect() {
    console.log(this.element.id); // => 'me'
    console.log(this.inputTarget.value); // => 12345
    console.log(this.indexValue); // => 4444
  }
}

官方针对它们提供了剪贴板案例以及状态管理案例,看完就知道它们两者是怎么回事了。

在个人看来 Stimulus 有别于传统的前端大框架的地方在于,它更像是一个辅助工具,而不是一个大的工具包,它让你操作 DOM 节点变得更方便,其他的事情就交给开发者自由发挥吧。

生命周期

最后还是要提一下我最喜欢 Stimulus 的生命周期这个概念,其实在 React,Vue 这些框架里面早就有相关的概念,只是它们要复杂许多,而 Stimulus 只有三个生命周期函数。有了这个机制,对比于传统的 JQuery 模式的编码模式,在 Hotwire 上我们能省很多事情,我举个例子,select2的使用场景,在网站上一般是这样的

<select class="js-select2" name="state">
  <option value="AL">Alabama</option>
  <option value="WY">Wyoming</option>
</select>
document.addEventListener("turbo:load", () => {
  $('.js-select2').select2()
})

看起来没毛病,然而在引入了 Turbo 的网站里面,只需要简单的浏览器后退前进就会出问题,重复几次之后,你会看到很多很多的 select2 组件。

Screen Shot 2021-06-17 at 10.44.36 AM.png

因为 Turbo 有缓存机制,你每次先后退前进它其实会在缓存中拿 “老” 页面,而这个页面中的对应组件其实已经经过select2处理了,你相当于重复调用$('.js-select2').select2(),因此会有多个重复组件。你也可以通过关闭缓存来解决这个问题,不过,这也太不好了吧,毕竟这样每次后退前进都会发送网络请求,其实没太大必要。

我们可以在页面加入缓存的时候把 select2 组件注销掉

document.addEventListener("turbo:before-cache", () => {
  $('.js-select2').select2('destroy')
})

这样确实能够解决缓存所导致的 select2 组件多次初始化的问题了。不过也会有其他问题,比如有些时候你需要通过 Turbo Streams 往表单里面插入<select class="js-select2">相关的元素,它并不会自动变成 select2 组件,因为上面的turbo:load监听函数没有被调用,这样要处理起来就复杂了。

这个时候如果把初始化成 select2 组件这个事情用 Stimulus 来解决,借助 Stimulus 的生命周期函数,一切事情就简单了。触发相关机制在这一节文档有说。因此上面的一切都可以简化成

<select data-controller='normal-select2'>
  <option value="AL">Alabama</option>
  <option value="WY">Wyoming</option>
</select>
// app/packs/controllers/normal_select2_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  connect() {
    $(this.element).select2()
  }

  disconnect() {
    $(this.element).select2('destroy')
  }
}

一劳永逸。除了Select2之外还有许多传统的 JS 组件,比如Slick在结合 Turbo 使用的时候都会因缓存导致各种奇奇怪怪的问题,这些都可以借助 Stimulus 的力量来统一解决。如果有个别组件需要特殊处理,再给它们写专属的 controler 就好了。

尾声

这篇文章简单分析了一下 Turbo 三个模块的作用以及它们的局限性,对于它们解决不了的问题我们必须要编写 JavaScript 代码来解决,Stimulus 能够很好帮助你做这个事情。有别于流行的 JS 框架,Stimulus 并没有那么大的野心,所以它更加简单。我也在本文讲解了它的主要功能以及应用场景。当然都是些比较基础的用法,有更有趣的用法,后面会再讲。

老哥你是真能写啊

jicheng1014 回复

😂 明天还有。

特别推荐 Stimulus, 非常好用

suupic 回复

正在慢慢一步步应用到项目中。

lanzhiheng 回复

最近你产量很多啊😃

zhugexinxin 回复

😂 还好,就顺便总结一下。

五种操作 两个 update , 有个是 replace 吧

lewislinlin 回复

我写错了,改过来了。感谢。

如果项目时间不是那么紧张,感觉还是不用 stimulus 的好

需要 登录 后方可回复, 如果你还没有账号请 注册新账号