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与Hyperapp一起使用。 替代方法包括内置的h
函数, @ hyperapp / html , hyperx和t7 。
这是使用@ 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使用称为虚拟节点的嵌套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
对象全局可用。
在此示例中,我将向您展示如何使用Giphy API异步更新状态以构建Gif搜索框
为了产生副作用,我们在回调或承诺被解决时在其他动作中调用动作。
返回null
, undefined
或Promise
对象的操作不会触发视图重新渲染。 如果操作返回了一个承诺,我们将把该承诺传递给调用方,使您可以像下面的示例一样创建异步操作。
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
事件,但是onkeydown
或oninput
也可以工作。
在每个按键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
来更新状态。
在此示例中,我将向您展示如何创建自定义组件,以将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>
从概念上讲,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/