从 AlpineJS 开始
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
注:源代码可以在我的Github上找到。
如今,JavaScript 框架、库和工具层出不穷,让人难以抉择。
我个人比较喜欢用Angular,但React和Vue越来越受欢迎。有些框架,比如KnockoutJS,现在几乎被人遗忘了,而另一些框架,比如Reason(与其说是框架,不如说是编程语言)和SvelteJS,则越来越受欢迎。
所以,虽然我打算开始学习 React(也该是时候了),但我还是忍不住尝试了一个大约一个月前推出的新挑战者。
欢迎AlpineJS!
AlpineJS 旨在成为一个比 Vue 或 React 轻量得多的库,同时其语法完全借鉴自 Vue(引用作者的话)。
为什么选择 AlpineJS?
你说得对:既然有那么多其他工具,而且这些工具都有庞大的用户群体,为什么还要费劲地再次学习一个新工具呢?
以下是一些优点:
- 你可以从一开始就见证一个库的发展历程,而且,为什么不呢?你还可以做出贡献;
- 我预感 AlpineJS 在 2020 年一定会获得一定的知名度;
- 虽然 React 和 Vue 受到了很多开发者的推崇,但 AlpineJS 提供了一种更轻量级的 Web 前端开发方式,比当今的框架更接近基础知识(而我确实很喜欢基础知识)。
缺点:
- 它是新的,所以并不完美;
- 你可能要花点时间才能弄明白它的实际运作方式;
- 我之前说过,我预感这个框架将来会流行起来,但你不能确定这一点。
它是如何运作的?
让我们来编写第一个组件!我们将编写一个非常简单且极简的待办事项列表。本文不会提供完整的解决方案,因为在我撰写本文时,AlpineJS 仍在开发中。
后续会有更新。
设置环境
首先,让我们安装 AlpineJS。
npm i alpinejs
本示例将使用 Node 包 AlpineJS,但您也可以使用 CDN。
正如文档中所述,我们需要添加一些针对 IE11 的 polyfill。
所以,创建一个名为 `<directory_name>` 的目录src。从现在开始,我们将把所有应用程序文件都创建在这个目录中。然后,创建一个index.html包含以下代码的 `<directory_name>`:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>TODO LIST - ALPINEJS</title>
<link rel="stylesheet" href="./css/tasks.css">
<script src="https://polyfill.io/v3/polyfill.min.js?features=MutationObserver%2CArray.from%2CArray.prototype.forEach%2CMap%2CSet%2CArray.prototype.includes%2CString.prototype.includes%2CPromise%2CNodeList.prototype.forEach%2CObject.values%2CReflect%2CReflect.set"></script>
<script src="https://cdn.jsdelivr.net/npm/proxy-polyfill@0.3.0/proxy.min.js"></script>
</head>
<body>
<section id="add-task-container">
<form>
<input type="text">
<button type="submit">Add task</button>
</form>
</section>
<section id="list-tasks-container">
</section>
</body>
</html>
好了,我们已经有了所需的文件index.html,这很好。现在,我们需要导入 AlpineJS 模块。
由于我们将其安装为 Node 模块,因此导入它的一种方法是app.js在名为 `<module_name>` 的目录中创建一个名为 `<module_name>` 的文件cjs-modules,并使用如下的CommonJS规范:
'use strict'
let Alpine = require('alpinejs');
// DO NOT WRITE Alpine.start(); or every event will fire twice
但是现在,我们的app.js文件是一个 Node 模块,不能在前端脚本和 HTML 页面中使用,对吧?
幸运的是,有一个名为gulp-bro的 Node 包(gulp-browserify目前已被 NPM 列入黑名单)。这个包允许我们使用Browserify,这是一个强大的工具,可以在前端应用程序中使用 Node 模块。
所以,在你的终端中:
npm install -D gulp gulp-bro
我们为什么要使用这个gulp?这纯粹是我随意选择的。你也可以使用grunt、webpack或其他任何库。我们还会用到一个名为browser-sync的包。BrowserSync 允许你在修改代码时实时刷新浏览器。
npm install -D browser-sync
现在,回到我们的index.html文件。
只需添加以下内容:
<script src="./app.js"></script>
在进行任何测试之前,我们还需要设置开发环境。我们将在gulpfile.js项目根目录下创建一个 Gulp 作业。
请写出以下内容:
// src and dest are for moving files
// watch is for Gulp to trigger tasks anytime the watched files are modified
// task is to register a task in Gulp
const { src, dest, watch, task } = require('gulp');
// bro is for browserify
const bro = require('gulp-bro');
// browserSync
const browserSync = require('browser-sync').create();
// our build function
function _build() {
// take every commonJS module, browserify them and put them into ./dist
src('./src/cjs-modules/*.js')
.pipe(bro())
.pipe(dest('./dist'));
// take every JS script, and put them into ./dist
src('./src/scripts/**/*.js')
.pipe(dest('./dist'));
// take every HTML and CSS and put them into ./dist
src(['./src/**/*.html', './src/**/*.css'])
.pipe(dest('./dist'));
}
// our watch function
function _watch() {
watch(['src/*/*.js', 'src/**/*.html'], _build);
watch(['src/*/*.js', 'src/**/*.html']).on('change', browserSync.reload);
}
// our serve function
function _serve() {
_build();
browserSync.init({
server: "./dist"
});
_watch();
}
// registering a 'serve' task so we can trigger the building and serving with
// gulp serve
task('serve', _serve);
好了,现在我们到这里。这应该是你进行任何测试之前的最低设置。但我们还没完全完成。在你的package.json文件中,添加以下脚本:
"start": "gulp serve"
这样,您可以通过输入任一命令来启动构建和应用npm start程序gulp serve。
首次发布
npm start使用或启动应用程序gulp serve。如果一切正常,您应该会看到一个包含输入框、按钮的页面,并且浏览器控制台中不会出现错误。
数据绑定
AlpineJS不使用任何虚拟 DOM。这确实是一个不小的挑战,但它使我们能够开发出速度更快的应用程序。
首先,AlpineJS 是如何处理数据绑定的?
根据文档,您可以使用一些指令。其中之一是 `@variables`x-data指令。该指令允许您声明将在页面/组件中使用的变量。
这里,我们将使用两个变量:
- 任务,最初只会包含一个标签;
- tasks,这将是一个数组,用于存放我们将要创建的所有任务;
所以,在容器上<body>,<section>写入:
<body x-data="{tasks: [], task: {label: ''}}">
我们稍后会用到这些变量。
现在,我们需要将一些数据绑定到表单。与 Angular 类似,AlpineJS 提供了一个名为 `<input type="data">` 的指令x-model,我们可以用它来实现双重绑定。这里,我们将使用 `<input type="data">` 指令将数据绑定task到x-data输入框。请按如下方式更新您的 HTML:
<input type="text" x-model="task.label">
现在,我们在输入框中输入的所有内容都会影响到label我们task变量的属性。
这固然很好,但仍然存在一个问题(以及其他一些问题):当我们点击“提交”按钮时,页面会重新加载。这是表单和提交按钮的默认行为。
幸运的是!AlpineJS 提供了一个x-on指令,可以用来处理事件!
它的其中一个特性是x-on:[event name].prevent,其功能与相同event.preventDefault()。
因此,在您的<form>标签中添加以下内容:
<form x-on:submit.prevent>
这样一来,点击提交按钮时页面就不会再重新加载了。
添加任务
由于 AlpineJS 尚未完成,我们需要做一些工作才能实现我们想要的目标。
首先,我们希望将一个行为绑定到点击事件上,以便在提交表单时触发。所以:
<button type="submit" x-on:click="tasks = addTask(task, tasks);">Add task</button>
现在,我们需要实现这个addTask方法。
创建一个名为 `<directory_name>` 的目录scripts,并在其中创建一个名为 `<script_name>` 的脚本functions.js。为了简单起见,我们暂时将所有函数都存储在该文件中。
在这个文件中,创建一个名为 `add` 的函数addTask。该函数将接受两个参数:要添加的任务和要将任务添加到的列表。
function addTask(task, tasks) {
// if we do something like [...tasks, task], then we will
// still have a reference over the task object, which is bound with
// x-model, making our list to have multiple references to this object
// And we do not want that.
return [...tasks, {...task}]
}
别忘了把它链接到你的HTML代码中:
<script src="./functions.js"></script>
在继续之前:为什么我们不直接使用 `this.js` 呢tasks.push(task)?嗯,目前来说,如果我们这样做,HTML 中绑定的变量将不会更新,我们将无法显示其内容。因此,我们需要返回一个更新后的副本,然后重新应用它,这样我们的 HTML 才能更新。
显示任务
遗憾的是,AlpineJS 目前尚未提供此x-for功能(详情请见此处)。这里有一个关于该x-html指令的PR 。
我们仍然可以通过以下方式展示数据:
<section id="list-tasks-container" x-text="JSON.stringify(tasks)">
</section>
通常情况下,每次按下“提交”按钮时,数据都会刷新并显示。
贡献
AlpineJS 正在蓬勃发展,但仍需要您的帮助和支持。欢迎您为这个极具潜力的框架贡献力量,并参与测试和试用。
随着 AlpineJS 的发展,本文将不时更新。
如果您觉得我的说明有任何不清楚的地方,或者您采用了其他方法,请随时告诉我。我也很想听听您对使用 AlpineJS 进行开发时的最佳实践有何看法。
感谢阅读!
文章来源:https://dev.to/nugetchar/starting-with-alpinejs-hjn