您好,
habrovchanin! 设计师是具有思想意识的人和客户,尤其是他们的业务需求。
想象一下,您在世界上最好的UIkit中以最酷的方式插入了您的JS框架。 看来项目需要一切。 现在,您可以喝咖啡,并通过在页面上放置组件来关闭所有新任务。 更好的是,如果您在NPM开放空间
的垃圾场中找到了这样的UIkit,它完全符合当前UX / UI及其需求。 小说!
真的……我在跟谁开玩笑? 你的幸福可能是短暂的。 的确,当设计师使用适用于下一页或“特殊项目”的新UI解决方案的Talmud时,总会出问题。
在这一点上,开发人员面临的问题是
“干或不干” ? 我应该以某种方式自定义现有组件吗? 是的,以免延迟现有案例的回归。 或按照“行之有效-请勿触摸”的原则行事,并从头开始编写新组件。 同时,扩大了UIkit并使支持复杂化。
如果您像许多人一样处于这种情况,请谨慎对待!
尽管进行了广泛的介绍,但在阅读
了有关Habré
的评论线索之一之后,写这篇文章的想法还是浮现在我脑海。 在那里,这些家伙被认真地介绍了如何在React上自定义按钮组件。 好吧,在我看过Telegram中的几个这样的工具之后,关于此的文章的需求终于得到了加强。
首先,让我们尝试想象我们可能需要将哪些“自定义”应用于组件。
款式
首先,这是组件样式的自定义。 一个常见的示例是灰色按钮,但需要蓝色按钮。 或没有圆角的按钮,突然需要它们。 根据我阅读的内容,我得出结论,大约有3种方法可以解决此问题:
1.整体风格
使用全局CSS样式的
全部功能,通过
!Important进行重定向,以便在外部全局地尝试重叠原始组件的样式。 轻描淡写地说,这个决定是有争议的,也太直接了。 而且,这种选择并非总是可能的,并且同时极度违反了样式的任何封装。 当然,除非在组件中使用它。
2.从父上下文传递类(样式)
这也是一个颇有争议的决定。 事实证明,我们正在创建一个特殊的
prop ,例如,我们将其称为
class,然后在上方将所需的类浸入组件中。
<Button classes="btn-red btn-rounded" />
自然,仅当组件支持以这种方式对其内容应用样式时,这种方法才有效。 此外,如果组件稍微复杂一点,并且由HTML元素的嵌套结构组成,那么显然将样式应用于所有样式将是有问题的。 因此,它们将被应用于组件的根元素,然后使用CSS规则,它们将以某种方式进一步传播。 可悲的是。
3.使用道具设置组件
它看起来是最明智的选择,但同时又是最不灵活的解决方案。 简而言之,我们希望该组件的作者是个天才,并事先考虑了所有选项。 也就是说,我们可能需要的所有东西,并确定了所有所需结果的所有必要道具:
<Button bgColor="red" rounded={true} />
听起来不太可能吧? 也许吧
举止

