• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

Stimulus刺激框架简介

有很多javascript框架。有时我什至开始认为我是唯一一个还没有创建框架的人。一些解决方案,比如angular,庞大而复杂,而一些,比如backbone(它更像是一个库而不是一个框架),非常简单,只提供了一些工具来加速开发过程。

在今天的文章中,我想向您介绍一个名为Stimulus的全新框架。它是由 David Heinemeier Hansson 领导的Basecamp团队创建的,他是一位受欢迎的开发人员,他是 Ruby on Rails之父。

Stimulus 是一个小框架,从未打算成长为大框架。它对前端开发有自己的理念和态度,一些程序员可能喜欢或不喜欢。Stimulus 还很年轻,但版本 1 已经发布,因此在生产中使用应该是安全的。我已经玩过这个框架了,真的很喜欢它的简单和优雅。希望你也会喜欢它!

在这篇文章中,我们将讨论Stimulus的基础知识,同时创建一个具有异步数据加载、事件、状态持久性和其他常见事物的单页应用程序

源代码可以在GitHub上找到。

刺激简介

Stimulus由 Basecamp 的开发人员创建。他们没有创建单页 JavaScript 应用程序,而是决定选择一个 Turbolinks和一些 javaScript提供支持的庞大单体。这段 JavaScript 代码演变成一个小而适中的框架,不需要您花费数小时学习它的所有概念和注意事项。

Stimulus 主要用于将自身附加到现有的dom元素并以某种方式与它们一起工作。但是,也可以动态呈现内容。总而言之,这个框架与其他流行的解决方案有很大不同,例如,它在 html 中保留状态,而不是在 JavaScript 对象中。一些开发人员可能会觉得这很不方便,但请给 Stimulus 一个机会,因为它真的可能会让您大吃一惊。

该框架只有三个您应该记住的主要概念,它们是:

  • 控制器:带有一些附加到 DOM 的方法和回调的JS 类。页面上出现“魔术”属性时,附件就会发生。data-controller文档解释说这个属性是 HTML 和 JavaScript 之间的桥梁,就像类充当 HTML 和css之间的桥梁一样。一个控制器可以连接多个元素,一个元素可以由多个控制器供电。

  • Actions:在特定事件上调用的方法。它们在特殊data-action属性中定义。

  • 目标:可以轻松访问和操作的重要元素。它们是在data-target属性的帮助下指定的。

如您所见,上面列出的属性 允许您以非常简单自然的方式将内容与行为逻辑分开。在本文后面,我们将看到所有这些概念的实际应用,并注意阅读 HTML 文档和理解发生的事情是多么容易。

引导刺激应用程序

Stimulus 可以很容易地作为NPM 包script安装或通过标签直接加载, 如文档中所述另请注意,默认情况下,此框架与 webpack 资产管理器集成,它支持控制器自动加载等好东西。您可以自由使用任何其他构建系统,但在这种情况下 需要做更多的工作。

开始使用 Stimulus 的最快方法是利用这个已经连接了Express Web 服务器Babel的启动项目。它也依赖于Yarn,所以一定要安装它。要克隆项目并安装其所有依赖项,请运行:

git clone https://github.com/stimulusjs/stimulus-starter.git
cd stimulus-starter
yarn install

如果你不想在本地安装任何东西,你可以在 Glitch 上重新混合这个项目,并在你的浏览器中完成所有的编码。

太好了——我们都准备好了,可以继续下一部分了!

一些标记

假设我们正在创建一个小型单页应用程序,它显示员工列表并加载诸如姓名、照片、职位、薪水、生日等信息。

让我们从员工名单开始。我们要编写的所有标记都应该放在public/index.html文件中,该文件已经包含一些非常小的 HTML。目前,我们将通过以下方式对所有员工进行硬编码:

 <h1>Our employees</h1>
 
  <div>
    <ul>
      <li><a href="#">John Doe</a></li>
      <li><a href="#">Alice Smith</a></li>
      <li><a href="#">Will Brown</a></li>
      <li><a href="#">Ann Grey</a></li>
    </ul>
  </div>

