hyperapp 共享
在本教程中,我们将使用Hyperapp构建待办事项列表应用程序。 如果您想学习函数式编程原理,但又不想陷入细节,请继续阅读。
Hyperapp现在很热门。 它最近在GitHub上超过了11,000个星,并在2017年JavaScript Rising Stars的``前端框架''部分中排名第五。 当它达到1.0版时,它最近也出现在SitePoint上 。
Hyperapp受欢迎的原因可以归功于它的实用性和超轻的大小(1.4 kB),同时获得了与React和Redux相似的开箱即用的结果。
那么,什么是HyperApp?
Hyperapp允许您利用虚拟DOM来构建动态的单页Web应用程序,以类似于React的方式快速高效地更新网页上的元素。 与Redux一样,它也使用一个对象来跟踪应用程序的状态。 这样可以更轻松地管理应用程序的状态,并确保不同的元素不会彼此不同步。 Hyperapp背后的主要影响是Elm架构 。
Hyperapp的核心包括三个主要部分:
- 状态 。 这是一个单个对象树,用于存储有关应用程序的所有信息。
- 动作 。 这些方法用于更改和更新状态对象中的值。
- 查看 。 这是一个函数,它返回编译为HTML代码的虚拟节点对象。 它可以使用JSX或类似的模板语言,并且可以访问
state
和actions
对象。
这三个部分相互交互以生成动态应用程序。 操作由页面上的事件触发。 然后,该操作将更新状态,然后触发对视图的更新。 这些更改是对虚拟DOM进行的,Hyperapp使用它来更新网页上的实际DOM。
入门
为了尽快上手,我们将使用CodePen开发我们的应用程序。 您需要使用以下链接确保将JavaScript预处理器设置为Babel并将Hyperapp软件包作为外部资源加载:
https://unpkg.com/hyperapp
要使用Hyperapp,我们需要导入app
函数以及h
方法,Hyperapp用于创建VDOM节点。 将以下代码添加到CodePen中JavaScript窗格中:
const { h, app } = hyperapp;
我们将使用JSX作为视图代码。 为了确保Hyperapp知道这一点,我们需要在代码中添加以下注释:
/** @jsx h */
app()
方法用于初始化应用程序:
const main = app(state, actions, view, document.body);
这将state
和actions
对象作为前两个参数,将view()
函数作为第三个参数,最后一个参数是将应用程序插入标记HTML元素。 按照惯例,这通常是<body>
标记,由document.body
表示。
为了易于上手,我在CodePen上创建了样板化的Hyperapp代码模板,其中包含上述所有元素。 单击此链接可以将其分叉。
您好Hyperapp!
让我们玩一下Hyperapp,看看它是如何工作的。 view()
函数接受state
和actions
对象作为参数,并返回虚拟DOM对象。 我们将使用JSX,这意味着我们可以编写看起来更像HTML的代码。 这是一个返回标题的示例:
const view = (state, actions) => (
<h1>Hello Hyperapp!</h1>
);
实际上,这将返回以下VDOM对象:
{
name: "h1",
props: {},
children: "Hello Hyperapp!"
}
每当state
对象更改时,都会调用view()
函数。 然后,Hyperapp将根据已发生的任何更改构建新的虚拟DOM树。 然后,Hyperapp将比较新的虚拟DOM与存储在内存中的旧虚拟DOM的差异,从而以最有效的方式来更新实际的网页。
组件
组件是返回虚拟节点的纯函数。 它们可用于创建可重用的代码块,然后将其插入视图中。 他们可以用任何函数都可以接受的常规方式来接受参数,但是他们不能以与视图相同的方式来访问state
和actions
对象。
在下面的示例中,我们创建一个名为Hello()
的组件,该组件接受一个对象作为参数。 在返回包含该值的标题之前,我们使用分解从该对象中提取name
值:
const Hello = ({name}) => <h1>Hello {name}</h1>;
现在,我们可以在视图中引用该组件,就好像它是一个名为<Hello />
HTML元素一样。 我们可以像将props传递给React组件一样,将数据传递给这个元素:
const view = (state, actions) => (
<Hello name="Hyperapp" />
);
请注意,在使用JSX时,组件名称必须以大写字母开头或包含句点。
州
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
状态是一个普通的旧JavaScript对象,其中包含有关应用程序的信息。 它是应用程序的“单一事实来源”,只能使用操作进行更改。
让我们为应用程序创建状态对象,并设置一个名为name
的属性:
const state = {
name: "Hyperapp"
};
现在,视图函数可以访问此属性。 将代码更新为以下内容:
const view = (state, actions) => (
<Hello name={state.name} />
);
由于视图可以访问state
对象,因此我们可以将其name
属性用作<Hello />
组件的属性。
动作
动作是用于更新状态对象的函数。 它们以特定形式编写,该形式返回另一个咖喱函数,该函数接受当前状态并返回更新的部分状态对象。 这部分是风格上的,但也可以确保state
对象保持不变。 通过将操作结果与先前状态合并来创建一个全新的state
对象。 然后,这将导致调用view
函数并更新HTML。
下面的示例演示如何创建一个名为changeName()
。 此函数接受一个名为name
的参数,并返回一个用于使用此新名称更新state
对象中name
属性的咖喱函数。
const actions = {
changeName: name => state => ({name: name})
};
要查看此动作,我们可以在视图中创建一个按钮,并使用onclick
事件处理程序调用该动作,并将其参数设置为“蝙蝠侠”。 为此,将view
功能更新为以下内容:
const view = (state, actions) => (
<div>
<Hello name={state.name} />
<button onclick={() => actions.changeName('Batman')}>I'm Batman</button>
</div>
);
现在尝试单击按钮,观察名称更改!
超级清单
现在该构建更重要的东西了。 我们将构建一个简单的待办事项列表应用程序,使您可以创建列表,添加新项目,将其标记为已完成并删除项目。
首先,我们需要在CodePen上启动一支新笔。 添加以下代码,或简单地使用我的HyperBoiler笔进行分叉:
const { h, app } = hyperapp;
/** @jsx h */
const state = {
};
const actions = {
};
const view = (state, actions) => (
);
const main = app(state, actions, view, document.body);
您还应该在CSS部分中添加以下内容并将其设置为SCSS:
// fonts
@import url("https://fonts.googleapis.com/css?family=Racing+Sans+One");
$base-fonts: Helvetica Neue, sans-serif;
$heading-font: Racing Sans One, sans-serif;
// colors
$primary-color: #00caff;
$secondary-color: hotpink;
$bg-color: #222;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
padding-top: 50px;
background: $bg-color;
color: $primary-color;
display: flex;
height: 100vh;
justify-content: center;
font-family: $base-fonts;
}
h1 {
color: $secondary-color;
& strong{ color: $primary-color; }
font-family: $heading-font;
font-weight: 100;
font-size: 4.2em;
text-align: center;
}
a{
color: $primary-color;
}
.flex{
display: flex;
align-items: top;
margin: 20px 0;
input {
border: 1px solid $primary-color;
background-color: $primary-color;
font-size: 1.5em;
font-weight: 200;
width: 50vw;
height: 62px;
padding: 15px 20px;
margin: 0;
outline: 0;
&::-webkit-input-placeholder {
color: $bg-color;
}
&::-moz-placeholder {
color: $bg-color;
}
&::-ms-input-placeholder {
color: $bg-color;
}
&:hover, &:focus, &:active {
background: $primary-color;
}
}
button {
height: 62px;
font-size: 1.8em;
padding: 5px 15px;
margin: 0 3px;
}
}
ul#list {
display: flex;
flex-direction: column;
padding: 0;
margin: 1.2em;
width: 50vw;
li {
font-size: 1.8em;
vertical-align: bottom;
&.completed{
color: $secondary-color;
text-decoration: line-through;
button{
color: $primary-color;
}
}
button {
visibility: hidden;
background: none;
border: none;
color: $secondary-color;
outline: none;
font-size: 0.8em;
font-weight: 50;
padding-top: 0.3em;
margin-left: 5px;
}
&:hover{
button{
visibility: visible;
}
}
}
}
button {
background: $bg-color;
border-radius: 0px;
border: 1px solid $primary-color;
color: $primary-color;
font-weight: 100;
outline: none;
padding: 5px;
margin: 0;
&:hover, &:disabled {
background: $primary-color;
color: #111;
}
&:active {
outline: 2px solid $primary-color;
}
&:focus {
border: 1px solid $primary-color;
}
}
这些只是为应用程序添加了一些样式和Hyperapp品牌。
现在,让我们开始构建实际的应用程序!
初始状态和视图
首先,我们将设置初始state
对象和一个简单的视图。
创建初始state
对象时,考虑一下应用程序在整个生命周期中想要跟踪哪些数据和信息很有用。 对于我们的列表,我们需要一个数组来存储待办事项,以及一个字符串,该字符串表示在实际待办事项输入框中输入的内容。 如下所示:
const state = {
items: [],
input: '',
};
接下来,我们将创建view()
函数。 首先,我们将重点介绍添加项目所需的代码。 添加以下代码:
const view = (state, actions) => (
<div>
<h1><strong>Hyper</strong>List</h1>
<AddItem add={actions.add} input={actions.input} value={state.input} />
</div>
);
这将显示标题以及名为<AddItem />
的元素。 这不是新HTML元素,而是我们需要创建的组件。 让我们现在开始:
const AddItem = ({ add, input, value }) => (
<div class='flex'>
<input type="text" value={value}
onkeyup={e => (e.keyCode === 13 ? add() : null)}
oninput={e => input({ value: e.target.value })}
/>
<button onclick={add}>+</button>
</div>
);
这将返回用于输入待办事项的<input>
元素,以及用于将其添加到列表中的<button>
元素。 该组件接受一个对象作为参数,我们从中提取三个属性: add
, input
和value
。
如您所料, add()
函数将用于将一项添加到我们的待办事项列表中。 如果按下Enter键(其KeyCode
为13)或单击该按钮,则将调用此函数。 input()
函数用于更新状态下当前项目的值,并在文本字段接收到用户输入时调用该函数。 最后, value
属性是用户在输入字段中键入的任何内容。
请注意, input()
和add()
函数是作为道具传递给<AddItem />
组件的动作:
<AddItem add={actions.add} input={actions.input} value={state.input} />
您还可以看到, value
prop取自该州的input
属性。 因此,输入字段中显示的文本实际上存储在状态中,并在每次按键时更新。
要将所有内容粘合在一起,我们需要添加input
操作:
const actions = {
input: ({ value }) => ({ input: value })
}
现在,如果您开始在输入字段中输入内容,您应该会看到它显示您正在输入的内容。 这演示了Hyperapp循环:
- 当用户在输入字段中键入文本时,会触发
oninput
事件 -
input()
动作被调用 - 该操作将更新状态中的
input
属性 - 状态更改会导致
view()
函数被调用并更新VDOM - 然后对实际的DOM进行VDOM中的更改,并重新渲染页面以显示按下的键。
试试看,您应该看到输入的内容出现在输入字段中。 不幸的是,按Enter或单击“ +”按钮目前无法执行任何操作。 那是因为我们需要创建一个将项目添加到列表中的动作。
添加任务
在考虑创建列表项之前,我们需要考虑如何表示它们。 JavaScript的对象表示法非常完美,因为它使我们可以将信息存储为键值对。 我们需要考虑列表项可能具有的属性。 例如,它需要一个描述需要完成的值。 它还需要一个说明项目是否已完成的属性。 一个示例可能是:
{
value: 'Buy milk',
completed: false,
id: 123456
}
注意,该对象还包含一个名为id
的属性。 这是因为Hyperapp中的VDOM节点需要唯一的密钥来标识它们。 我们将为此使用时间戳 。
现在,我们可以开始创建添加项目的操作。 我们的第一项工作是将输入字段重置为空。 通过将input
属性重置为空字符串来完成此操作。 然后,我们需要向items
数组添加一个新对象。 这是使用Array.concat
方法完成的。 这与Array.push()
方法的行为类似,但是它返回一个新数组,而不是对其作用的数组进行变异。 请记住,我们要创建一个新的状态对象,然后将其与当前状态合并,而不是简单地直接更改当前状态。 value
属性设置为state.input
包含的值,该值表示在输入字段中输入的内容:
add: () => state => ({
input: '',
items: state.items.concat({
value: state.input,
completed: false,
id: Date.now()
})
})
请注意,此操作包含两个不同的状态。 当前状态由提供给第二个函数的参数表示。 还有一个新状态是第二个函数的返回值。
为了演示这一点,让我们想象一下该应用程序刚开始时只有一个空项目列表,并且用户在输入字段中输入了文本“ Buy milk”(买牛奶),然后按Enter ,从而触发add()
操作。
在执行操作之前,状态如下所示:
state = {
input: 'Buy milk',
items: []
}
该对象作为参数传递给add()
操作,该操作将返回以下状态对象:
state = {
input: '',
items: [{
value: 'Buy milk',
completed: false,
id: 1521630421067
}]
}
现在我们可以将状态添加到items
数组中的items
,但看不到它们! 要解决此问题,我们需要更新视图。 首先,我们需要创建一个组件来显示列表中的项目:
const ListItem = ({ value, id }) => <li id={id} key={id}>{value}</li>;
这使用<li>
元素显示一个值,该值作为参数提供。 还要注意, id
和key
属性都具有相同的值,即项目的唯一ID。 key
属性由Hyperapp内部使用,因此不会在呈现HTML中显示,因此使用id
属性显示相同的信息也很有用,尤其是因为该属性具有相同的唯一性条件。
现在我们有了列表项的组件,我们需要实际显示它们。 JSX使这一过程变得非常简单,因为它将遍历一组值并依次显示每个值。 问题在于state.items
不包含JSX代码,因此我们需要使用Array.map
将数组中的每个item对象更改为JSX代码,如下所示:
state.items.map(item => ( <ListItem id={item.id} value={item.value} /> ));
这将遍历state.items
数组中的每个对象,并创建一个包含ListItem
组件的新数组。 现在我们只需要将其添加到视图中即可。 将view()
函数更新为以下代码:
const view = (state, actions) => (
<div>
<h1><strong>Hyper</strong>List</h1>
<AddItem add={actions.add} input={actions.input} value={state.input} />
<ul id='list'>
{ state.items.map(item => ( <ListItem id={item.id} value={item.value} /> )) }
</ul>
</div>
);
这只是将新的ListItem
组件数组放置在一对<ul>
标记内,因此它们将显示为无序列表。
现在,如果您尝试添加项目,您应该看到它们出现在输入字段下方的列表中!
将任务标记为已完成
我们的下一个工作是能够切换待办事项的completed
属性。 单击未完成的任务应将其completed
属性更新为true
,而单击已完成的任务应将其completed
属性切换回false
。
这可以通过使用以下操作来完成:
toggle: id => state => ({
items: state.items.map(item => (
id === item.id ? Object.assign({}, item, { completed: !item.completed }) : item
))
})
这个动作有很多事情要做。 首先,它接受一个名为id
的参数,该参数引用每个任务的唯一ID。 然后,该操作将遍历数组中的所有项目,检查提供的ID值是否与每个列表项对象的id
属性匹配。 如果是这样,它将使用否定运算符将completed
属性更改为与当前属性相反的状态!
。 使用Object.assign()
方法进行此更改,该方法创建一个新对象,并与旧item
对象和更新后的属性进行浅表合并。 请记住,我们永远不会直接更新状态中的对象。 而是,我们创建一个覆盖当前状态的新状态。
现在,我们需要将此动作连接到视图。 我们通过更新ListItem
组件来做到这一点,使其具有onclick
事件处理程序,该处理程序将调用刚刚创建的toggle
动作。 更新ListItem
组件代码,使其看起来如下所示:
const ListItem = ({ value, id, completed, toggle, destroy }) => (
<li class={completed && "completed"} id={id} key={id} onclick={e => toggle(id)}>{value}</li>
);
在你们中间,老鹰眼会发现该组件获得了一些额外的参数,并且<li>
属性列表中还有一些额外的代码:
class={completed && "completed"}
这是Hyperapp中的一种常见模式,用于在满足某些条件时插入额外的代码片段。 如果completed
参数为true,则使用短路或惰性求值将类设置为completed。 这是因为当使用&&
,如果两个操作数都为true,则该操作的返回值将是第二个操作数。 由于字符串"completed"
始终为true,因此如果第一个操作数( completed
参数)为true,则将返回此字符串。 这意味着,如果任务已完成,它将具有“已完成”类,并且可以相应地设置样式。
我们的最后一项工作是更新view()
函数中的代码,以将额外的参数添加到<ListItem />
组件中:
const view = (state, actions) => (
<div>
<h1><strong>Hyper</strong>List</h1>
<AddItem add={actions.add} input={actions.input} value={state.input} />
<ul id='list'>
{
state.items.map(item => (
<ListItem id={item.id} value={item.value} completed={item.completed} toggle={actions.toggle} />
))
}
</ul>
</div>
);
现在,如果您添加一些项目并尝试单击它们,您应该看到它们被标记为完成,并在它们之间出现一行。 再次单击,它们将恢复为未完成状态。
删除任务
目前,我们的列表应用运行良好,但是如果我们可以删除列表中不再需要的任何项目,那就太好了。
我们的第一项工作是添加destroy()
操作,该操作将从状态中的items
数组中删除一个项目。 我们无法使用Array.slice()
方法执行此操作,因为这是对原始数组起作用的破坏性方法。 相反,我们使用filter()
方法,该方法返回一个新数组,其中包含所有通过指定条件的项目对象。 这种情况是id
属性不等于作为参数传递给destroy()
操作的ID。 换句话说,它返回一个不包含我们要摆脱的项目的新数组。 状态更新后,此新列表将替换旧列表。
将以下代码添加到actions
对象:
destroy: id => state => ({ items: state.items.filter(item => item.id !== id) })
现在,我们再次必须更新ListItem
组件,以添加触发该动作的机制。 我们将通过添加带有onclick
事件处理程序的按钮来做到这一点:
const ListItem = ({ value, id, completed, toggle, destroy }) => (
<li class={completed && "completed"} id={id} key={id} onclick={e => toggle(id)}>
{value}
<button onclick={ () => destroy(id) }>x</button>
</li>
);
请注意,我们还需要添加另一个名为destroy
参数,该参数表示单击按钮时要使用的动作。 这是因为与视图不同,组件没有对actions
对象的直接访问,因此视图需要显式传递任何动作。
最后,我们需要更新视图以将actions.destroy
作为参数传递给<ListItem />
组件:
const view = (state, actions) => (
<div>
<h1><strong>Hyper</strong>List</h1>
<AddItem add={actions.add} input={actions.input} value={state.input} />
<ul id='list'>
{state.items.map(item => (
<ListItem
id={item.id}
value={item.value}
completed={item.completed}
toggle={actions.toggle}
destroy={actions.destroy}
/>
))}
</ul>
</div>
);
现在,如果您将一些项目添加到列表中,则将鼠标悬停在它们上方时应注意“ x”按钮。 单击此按钮,它们应该消失在乙醚中!
删除所有已完成的任务
我们要添加到列表应用程序中的最后一个功能是能够一次删除所有已完成的任务。 这使用了与我们先前使用的相同的filter()
方法-返回一个仅包含具有completed
属性值false
项目对象的数组。 将以下代码添加到actions
对象:
clearAllCompleted: ({items}) => ({ items: items.filter(item => !item.completed) })
为了实现这一点,我们只需要添加一个带有onclick
事件处理程序的按钮,即可在视图底部调用此操作:
const view = (state, actions) => (
<div>
<h1><strong>Hyper</strong>List</h1>
<AddItem add={actions.add} input={actions.input} value={state.input} />
<ul id='list'>
{state.items.map(item => (
<ListItem
id={item.id}
value={item.value}
completed={item.completed}
toggle={actions.toggle}
destroy={actions.destroy}
/>
))}
</ul>
<button onclick={() => actions.clearAllCompleted({ items: state.items })}>
Clear completed items
</button>
</div>
);
现在可以添加一些项目,将其中一些标记为完成,然后按按钮将其清除。 太棒了!
请参阅CodePen上的SitePoint( @SitePoint )的笔超级 列表 。
够了,伙计们
至此,本教程结束。 我们将一个简单的待办事项列表应用程序组合在一起,该应用程序可以完成您期望此类应用程序执行的大多数操作。 如果您正在寻找灵感并希望添加到功能中,则可以考虑添加优先级,使用拖放更改项目的顺序或添加具有多个列表的功能。
我希望本教程可以帮助您了解Hyperapp的工作原理。 如果您想对Hyperapp进行更深入的研究,建议阅读文档 ,并对源代码有所了解。 时间不长,可以让您深入了解一切在后台的工作方式。 您还可以在Hyperapp Slack组上提出更多问题。 这是我使用过的最友好的小组之一,而且知识渊博的成员也为我提供了很多帮助。 您还会发现Hyperapp的创建者Jorge Bucaran经常在此闲逛并提供帮助和建议。
使用CodePen可以使开发Hyperapp应用程序变得非常快速和容易,但是您最终将希望在本地构建自己的应用程序,然后在线部署它们。 有关如何执行此操作的提示,请查看我的下一篇有关捆绑Hyperapp应用并将其部署到GitHub Pages的文章 !
希望您喜欢本文中的代码,并希望在下面的评论中分享您的想法或问题。
hyperapp 共享