HyperApp:用于构建前端应用程序的1 KB JavaScript库

Hyperapp是一个JavaScript库,用于构建功能丰富的Web应用程序。 它结合了实用的Elm启发式状态管理方法和支持键更新和生命周期事件的VDOM引擎-所有这些都无需依赖。 提供或占用几个字节,将整个源代码缩小并压缩到大约1 KB。

在本教程中,我将向您介绍Hyperapp,并引导您完成一些代码示例,以帮助您立即上手。 我假定对HTML和JavaScript有所了解,但是不需要具有其他框架的先前经验。

你好,世界

我们将从一个简单的演示开始,该演示显示所有运动部件如何协同工作。

您也可以在线尝试代码

import { h, app } from "hyperapp"
// @jsx h

const state = {
  count: 0
}

const actions = {
  down: () => state => ({ count: state.count - 1 }),
  up: () => state => ({ count: state.count + 1 })
}

const view = (state, actions) => (
  <div>
    <h1>{state.count}</h1>
    <button onclick={actions.down}>-</button>
    <button onclick={actions.up}>+</button>
  </div>
)

app(state, actions, view, document.body)

这几乎是每个Hyperapp应用程序的外观。 单个状态对象,填充状态的动作以及将状态和动作转换为用户界面的视图。

在app函数内部,我们复制了您的状态和动作(对我们不拥有的对象进行突变是不礼貌的)并将它们传递给视图。 我们还会包装您的操作,以便它们在每次状态更改时重新呈现应用程序。

app(state, actions, view, document.body)

状态是描述您的应用程序数据模型的普通JavaScript对象。 这也是一成不变的。 要更改它,您需要定义动作并调用它们。

const state = {
  count: 0
}

在视图内部,您可以显示状态的属性,使用它来确定应该显示或隐藏UI的哪些部分,等等。

<h1>{state.count}</h1>

您还可以将操作附加到DOM事件,或在自己的内联事件处理程序中调用操作。

<button onclick={actions.down}>-</button>
<button onclick={actions.up}>+</button>

动作不会直接改变状态,而是会返回状态的新片段。 如果您尝试使操作中的状态发生变化然后返回它,则视图将不会像您期望的那样重新呈现。

const actions = {
  down: () => state => ({ count: state.count - 1 }),
  up: () => state => ({ count: state.count + 1 })
}

应用程序调用返回连接到状态更新视图-渲染周期的操作对象。 您还可以在视图函数内和操作内收到该对象。 将此对象暴露给外界非常有用,因为它使您可以从另一个程序,框架或原始JavaScript与您的应用程序对话。

const main = app(state, actions, view, document.body)

setTimeout(main.up, 1000)

关于JSX的说明

为了熟悉本文,我将在整个本文档中使用JSX ,但是您不需要将JSX与Hyperapp一起使用。 替代方法包括内置的h函数, @ hyperapp / htmlhyperxt7

这是使用@ hyperapp / html的相同示例。

import { app } from "hyperapp"
import { div, h1, button } from "@hyperapp/html"

const state = { count: 0 }

const actions = {
  down: () => state => ({ count: state.count - 1 }),
  up: () => state => ({ count: state.count + 1 })
}

const view = (state, actions) =>
  div([
    h1(state.count),
    button({ onclick: actions.down }, "–"),
    button({ onclick: actions.up }, "+")
  ])

app(state, actions, view, document.body)

虚拟DOM

虚拟DOM使用称为虚拟节点的嵌套JavaScript对象树来描述DOM的外观。

{
  name: "div",
  props: {
    id: "app"
  },
  children: [{
    name: "h1",
    props: null,
    children: ["Hi."]
  }]
}

应用程序的虚拟DOM树在每个渲染周期都从头开始创建。 这意味着我们每当状态更改时都调用view函数,并使用新计算的树来更新实际DOM。

通过将新的虚拟DOM与先前的虚拟DOM进行比较,我们尝试在尽可能少的DOM操作中做到这一点。 这导致了高效率,因为通常只需要更改一小部分节点,并且与重新计算虚拟DOM相比,更改实际DOM节点的成本很高。

为了帮助您以更紧凑的方式创建虚拟节点,Hyperapp提供了h函数。

import { h } from "hyperapp"