好的!现在让我们添加一点刺激魔法。

创建控制器

正如官方文档所解释的,Stimulus 的主要目的是将 JavaScript 对象(称为控制器)连接到 DOM 元素。然后,控制器将使页面栩栩如生。按照惯例,控制器的名称应该以后缀结尾_controller(Rails 开发人员应该非常熟悉)。

有一个已经可用的控制器目录,称为src/controllers在里面,你会发现一个  hello_controller.js定义了一个空类的文件:

import { Controller } from "stimulus"

export default class extends Controller {

}

让我们将此文件重命名为employees_controller.js我们不需要特别要求它,因为控制器会自动加载,这要归功于src/index.js文件中的以下代码行:

const application = Application.start()
const context = require.context("./controllers", true, /\.js$/)
application.load(definitionsFromContext(context))

下一步是将我们的控制器连接到 DOM。为此,设置一个data-controller属性并为其分配一个标识符(employees在我们的例子中):

<div data-controller="employees">
  <ul>
    <!-- your list -->
  </ul>
</div>

而已!控制器现在已附加到 DOM。

生命周期回调

关于控制器需要了解的一件重要事情是,它们具有三个在特定条件下触发的生命周期回调

  • initialize: 这个回调只发生一次,当控制器被实例化时。

  • connect: 每当我们将控制器连接到 DOM 元素时触发。由于一个控制器可能会连接到页面上的多个元素,因此该回调可能会运行多次。

  • disconnect: 你可能已经猜到了,只要控制器与 DOM 元素断开连接,这个回调就会运行。

没什么复杂的,对吧?让我们利用initialize()connect()回调来确保我们的控制器确实有效:

// src/controllers/employees_controller.js

export default class extends Controller {
  initialize() {
    console.log('Initialized')
    console.log(this)
  }

  connect() {
    console.log('Connected')
    console.log(this)
  }
}

接下来,通过运行启动服务器:

yarn start

导航到http://localhost:9000打开浏览器的控制台并确保显示两条消息。这意味着一切都按预期工作!

添加事件

下一个核心刺激概念是事件事件用于响应页面上的各种用户操作:单击、悬停、聚焦等。Stimulus 不尝试重新发明自行车,其事件系统基于通用 JS 事件。

例如,让我们将点击事件绑定到我们的员工。每当发生此事件时,我想调用尚未存在choose()的方法employees_controller

  <ul>
    <li><a href="#" data-action="click->employees#choose">John Doe</a></li>
    <li><a href="#" data-action="click->employees#choose">Alice Smith</a></li>
    <li><a href="#" data-action="click->employees#choose">Will Brown</a></li>
    <li><a href="#" data-action="click->employees#choose">Ann Grey</a></li>
  </ul>

大概,你可以自己理解这里发生了什么。

  • data-action是将事件绑定到元素并解释应该调用什么动作的特殊属性。

  • click,当然是事件的名称。

  • employees是我们控制器的标识符。

  • choose是我们要调用的方法的名称。

由于click是最常见的事件,因此可以安全地省略:

<li><a href="#" data-action="employees#choose">John Doe</a></li>

在这种情况下, click将隐式使用。

接下来,让我们编写choose()方法。我不希望发生默认操作(显然,打开href属性中指定的新页面),所以让我们阻止它:

// src/controllers/employees_controller.js

// callbacks here...

  choose(e) {
    e.preventDefault()
    console.log(this)
    console.log(e)
  }

e是包含有关触发事件的完整信息的特殊事件对象。请注意,顺便说一句,它this返回控制器本身,而不是单个链接!为了获得对作为事件目标的元素的访问权限,请使用e.target.

重新加载页面,单击列表项,然后观察结果!

与***合作

现在我们已经为员工绑定了一个点击事件处理程序,我想存储当前选择的人。为什么?存储此信息后,我们可以防止第二次选择同一员工。这将使我们稍后也可以避免多次加载相同的信息。

