• 作者:老汪软件技巧
  • 发表时间: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`
"${async (e) => { e.preventDefault() try { await this.send_chat(this.entry.speaker, this.entry.spoken) this.entry.spoken = '' } catch (err) { this.set_error(err) } return false }}" > type="text" @input="${(e) => (this.entry.speaker = e.target.value)}" value="${this.entry.speaker}" placeholder="your name" name="speaker" required /> type="text" @input="${(e) => (this.entry.spoken = e.target.value)}" value="${() => this.entry.spoken}" placeholder="say something" name="spoken" required autofocus />
` } 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 项目中尝试使用柯里化,并在下面的评论中告诉我它如何改善了您的编码体验!

创建和维护这个博客以及相关的库带来了十分庞大的工作量,即便我十分热爱它们,仍然需要你们的支持。或者转发文章。通过赞助我,可以让我有能投入更多时间与精力在创造新内容,开发新功能上。赞助我最好的办法是微信公众号看看广告。