const node = h(
  "div",
  {
    id: "app"
  },
  [h("h1", null, "Hi.")]
)

创建虚拟节点的另一种方法是使用JSX 。 JSX是一种JavaScript语言扩展,用于表示动态HTML。

import { h } from "hyperapp"

const node = (
  <div id="app">
    <h1>Hi.</h1>
  </div>
)

浏览器不了解JSX,因此我们需要将其编译为h函数调用,因此需要导入h语句。 让我们看看如何使用babel进行此过程。

首先,安装依赖项:

npm i babel-cli babel-plugin-transform-react-jsx

然后创建一个.babelrc文件:

{
  "plugins": [
    [
      "transform-react-jsx",
      {
        "pragma": "h"
      }
    ]
  ]
}

并从命令行编译代码:

npm run babel src/index.js > index.js

如果您不想使用构建系统,则还可以从CDN加载unappkg之类的Hyperapp ,它将通过window.hyperapp对象全局可用。

例子

Gif搜索框

在此示例中,我将向您展示如何使用Giphy API异步更新状态以构建Gif搜索框

为了产生副作用,我们在回调或承诺被解决时在其他动作中调用动作。

返回nullundefinedPromise对象的操作不会触发视图重新渲染。 如果操作返回了一个承诺,我们将把该承诺传递给调用方,使您可以像下面的示例一样创建异步操作。

现场例子

import { h, app } from "hyperapp"
// @jsx h

const GIPHY_API_KEY = "dc6zaTOxFJmzC"

const state = {
  url: "",
  query: "",
  isFetching: false
}

const actions = {
  downloadGif: query => async (state, actions) => {
    actions.toggleFetching(true)
    actions.setUrl(
      await fetch(
        `//api.giphy.com/v1/gifs/search?q=${query}&api_key=${GIPHY_API_KEY}`
      )
        .then(data => data.json())
        .then(({ data }) => (data[0] ? data[0].images.original.url : ""))
    )
    actions.toggleFetching(false)
  },
  setUrl: url => ({ url }),
  setQuery: query => ({ query }),
  toggleFetching: isFetching => ({ isFetching })
}

const view = (state, actions) => (
  <div>
    <input type="text"
      placeholder="Type here..."
      autofocus
      onkeyup={({ target: { value } }) =/> {
        if (value !== state.query) {
          actions.setQuery(value)
          if (!state.isFetching) {
            actions.downloadGif(value)
          }
        }
      }}
    />
    <div class="container">
      <img src={state.url}
        style={{
          display: state.isFetching || state.url === "" ? "none" : "block"
        }}
      />
    </div>
  </div>
)

app(state, actions, view, document.body)

该状态存储Gif URL的字符串,搜索查询和一个布尔标志,以了解浏览器何时获取新的Gif。

const state = {
  url: "",
  query: "",
  isFetching: false
}

当浏览器繁忙时, isFetching标志用于隐藏Gif。 没有它,最后一次下载的Gif将显示为请求另一个Gif。

<img src={state.url}
  style={{
    display: state.isFetching || state.url === "" ? "none" : "block"
  }}
/>

该视图由文本输入和显示Gif的img元素组成。

为了处理用户输入,使用了onkeyup事件,但是onkeydownoninput也可以工作。

在每个按键actions.downloadGif被称为一个新的GIF动画的要求,但只有在获取尚未挂起和文字输入不是空的。

if (value !== state.query) {
  actions.setQuery(value)
  if (!state.isFetching) {
    actions.downloadGif(value)
  }
}

actions.downloadGif内部,我们使用提取 API从Giphy请求Gif URL。

fetch完成后,我们将在promise中接收带有Gif信息的有效负载。

actions.toggleFetching(true)
actions.setUrl(
  await fetch(
    `//api.giphy.com/v1/gifs/search?q=${query}&api_key=${GIPHY_API_KEY}`
  )
    .then(data => data.json())
    .then(({ data }) => (data[0] ? data[0].images.original.url : ""))
)
actions.toggleFetching(false)

接收到数据后,将调用actions.toggleFetching (这允许进行进一步的提取请求),并通过将获取的Gif URL传递给actions.setUrl来更新状态。

TweetBox克隆

在此示例中,我将向您展示如何创建自定义组件,以将UI组织成可重用的标记并构建简单的TweetBox克隆。

现场例子