Stimulus 指示我们在Data api中保持状态,这似乎很合理。首先,让我们使用 data-id属性为每个员工提供一些任意的 id:

  <ul>
    <li><a href="#" data-id="1" data-action="employees#choose">John Doe</a></li>
    <li><a href="#" data-id="2" data-action="click->employees#choose">Alice Smith</a></li>
    <li><a href="#" data-id="3" data-action="click->employees#choose">Will Brown</a></li>
    <li><a href="#" data-id="4" data-action="click->employees#choose">Ann Grey</a></li>
  </ul>

接下来,我们需要获取id 并将其持久化。在 Stimulus 中使用 Data api很常见,因此this.data为每个控制器提供了一个特殊的对象。在它的帮助下,我们可以运行以下方法:

  • this.data.get('name'):通过其属性获取值。

  • this.data.set('name', value):设置某个属性下的值。

  • this.data.has('name'): 检查属性是否存在(返回一个布尔值)。

不幸的是,这些快捷方式不适用于点击事件的目标,因此我们必须坚持使用getAttribute()它们:

  // src/controllers/employees_controller.js
  choose(e) {
    e.preventDefault()
    this.data.set("current-employee", e.target.getAttribute('data-id'))
  }

但是我们可以通过创建一个 getter 和一个 setter 来做得更好currentEmployee

  // src/controllers/employees_controller.js
  get currentEmployee() {
    return this.data.get("current-employee")
  }

  set currentEmployee(id) {
    if (this.currentEmployee !== id) {
      this.data.set("current-employee", id)
    }
  }

请注意我们如何使用 this.currentEmployeegetter 并确保提供的 id 与已存储的不同。

现在您可以通过以下方式重写该choose()方法:

  // src/controllers/employees_controller.js
  
  choose(e) {
    e.preventDefault()
    this.currentEmployee = e.target.getAttribute('data-id')
  }

重新加载页面以确保一切正常。您还不会注意到任何视觉上的变化,但在 Inspector 工具的帮助下,您会注意到当您单击链接时,ul该属性的值会发生变化。data-employees-current-employee属性名称中的employees部分是控制器的标识符,并且会自动添加。

现在让我们继续并突出显示当前选择的员工。

使用目标

选择员工后,我想为相应的元素分配一个.chosen类。当然,我们可能已经通过使用一些 JS 选择器函数解决了这个任务,但是 Stimulus 提供了一个更简洁的解决方案。

满足目标,允许您在页面上标记一个或多个重要元素。然后可以根据需要轻松访问和操作这些元素。为了创建一个目标,添加一个data-target值为 的属性{controller}.{target_name}(称为 目标描述符):

  <ul data-controller="employees">
    <li><a href="#" data-target="employees.employee"
      data-id="1" data-action="employees#choose">John Doe</a></li>

    <li><a href="#" data-target="employees.employee"
      data-id="2" data-action="click->employees#choose">Alice Smith</a></li>

    <li><a href="#" data-target="employees.employee"
      data-id="3" data-action="click->employees#choose">Will Brown</a></li>
      
    <li><a href="#" data-target="employees.employee"
      data-id="4" data-action="click->employees#choose">Ann Grey</a></li>
  </ul>

现在通过定义一个新的静态值让 Stimulus 了解这些新目标:

// src/controllers/employees_controller.js

export default class extends Controller {
  static targets = [ "employee" ]
  
  // ...
}

我们现在如何访问目标?就像说this.employeeTarget(获取第一个元素)或this.employeeTargets(获取所有元素)一样简单:

  // src/controllers/employees_controller.js
  choose(e) {
    e.preventDefault()
    this.currentEmployee = e.target.getAttribute('data-id')
    console.log(this.employeeTargets)
    console.log(this.employeeTarget)
  }

