- 作者:老汪软件技巧
- 发表时间:2024-09-06 11:01
- 浏览量:
大家好,我是梦兽。一个 WEB 全栈开发和 Rust 爱好者。如果你对 Rust 非常感兴趣,可以关注梦兽编程公众号获取群,进入和梦兽一起交流。传统服务渲染的应用,
JavaScript 模板字符串是一个非常强大的工具。这个小小的反引号允许创建多行字符串、嵌入表达式以及标记模板。标记模板?是的,就是那种让你能够这样写的小小魔法:
例如,你可以使用模板字符串来轻松地构建带有变量的字符串,如下所示:
await sql`SELECT email, name, prefs FROM user WHERE id=${user.id}`
或者
html`hello ${() => user.name}`
当 user.name 发生变化时,HTML 函数会重新渲染!
sql 示例是在一个 bun.sh 服务器上使用了 @neondatabase/serverless。html 示例来自 arrow-js,这是一个小(约 2k 大小)的客户端库,支持响应式和模板功能。
让我们来分解一下第二个示例:
${}表示计算这部分内容。()=>这是一个函数。user.name这是一个响应式状态。
通过将状态封装在一个函数中,就可以设置一个重新评估的点。库可以看到哪些状态被访问了以及函数渲染在 DOM 中的位置。所以,如果你只是想输出数据的话:
这种机制使得当状态改变时,只有相关的部分会被重新计算和更新,从而实现局部刷新而不是整个页面的重绘。这样可以提高应用的性能和用户体验。
html`${user.name}`
没有函数包装的话,就不会有其使用的记录,因此也就不会有重新评估。
那么为什么是 Arrow-js 呢?如果你看看 JavaScript 从最初的小型客户端交互性发展到庞大的服务器端库领域和 npm——我们现在已经有了大量的客户端和服务器端库,一种类型安全的变体,以及如此多的框架。这一切都是为了让这门小巧的语言成为今天撼动世界的语言。我们必须通过 JavaScript 进行开发——它实际上存在于每一台桌面电脑上,握在每个人的手中!话虽如此,所有这些历史负担都需要进行模块优化。随着语言的发展,框架要么改变要么被替换。我最近写了一篇关于 vanjs 的文章。我在渲染列表时遇到了一些问题,所以我又开始寻找替代方案。就是在那时我发现的 arrow-js。
Arrow-js 提供了三个函数:reactive、watch 和 html。Reactive 允许你包装状态——这种状态会记录访问并发出通知。
import { reactive } from '@arrow-js/core'
const state = reactive({
status: `connecting`,
error: null,
profile: null,
broadcast: null,
})
这是我的 WebSocket 的状态。它将保存连接状态、用户的个人资料以及需要由组件处理的广播消息。
const chat = reactive({transcript:{}})
watch(
() => state.broadcast,
(value)=>{
if (value?.channel == 'chat') {
value.payload.created = new Date(value.payload.id)
this.chat.transcript[value.payload.id] = value.payload
}
})
})
然后我们可以使用一个 HTML 模板来显示我们的聊天记录。
html`"transcript">
${() =>
Object.values(this.chat.transcript)
.sort((a, b) => a.id - b.id)
.map((item) => {
return html`- "${speaker_class(item)}">
${item.content}
"speaker">${speaker_label(item)}
`.key(item.id)
})}
`
为什么要有 speaker_class 和 speaker_label —— 这些提供了气泡的颜色和标签——你知道你是谁!所以在这里,我对 ul 有一个评估,并且在其中嵌入了对 li 的评估。两者都返回 HTML 函数,但是 li 后面跟着一个对 key() 的调用,这使得 DOM 更新变得更小、更精确。最奇妙的是,这仅仅是 JavaScript。
以上就是全部内容——三个动词:reactive、watch 和 html。虽然每一个看起来都很简单,但它们共同代表了一个真正的响应式库。如果你遇到问题,别忘了把它变成一个函数 ()=>。如果你不明白我在说什么,去试试看就知道了。
然后我的 HTML 看起来是这样的:
type html>
"en">
"UTF-8" />
"icon"
type="image/svg"
href="{{ static_url('message-circle.svg') }}"
/>
"viewport" content="width=device-width, initial-scale=1.0" />
Chatty
"stylesheet" href="site.css" />
static_url 函数是为了让 Tornado 进行缓存和响应。
现在 site.js 包含一个名为 App 的类,该类有一个 connect 函数用于创建 WebSocket。connect 和 send 函数都返回 Promise。还有四个显示函数,它们返回 arrow-js 的 HTML 函数。
import { reactive, watch, html } from 'https://esm.sh/@arrow-js/core' //'./arrow/index.mjs'
class App {
ws = null
state = reactive({
status: `connecting`,
error: null,
profile: null,
broadcast: null,
})
chat = reactive({
transcript: {},
})
entry = reactive({
speaker: 'peterb',
spoken: '',
})
constructor() {
watch(
() => this.state.broadcast,
(value) => {
if (value?.channel == 'chat') {
value.payload.created = new Date(value.payload.id)
this.chat.transcript[value.payload.id] = value.payload
}
}
)
}
connect() {
return new Promise((resolve, reject) => {
const protocol = location.protocol === 'https:' ? 'wss://' : 'ws://'
const ws = new WebSocket(
`${protocol}${location.hostname}:${location.port}/ws`
)
ws.onopen = () => {
this.ws = ws
this.state.status = 'connected'
resolve(this)
}
ws.onerror = (err) => {
this.set_error(err)
if (this.ws != null) {
reject(err)
}
}
ws.onclose = () => {
this.state.status = 'disconnected'
this.ws = null
}
ws.onmessage = (evt) =>
(this.state.broadcast = JSON.parse(evt.data))
})
}
disconnect() {
if (this.ws) {
this.ws.close()
}
}
set_error(err) {
this.state.error = err.message || err
}
send_chat(speaker, content) {
return new Promise((resolve, reject) => {
const message = {
channel: 'chat',
payload: {
speaker: speaker,
content: content,
id: new Date().getTime(),
},
}
try {
this.ws.send(JSON.stringify(message))
resolve(message)
} catch (err) {
reject(err)
}
})
}
display_status() {
return html`${() => (this.state.status == 'connected' ? '' : '')}`
}
display_error() {
return html`"${() => (this.state.error = null)}"
visible="${this.state.error != null}"
>
${() => this.state.error}
`
}
display_entry() {
return html``
}
display_transcript() {
const speaker_class = (item) => {
return item.speaker == this.entry.speaker ? 'from-me' : 'from-them'
}
const speaker_label = (item) => {
return item.speaker == this.entry.speaker ? '' : item.speaker
}
return html`${() =>
Object.values(this.chat.transcript)
.sort((a, b) => a.id - b.id)
.map((item) => {
return html`"${speaker_class(item)}">
${item.content}
"speaker">${speaker_label(item)}
`.key(item.id)
})}`
}
}
const app = (window.app = new App())
window.addEventListener('load', async () => {
try {
await app.connect()
document.body.innerHTML = ''
html`"status">
"content">
"entry">
"error">
"transcript">
`(document.body)
await app.send_chat('foo', 'bar is a three letter word.')
} catch (err) {
app.set_error(err)
}
app.display_status()(document.querySelector('.status'))
app.display_error()(document.querySelector('.error'))
app.display_entry()(document.querySelector('.entry'))
app.display_transcript()(document.querySelector('.transcript'))
})
这是我目前使用 arrow-js 所达到的程度。它似乎能做我想让它做的所有事情。我相信你能看出 JavaScript 的时代感——是的,我使用了一个类,哦,还有 querySelector。
自从 knockout-js 以来,出现了许多小型的响应式库。它们让这个世界变得更加易于生活。过去我们把模板声明为隐藏的头部元素,而现在它们直接在 JavaScript 中,无需预编译器。
构成网页的有三种语言:HTML、CSS 和 JavaScript。它们各自争夺着各自的领地。想想看,模态对话框是如何从 JavaScript 移动到 CSS 再到现在移到 HTML 的。Arrow-js 加入了一个竞争激烈的领域,并且它还没有离开 alpha 阶段!我认为它的功能及其实现方式是非常值得注意的。它不是 Vue(我的首选),当然也不是 Angular,尽管有点类似于 React,但它值得探索。
如果你尝试过 Arrow-js,请在评论中告诉我们你的看法。
结束语
感谢阅读!感谢您的时间,并希望您觉得这篇文章有价值。在您的下一个 JavaScript 项目中尝试使用柯里化,并在下面的评论中告诉我它如何改善了您的编码体验!
创建和维护这个博客以及相关的库带来了十分庞大的工作量,即便我十分热爱它们,仍然需要你们的支持。或者转发文章。通过赞助我,可以让我有能投入更多时间与精力在创造新内容,开发新功能上。赞助我最好的办法是微信公众号看看广告。