有很多JavaScript框架。 有时我什至开始认为我是唯一尚未创建框架的人。 有些解决方案(例如Angular)既庞大又复杂,而另一些解决方案(例如Backbone (更多是库而不是框架))非常简单,仅提供了少数工具来加快开发过程。
在今天的文章中,我想向您介绍一个名为Stimulus的全新框架。 它是由David Heinemeier Hansson领导的Basecamp团队创建的,David Heinemeier Hansson是Ruby on Rails的父亲。
刺激是一个小框架,从来没有打算发展成大的东西。 它对前端开发有自己的哲学和态度,有些程序员可能会喜欢或不喜欢它。 刺激物还很年轻,但是版本1已经发布,因此在生产中应该可以安全使用。 我已经使用了这个框架很多,并且非常喜欢它的简洁和优雅。 希望您也会喜欢它!
在本文中,我们将讨论在创建具有异步数据加载,事件,状态持久性和其他常见事物的单页应用程序时,Stimulus的基础知识。
可以在GitHub上找到源代码。
刺激简介
Stimulus是由Basecamp的开发人员创建的。 他们没有选择创建单页JavaScript应用程序,而是选择了由Turbolinks和某些JavaScript驱动的雄伟整体 。 该JavaScript代码演变成一个小型且适度的框架,不需要您花费数小时来学习其所有概念和注意事项。
刺激主要是将其自身附加到现有DOM元素上并以某种方式与它们一起工作。 但是,也可以动态呈现内容。 总而言之,此框架与其他流行的解决方案完全不同 ,例如,它在HTML中而不是JavaScript对象中保持状态。 一些开发人员可能会觉得它很不方便,但确实给Stimulus一个机会,因为它确实会让您感到惊讶。
该框架只有三个主要概念应该记住:
- 控制器 :带有一些方法和回调的JS类,这些方法和回调将自身附加到DOM。 当
data-controller
“魔术”属性出现在页面上时,将发生附件 。 该文档解释说,此属性是HTML和JavaScript之间的桥梁,就像类充当HTML和CSS之间的桥梁一样。 一个控制器可以连接到多个元素,一个元素可以由多个控制器加电。 - 动作 :在特定事件上要调用的方法。 它们在特殊的
data-action
属性中定义。 - 目标 :可以轻松访问和操作的重要元素。 它们是在
data-target
属性的帮助下指定的。
如您所见,上面列出的属性使您能够以非常简单自然的方式将内容与行为逻辑分开 。 在本文的稍后部分,我们将看到所有这些概念,并注意到阅读HTML文档并了解正在发生的事情是多么容易。
引导刺激应用
刺激可以很容易地作为NPM软件包安装,也可以通过docs标签中的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>
真好! 现在,让我们添加一点刺激音。
创建一个控制器
作为官方文件解释 ,刺激的主要目的是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事件。
例如,让我们将click事件绑定到我们的员工。 每当此事件发生时,我都想调用employees_controller
尚不存在的choose()
方法:
<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
。
重新加载页面,单击列表项,然后观察结果!
与国家合作
现在,我们已将click事件处理程序绑定到员工,我想存储当前选择的人。 为什么? 存储了此信息后,我们可以防止第二次选择同一名员工。 稍后,这将使我们避免多次加载相同的信息。
刺激指示我们在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并将其保留。 使用数据API在Stimulus中非常常见,因此为每个控制器提供了一个特殊的this.data
对象。 在它的帮助下,我们可以运行以下方法:
-
this.data.get('name')
:通过其属性获取值。 -
this.data.set('name', value)
:在某些属性下设置值。 -
this.data.has('name')
:检查属性是否存在(返回布尔值)。
不幸的是,这些快捷方式不适用于click事件的目标,因此在这种情况下,我们必须坚持使用getAttribute()
:
// src/controllers/employees_controller.js
choose(e) {
e.preventDefault()
this.data.set("current-employee", e.target.getAttribute('data-id'))
}
但是我们可以通过为currentEmployee
创建一个getter和setter来做得更好:
// 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.currentEmployee
getter并确保提供的ID与已经存储的ID不相同。
现在,您可以通过以下方式重写choice 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提供了更整洁的解决方案。
达到目标 ,您可以在页面上标记一个或多个重要元素。 然后可以根据需要轻松地访问和操作这些元素。 为了创建目标,请添加一个值为{controller}.{target_name}
的data-target
属性(称为target描述符 ):
<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
下的data-id
进行比较。 如果匹配, .chosen
元素分配.chosen
类。 否则,将删除此类。 您还可以从设置器中提取if (this.currentEmployee !== id) {
条件,并在if (this.currentEmployee !== id) {
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"))
})
}
}
看上去很好! 最后,我们将为public/main.css
的.chosen
类提供一些非常简单的样式:
.chosen {
font-weight: bold;
text-decoration: none;
cursor: default;
}
再次重新加载页面,单击一个人,并确保该人正确突出显示。
异步加载数据
我们的下一个任务是加载有关所选员工的信息。 在实际的应用程序中,您将必须设置一个托管提供程序 ,一个由Django或Rails之类的东西提供支持的后端以及一个以包含所有必要数据的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。 还有两个承诺:一个是获取主体,另一个是显示加载的信息(我们将在稍后添加相应的方法)。
在choice 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"
}
]
现在,通过删除硬编码列表并添加data-employees-url
属性来调整public/index.html
文件(请注意,我们必须提供控制器的名称,否则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()
也可以简化,因为现在就在loadFrom()
内部解析了JSON:
// 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()
并编写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框架。 我们已经看到了如何创建一个新的应用程序,如何添加带有大量回调和动作的控制器以及如何引入事件和动作。 另外,我们在获取请求的帮助下完成了一些异步数据加载。
总而言之,这就是Stimulus的基础知识–确实不希望您掌握一些奥秘的知识来编写Web应用程序。 当然,该框架将来可能会具有一些新功能,但是开发人员并不打算将其变成具有数百种工具的巨大怪物。
如果您想找到更多使用Stimulus的示例,还可以查看本小手册 。 并且,如果您正在寻找其他JavaScript资源以供学习或在工作中使用,请查看Envato Market中提供的内容 。
你喜欢刺激物吗? 您是否想尝试创建由该框架提供支持的真实应用程序? 在评论区分享你的观点!
与往常一样,我感谢您与我在一起直到下一次。
翻译自: https://code.tutsplus.com/tutorials/introduction-to-stimulus-framework--cms-30563