伟大的!这些目标现在如何帮助我们?好吧,我们可以根据一些标准使用它们轻松地添加和删除 CSS 类:

 // src/controllers/employees_controller.js
  
  choose(e) {
    e.preventDefault()
    this.currentEmployee = e.target.getAttribute('data-id')
    
    this.employeeTargets.forEach((el, i) => {
      el.classList.toggle("chosen", this.currentEmployee === el.getAttribute("data-id"))
    })
  }

这个想法很简单:我们遍历一个目标数组,并将每个目标与data-id存储在this.currentEmployee如果它匹配es,则为元素分配.chosen类。否则,将删除此类。您也可以 if (this.currentEmployee !== id) {从 setter 中提取条件并在chosen()方法中使用它:

  // src/controllers/employees_controller.js
  
  choose(e) {
    e.preventDefault()
    const id = e.target.getAttribute('data-id')

    if (this.currentEmployee !== id) { // <---
      this.currentEmployee = id

      this.employeeTargets.forEach((el, i) => {
        el.classList.toggle("chosen", id === el.getAttribute("data-id"))
      })
    }
  }

看起来不错!最后,我们将为.chosen类中的类提供一些非常简单的样式public/main.css

.chosen {
  font-weight: bold;
  text-decoration: none;
  cursor: default;
}

再次重新加载页面,单击一个人,并确保该人被正确突出显示。

异步加载数据

我们的下一个任务是加载有关所选员工的信息。在现实世界的应用程序中,您必须设置一个 托管服务提供商、一个由DjangoRails等支持的后端,以及一个使用包含所有必要数据的 JSON 进行响应的API 端点但是我们将让事情变得更简单一些,并且只关注客户端。在文件夹下创建一个employees目录public接下来,添加四个包含个别员工数据的文件:

1.json

{
  "name": "John Doe",
  "gender": "male",
  "age": "40",
  "position": "CEO",
  "salary": "$120.000/year",
  "image": "https://burst.shopifycdn.com/photos/couple-in-love-at-sunset_373x.jpg"
}

2.json

{
  "name": "Alice Smith",
  "gender": "female",
  "age": "32",
  "position": "CTO",
  "salary": "$100.000/year",
  "image": "https://burst.shopifycdn.com/photos/woman-listening-at-team-meeting_373x.jpg"
}

3.json

{
  "name": "Will Brown",
  "gender": "male",
  "age": "30",
  "position": "Tech Lead",
  "salary": "$80.000/year",
  "image": "https://burst.shopifycdn.com/photos/casual-urban-menswear_373x.jpg"
}

4.json

{
  "name": "Ann Grey",
  "gender": "female",
  "age": "25",
  "position": "Junior Dev",
  "salary": "$20.000/year",
  "image": "https://burst.shopifycdn.com/photos/woman-using-tablet_373x.jpg"
}

所有照片均取自 Shopify 名为Burst的免费图库摄影。

我们的数据已准备就绪,等待加载!为此,我们将编写一个单独的 loadInfoFor()方法:

  // src/controllers/employees_controller.js
  
  loadInfoFor(employee_id) {
    fetch(`employees/${employee_id}.json`)
    .then(response => response.text())
    .then(json => { this.displayInfo(json) })
  }

此方法接受员工的 id 并向给定的 URI发送异步获取请求。还有两个 Promise:一个是获取正文,另一个是显示加载的信息(稍后我们将添加相应的方法)。

在里面使用这个新方法choose()

  // src/controllers/employees_controller.js
  
  choose(e) {
    e.preventDefault()
    const id = e.target.getAttribute('data-id')

    if (this.currentEmployee !== id) {
      this.loadInfoFor(id)
      // ...
    }
  }

在对方法进行编码之前displayInfo(),我们需要一个元素来实际呈现数据。为什么我们不再次利用目标?

<!-- public/index.html -->

<div data-controller="employees">
  <div data-target="employees.info"></div>
  <ul>
    <!-- ... -->      
  </ul>
</div>

定义目标:

// src/controllers/employees_controller.js

export default class extends Controller {
  static targets = [ "employee", "info" ]
  // ...
}

现在利用它来显示所有信息:

  // src/controllers/employees_controller.js
  
  displayInfo(raw_json) {
    const info = JSON.parse(raw_json)
    const html = `<ul><li>Name: ${info.name}</li><li>Gender: ${info.gender}</li><li>Age: ${info.age}</li><li>Position: ${info.position}</li><li>Salary: ${info.salary}</li><li><img src="${info.image}"></li></ul>`
    this.infoTarget.innerHTML = html
  }

当然,您可以自由地使用像handlebars这样的模板引擎,但对于这种简单的情况,这可能是矫枉过正的。

现在重新加载页面并选择其中一名员工。他的简历和图片应该几乎立即加载,这意味着我们的应用程序运行正常!

员工动态列表

使用上述方法,我们可以更进一步,即时加载员工列表,而不是对其进行硬编码。

准备文件内的数据public/employees.json

[
  {
    "id": "1",
    "name": "John Doe"
  },
  {
    "id": "2",
    "name": "Alice Smith"
  },
  {
    "id": "3",
    "name": "Will Brown"
  },
  {
    "id": "4",
    "name": "Ann Grey"
  }
]

现在public/index.html通过删除硬编码列表并添加一个data-employees-url属性来调整文件(请注意,我们必须提供控制器的名称,否则 Data API 将无法工作):

<div data-controller="employees" data-employees-url="/employees.json">
  <div data-target="employees.info"></div></div>

一旦控制器被附加到 DOM,它应该发送一个获取请求来构建一个员工列表。这意味着connect()回调是执行此操作的理想场所:

   // src/controllers/employees_controller.js
  
  connect() {
    this.loadFrom(this.data.get('url'), this.displayEmployees)
  }

我建议我们创建一个更通用的loadFrom()方法,该方法接受一个从 URL 加载数据和一个回调来实际呈现这些数据:

  // src/controllers/employees_controller.js
  
  loadFrom(url, callback) {
    fetch(url)
        .then(response => response.text())
        .then(json => { callback.call( this, JSON.parse(json) ) })
  }

调整choose()方法以利用loadFrom()

  // src/controllers/employees_controller.js
  
  choose(e) {
    e.preventDefault()
    const id = e.target.getAttribute('data-id')

    if (this.currentEmployee !== id) {
      this.loadFrom(`employees/${id}.json`, this.displayInfo) // <---
      this.currentEmployee = id

      this.employeeTargets.forEach((el, i) => {
        el.classList.toggle("chosen", id === el.getAttribute("data-id"))
      })
    }
  }

displayInfo()也可以简化,因为现在正在解析 JSON loadFrom()

 // src/controllers/employees_controller.js
  
  displayInfo(info) {
    const html = `<ul><li>Name: ${info.name}</li><li>Gender: ${info.gender}</li><li>Age: ${info.age}</li><li>Position: ${info.position}</li><li>Salary: ${info.salary}</li><li><img src="${info.image}"></li></ul>`
    this.infoTarget.innerHTML = html
  }

删除 loadInfoFor()并编码该displayEmployees()方法:

  // src/controllers/employees_controller.js
  
  displayEmployees(employees) {
    let html = "<ul>"
    employees.forEach((el) => {
      html += `<li><a href="#" data-target="employees.employee" data-id="${el.id}" data-action="employees#choose">${el.name}</a></li>`
    })
    html += "</ul>"
    this.element.innerHTML += html
  }

而已!我们现在根据服务器返回的数据动态呈现我们的员工列表。

结论

在本文中,我们介绍了一个名为 Stimulus 的适度 JavaScript 框架。我们已经看到了如何创建一个新的应用程序,添加一个带有一堆回调和动作的控制器,以及引入事件和动作。此外,我们在获取请求的帮助下完成了一些异步数据加载。


文章目录
  • 刺激简介
  • 引导刺激应用程序
    • 一些标记
  • 创建控制器
    • 生命周期回调
  • 添加事件
  • 与***合作
  • 使用目标
  • 异步加载数据
    • 员工动态列表
  • 结论