import { h, app } from "hyperapp"
// @jsx h

const MAX_LENGTH = 140
const OFFSET = 10

const OverflowWidget = ({ text, offset, count }) => (
  <div class="overflow">
    <h1>Whoops! Too long.</h1>
    <p>
      ...{text.slice(0, offset)}
      <span class="overflow-text">{text.slice(count)}</span>
    </p>
  </div>
)

const Tweetbox = ({ count, text, update }) => (
  <div>
    <div class="container">
      <ul class="flex-outer">
        <li>
          <textarea placeholder="What's up?" value={text} oninput={update}></textarea>
        </li>

        <li class="flex-inner">
          <span class={count > OFFSET ? "overflow-count" : "overflow-count-alert"}
          >
            {count}
          </span>

          <button onclick={() => alert(text)}
            disabled={count >= MAX_LENGTH || count < 0}
          >
            Tweet
          </button>
        </li>
      </ul>

      {count < 0 && (
        <OverflowWidget
          text={text.slice(count - OFFSET)}
          offset={OFFSET}
          count={count}
        />
      )}
    </div>
  </div>
)

const state = {
  text: "",
  count: MAX_LENGTH
}

const view = (state, actions) => (
  <tweetbox text={state.text}
    count={state.count}
    update={e => actions.update(e.target.value)}
  />
)

const actions = {
  update: text => state => ({
    text,
    count: state.count + state.text.length - text.length
  })
}

app(state, actions, view, document.body)

状态存储消息的文本和剩余字符count ,已初始化为MAX_LENGTH

const state = {
  text: "",
  count: MAX_LENGTH
}

该视图由我们的TweetBox组件组成。 我们使用属性/属性将数据传递到小部件中。

const view = (state, actions) => (
  </tweetbox><tweetbox text={state.text}
    count={state.count}
    update={e => actions.update(e.target.value)}
  />
)

当用户输入输入内容时,我们调用actions.update()来更新当前文本并计算剩余字符。

update: text => state => ({
  text,
  count: state.count + state.text.length - text.length
})

从前一个文本的长度中减去当前文本的长度,可以告诉我们剩余字符的数量如何变化。 因此,剩余字符的新计数是旧计数加上上述差异。

输入为空时,此操作等于(MAX_LENGTH - text.length)

state.count小于0时,我们知道state.text必须大于MAX_LENGTH ,因此我们可以禁用tweet按钮并显示OverflowWidget组件。

<button onclick={() => alert(text)} disabled={count >= MAX_LENGTH || count < 0}>
  Tweet
</button>

state.count === MAX_LENGTH ,tweet按钮也被禁用,因为这意味着我们尚未输入任何字符。

OverflowWidget标记显示消息的不允许部分以及上下文的一些相邻字符。 常量OFFSET告诉我们有多少个额外的字符可以分割state.text

<overflowwidget text={text.slice(count - OFFSET)}
  offset={OFFSET}
  count={count}></overflowwidget>

通过将OFFSET传递给OverflowWidget,我们可以进一步分割text并将overflow-text类应用于特定的溢出部分。

<span class="overflow-text">{text.slice(count)}</span>

与React的比较

从概念上讲,Hyperapp和React有很多共同点。 这两个库都使用虚拟DOM,生命周期事件和基于密钥的协调。 Hyperapp的外观和感觉很像React和Redux,但样板更少。

React将视图的概念推广为状态的函数。 Hyperapp通过内置的,受Elm启发的状态管理解决方案使这一想法更进一步。

Hyperapp拒绝仅依赖纯功能组件的本地组件状态的想法。 这意味着高可重用性,廉价的备忘录和简单的测试。

最后的想法

由于Hyperapp是如此之小,因此与几乎任何其他替代方案相比,它在网络上的传输速度更快,解析速度也更快。 这意味着需要学习的概念更少,错误更少,框架稳定性更高。

我从来都不喜欢大型框架。 不是因为它们不好,而是因为我想编写自己的JavaScript,而不是框架要我使用的JavaScript。 我想要的是可转让的技能。 我想提高JavaScript的技能,而不是将技能提高到框架中。


要了解有关Hyperapp的更多信息,请查看官方文档,并在Twitter上关注我们以获取更新和公告。

From: https://www.sitepoint.com/hyperapp-1-kb-javascript-library/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值