在这里仍然更加模棱两可。 首先,因为自定义组件行为的困难来自任务。 组件和其中的逻辑越复杂,我们要进行的更改越复杂,则进行更改就越困难。 结果是某种重言式...总之,您了解! ;-)
但是,即使在这里,还是有一组工具可以帮助我们自定义组件或不自定义组件。 由于我们正在专门讨论组件方法,因此我将重点介绍以下有用的工具:
1.使用道具方便工作
首先,您需要能够模仿组件道具集,而不必重新描述该道具集,并方便地进一步代理它们。
此外,如果我们尝试向组件添加某些行为,则很可能需要使用原始组件不需要的其他一组道具。 因此,最好能切断部分道具并将仅必要的道具转移到原始组件上。 同时,保持所有属性同步。
反面是当我们想要实现组件行为的特殊情况时。 以某种方式将他的部分状态固定在特定任务上。
2.跟踪组件的生命周期和事件
换句话说,组件内部发生的所有事情都不应该是一本完全封闭的书。 否则,这的确会使他的行为自定义变得复杂。
我并不是说违反封装和内部不受控制的干扰。 该组件应通过其公共api(通常是道具和/或方法)进行管理。 但是,以某种方式“发现”内部发生的事情并跟踪其状态变化仍然是必要的。
3.当务之急
我们将假定我没有告诉您这一点。 但是,有时候,能够获得一个组件实例并强制性地“拉弦”是一件好事。 最好避免这种情况,但是在特别复杂的情况下,您不能没有它。
好的,整理一下理论。 总的来说,一切都是显而易见的,但并非一切都是清晰的。 因此,值得考虑至少一些实际情况。
案例
我在上面提到,写文章的想法是由于自定义按钮的喜剧风格而产生的。 因此,我认为解决这种情况将具有象征意义。 愚蠢地更改颜色或倒圆角将太容易了,因此我尝试提出一种稍微复杂一点的案例。
想象一下,我们有基本按钮的某个组件,该组件用于监禁应用程序位置。 此外,它为所有应用程序按钮以及一些基本的封装样式实现了一些基本行为,这些样式有时会与UI指南等同步。
此外,有必要为服务器的提交按钮(提交按钮)增加一个组件,除了更改样式外,还需要其他行为。 例如,它可以是发送进度的图形,也可以是此操作结果的直观表示-成功或不成功。
它可能看起来像这样:
不难猜测基本按钮位于左侧,而右侧的提交按钮处于成功完成请求的状态。 好吧,如果情况清楚,那就开始吧!
解决方案
我仍然不知道是什么原因导致了关于React的决定。 显然没有那么简单。 因此,我不会碰运气,而是使用更熟悉的工具
SvelteJS- 一种新一代消失的框架 ,几乎可以完美地解决
此类问题 。
我们将立即同意,我们不会以任何方式干扰基本按钮的代码。 我们假定它根本不是我们编写的,并且其代码已关闭以进行更正。 在这种情况下,基本按钮的组件将如下所示:
Button.html <button {type} {name} {value} {disabled} {autofocus} on:click > <slot></slot> </button> <script> export default { data() { return { type: 'button', disabled: false, autofocus: false, value: '', name: '' }; } }; </script> <style> </style>
并以此方式使用:
<Button on:click="cancel()">Cancel</Button>
请注意,按钮组件确实非常基础。 它绝对不包含任何有助于实现组件扩展版本的辅助元素或道具。 该组件甚至不支持通过prop或至少某种内置的自定义样式来传递样式,并且所有样式都严格隔离,并且不会泄漏出去。
在此组件的基础上创建另一个具有增强功能甚至没有进行更改的任务,似乎很容易。 但是当您使用
Svelte时不会。
现在,让我们确定“提交”按钮应该能够执行的操作:
- 首先,框架和按钮文本应为绿色。 悬停时,背景也应该是绿色而不是深灰色。
- 此外,当按下一个按钮时,它应该“猛击”到一个圆形的进度指示器中。
- 完成该过程(由外部控制)后,必须将按钮的状态更改为成功(成功)或不成功(错误)。 同时,指示器上的按钮应变成带有花键的绿色徽章或带有叉号的红色徽章。
- 还必须能够设置一个时间,在该时间之后相应的徽标将再次变成其原始状态(空闲)的按钮。
- 当然,您需要在基本按钮的顶部完成所有这些操作,然后从那里保存并应用所有样式和道具。
h,这不是一件容易的事。 首先创建一个新组件,并使用基本按钮将其包装起来:
SubmitButton.html <Button> <slot></slot> </Button> <script> import Button from './Button.html'; export default { components: { Button } }; </script>
虽然这是完全相同的按钮,但更糟糕的是-它甚至不知道如何代理道具。 没关系,我们稍后再返回。
程式化
同时,让我们考虑如何根据任务来设置新按钮的样式,即更改颜色。 不幸的是,似乎我们无法使用上述任何方法。
由于样式被隔离在按钮内,因此可能会出现全局样式的问题。 也无法获取样式-基本按钮不支持此功能。 以及借助道具自定义外观。 另外,我们希望为新按钮编写的所有样式也都封装在此按钮内,而不会泄漏出去。
解决方案非常简单,但
前提是您已经在使用
Svelte 。 因此,只需为新按钮编写样式:
<div class="submit"> <Button> <slot></slot> </Button> </div> ... <style> .submit :global(button) { border: 2px solid #1ECD97; color: #1ECD97; } .submit :global(button:hover) { background-color: #1ECD97; color: #fff; } </style>
Svelte的主题演讲之一-简单的事情应该简单地解决。 在此版本中,特殊修饰符
:global将以这样的方式生成CSS,即只有该组件中带有
Submit类的块内的按钮才能接收指定的样式。
即使同类标记突然出现在应用程序的任何其他位置:
<div class="submit"> <button>Button</button> </div>
来自
SubmitButton组件的样式绝不会“泄漏”在那里。
使用此方法,
Svelte可以更轻松地自定义嵌套组件的样式,同时保留两个组件样式的封装。
我们扔道具并修复行为
好吧,我们几乎可以立即处理样式,无需任何其他道具,就可以直接传递CSS类。 现在,您需要通过新组件代理
Button组件的所有道具。 但是,我不想再次描述它们。 但是,对于初学者来说,让我们决定新组件将具有哪些属性。
从任务的
角度来看,
SubmitButton应该监视状态,并提供机会指定从成功/错误状态自动更改到初始状态之间的时间延迟:
<script> ... export default { ... data() { return { delay: 1500, status: 'idle' </script>
因此,我们的新按钮将具有4种状态:休息,下载,成功或错误。 此外,默认情况下,最后2个状态将在1.5秒后自动更改为空闲状态。
为了将所有传输的道具扔到
Button组件中,同时又切断显然对其无效的
状态和
延迟 ,我们将编写一个特殊的计算属性。 之后,我们仅使用
散布运算符就可以“涂抹”嵌入式组件上的其余道具。 另外,由于我们正是在执行“提交”按钮,因此我们需要修复按钮的类型,以便不能从外部进行更改:
<div class="submit"> <Button {...attrs} type="submit"> <slot></slot> </Button> </div> <script> ... export default { ... computed: { attrs: data => { const { delay, status, ...attrs } = data; return attrs; } }, }; </script>
相当简单而优雅。
结果,我们得到了具有修改样式的基本按钮的完整版本。 现在该实现新的按钮行为了。
我们更改并跟踪状态
因此,当您单击
SubmitButton按钮时
,我们不仅必须抛出事件以使用户代码可以对其进行处理(如
Button中所做的那样),而且还必须实现其他业务逻辑-设置下载状态。 为此,只需将事件从基本按钮捕获到您自己的处理程序中,执行所需的操作并将其进一步发送:
<div class="submit"> <Button {...attrs} type="submit" on:click="click(event)"> <slot></slot> </Button> </div> <script> ... export default { ... methods: { click(e) { this.set({ status: 'loading' }); this.fire('click', e); } }, }; </script>
此外,此按钮的父组件控制着自身发送数据的过程,可以通过道具设置相应的发送状态(
成功/错误 )。 同时,按钮应跟踪状态变化,并在指定的时间后自动将状态更改为
idle 。 为此,请使用
生命周期 onupdate挂钩:
<script> ... export default { ... onupdate({ current: { status, delay }, changed }) { if (changed.status && ['success', 'error'].includes(status)) { setTimeout(() => this.set({ status: 'idle' }), delay); } }, }; </script>
画龙点睛
还有2个点在任务中并不明显,在执行过程中会出现。 首先,为了使按钮变形的动画更流畅,您将不得不使用样式而不是其他元素来更改按钮本身。 为此,我们可以使用相同的
:global ,所以没有问题。 但是此外,有必要将按钮内部的标记隐藏在所有状态(
闲置除外)中。
值得一提的是,按钮内部的标记可以是任意标记,并且通过嵌套的插槽将其扔到基本按钮的原始组件中。 但是,尽管听起来很危险,但是解决方案不仅仅是原始的-您只需要在新组件内将插槽包装在其他元素中,然后对其应用必要的样式即可:
<div class="submit"> <Button {...attrs} type="submit" on:click="click(event)"> <span><slot></slot></span> </Button> </div> ... <style> ... .submit span { transition: opacity 0.3s 0.1s; } .submit.loading span, .submit.success span, .submit.error span { opacity: 0; } ... </style>
另外,由于按钮不会从页面隐藏,而是会随着状态变化而变化,因此在发送时将其禁用会很好。 换句话说,如果使用道具将“提交”按钮设置为“
禁用” ,或者状态不是“
空闲” ,则必须禁用该按钮。 为了解决这个问题,我们编写了另一个小的计算属性
isDisabled并将其应用于嵌套组件:
<div class="submit"> <Button {...attrs} type="submit" disabled={isDisabled}> <span><slot></slot></span> </Button> </div> <script> ... export default { ... computed: { ... isDisabled: ({ status, disabled }) => disabled || status !== 'idle' }, }; </script>
一切都会好起来的,但是基本按钮的样式使其在禁用状态下是半透明的,但是如果仅由于状态更改而暂时禁用了该按钮,则我们不需要。 所有这些都可以挽救
:global :
.submit.loading :global(button[disabled]), .submit.success :global(button[disabled]), .submit.error :global(button[disabled]) { opacity: 1; }
仅此而已! 新按钮很漂亮,可以使用了!
我将特意省略动画实现以及所有这些细节。 不仅因为它与本文的主题没有直接关系,而且还因为在此部分中,演示并未按我们的意愿进行。 我没有使我的任务复杂化,而是为这种按钮实现了一个完整的交钥匙解决方案,而是愚蠢地移植了Internet上的示例。
因此,我不建议在工作中使用此实现。 请记住,这只是本文的演示。
→
交互式演示和完整的示例代码如果您喜欢这篇文章并想了解有关
Svelte的更多信息,请
阅读其他
文章 。 例如,
“如何在不使用React + RxJS 6 + Recompose的情况下在GitHub上进行用户搜索”。 听新年的播客
RadioJS#54 ,我在其中详细讨论了
Svelte是什么,它如何“消失”以及为什么它还没有“还没有另一个js框架”。
查看俄语电报频道
SvelteJS 。 我们已经有超过200名,我们很高兴见到您!
P /秒突然,UI准则发生了变化。 现在,应用程序所有按钮中的标签应为大写。 但是,我们不担心发生这种情况。 添加
文本转换:大写; 在基本按钮的样式中,继续喝咖啡。
祝您工作愉快!