使用Clarity设计系统和Blazor的下一代应用程序

194 阅读7分钟

使用Clarity设计系统和Blazor的下一代应用程序

.NET Blazor框架使客户端的Web应用可以用.NET框架和C#编写。Blazor利用Web Assembly(WASM)来编译C#以在浏览器中运行。Blazor实现了开发人员在.NET世界中所习惯的奇妙的开发者体验和工具。使用像Clarity设计系统这样的下一代设计系统,我们可以快速构建出现代UI。

Clarity设计系统是通过利用自定义元素来建立任何UI框架的稳定性和兼容性。这使得Clarity组件可以被视为任何HTML元素,并在任何前端Web框架中工作,同时只保持一个代码库。

Many frameworks supported with the Clarity Design System Clarity设计系统支持的框架

在这篇文章中,我们将学习如何开始使用Blazor并安装Clarity设计系统。我们还将介绍Blazor与当今其他主流前端框架的权衡。

Blazor的设置

要开始使用Blazor,请确保你在本地安装了最新的.NET环境。克隆本篇博文的启动版示例库。

git clone git@github.com:coryrylan/clarity-blazor.git

克隆后,您应该打开该版本,看到Blazor项目的根部有一个package.json 文件。

Blazor Project Structure

如果你还没有安装NodeJS。安装完毕后,在资源库的根目录下运行以下命令。

npm installnpm run start

这些命令将安装我们项目的前端依赖项,并运行package.json 文件中的start npm/node脚本。或者,你也可以运行dotnet watch 。如果一切安装正确,你应该看到你的localhost webserver。

Blazor Project with Clarity Design System

安装Clarity

这个版本已经具备了将Clarity与Blazor一起使用的一切条件。因此,让我们来了解一下让一切就绪的步骤。首先,打开wwwroot/index.html 文件。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  <title>clarity-blazor</title>
  <base href="/" />
  <link rel="stylesheet" href="https://unpkg.com/modern-normalize@1.1.0/modern-normalize.css">
  <link rel="stylesheet" href="https://unpkg.com/@cds/core@6.0.0-next.4/global.min.css">
  <link rel="stylesheet" href="https://unpkg.com/@cds/core@6.0.0-next.4/styles/theme.dark.min.css">
  <link rel="stylesheet" href="https://unpkg.com/@cds/city@1.1.0/css/bundles/default.min.css">
  <link href="css/app.css" rel="stylesheet" />
</head>

<body cds-text="body" cds-theme="dark">
  <div id="app">
    <div id="blazor-loading-ui" cds-text="section">Loading...</div>
  </div>

  <script src="_framework/blazor.webassembly.js"></script>
  <script type="module" src="js/event-handlers.js"></script>
  <script type="module" src="js/index.js"></script>
</body>

</html>

index.html 文件是你的Blazor应用程序的根模板。为了使安装简单,我们使用直接CDN链接到Clarity。Blazor和Clarity可以使用任何捆绑工具,如Webpack或Rollup。

我们为Clarity添加主要的全局样式表,包括温和的CSS重置和可选的黑暗主题。

<link rel="stylesheet" href="https://unpkg.com/modern-normalize@1.1.0/modern-normalize.css">
<link rel="stylesheet" href="https://unpkg.com/@cds/core@6.0.0-next.4/global.min.css">
<link rel="stylesheet" href="https://unpkg.com/@cds/core@6.0.0-next.4/styles/theme.dark.min.css">
<link rel="stylesheet" href="https://unpkg.com/@cds/city@1.1.0/css/bundles/default.min.css">

添加后,打开wwwroot/js/index.js 文件:

import('https://cdn.skypack.dev/pin/@cds/core@v6.0.0-next.4-QVPeIoBWPyUxeVUrYlUP/mode=imports,min/optimized/@cds/core/button/register.js');
import('https://cdn.skypack.dev/pin/@cds/core@v6.0.0-next.4-QVPeIoBWPyUxeVUrYlUP/mode=imports,min/optimized/@cds/core/alert/register.js');

index.js 文件是一个普通的JavaScript文件,它被加载到index.html 。同样,为了保持简单并专注于Blazor,我们通过CDN加载这个文件,但也可以使用我们选择的捆绑器。

现在,我们的应用程序已经加载了Clarity组件和CSS,让我们来看看我们的第一个Blazor组件。

Blazor组件和Razor视图

Blazor可以使用.NET网络应用程序中常用的.razor 语法来编写视图。这在用C#语言编写时提供了出色的开发者体验。在这个版本中,我们有一个视图,Pages/Index.razor

Razor视图呈现普通的HTML,并且可以有一个@code 块来将任何视图逻辑附加到模板上。在这个视图中,我们有以下片段。

<cds-button @onclick="@(e => Show = !Show)" action="outline" status="primary">Hello There</cds-button>

<cds-alert-group status="info" hidden="@(!Show)">
  <cds-alert>
    General Kenobi, you are a bold one...
  </cds-alert>
</cds-alert-group>
@code {
  private bool Show = false;
}

我们可以通过razor视图中的@ 符号来绑定和监听HTML事件。例如,在我们的Claritycds-button ,我们通过@onclick 来监听点击事件,然后传递一个表达式来评估。

我们还可以在我们的元素上设置属性。例如,我们可以通过绑定到Show 属性来切换警报组件上的hidden 属性。通过事件和属性绑定,我们现在可以切换我们的警报框了。

在这一点上,一切都应该在你的演示程序中工作。然而,为了更好地了解事情是如何设置的,请继续阅读!

Blazor和自定义事件

到目前为止,Blazor是非常简单的。我们可以使用C#和Razor视图来渲染HTML。我们可以监听事件,并根据组件的状态来操作DOM。然而,目前Blazor在监听HTML自定义事件时存在一个需要解决的空白。

自定义事件是HTML元素可以在DOM中发出的一种事件类型。Blazor知道默认的事件类型,如click,hover 等。然而,如果一个组件使用了自定义事件,我们就需要让Blazor知道这些事件的存在,并对其进行监听。

我们可以用两个文件注册我们的事件。首先打开EventHanders.cs 文件。

/* 
 * EventHandlers.cs
 * Generated with https://github.com/coryrylan/custom-element-types
 */
using Microsoft.AspNetCore.Components;
using System.Text.Json;

namespace clarity_blazor;

[EventHandler("oncloseChange", typeof(CustomEventArgs))] // cds-dropdown, cds-modal, cds-internal-overlay, cds-internal-popup
[EventHandler("onexpandedChange", typeof(CustomEventArgs))] // cds-navigation-group, cds-navigation, cds-tree-item
[EventHandler("onselectedChange", typeof(CustomEventArgs))] // 
public static class EventHandlers
{

}

public class CustomEventArgs : EventArgs
{
  public dynamic? Detail { get; set; }

  /* Returns the detail value of CustomEvent with given type */
  public T GetDetail<T>() {
    return JsonSerializer.Deserialize<T>(Detail); // used to cast dynamic type, unknown until event occurs at runtime
  }
}

这个文件是用custom-element-types工具生成的,它采用了一个自定义元素库并为各种框架生成了类型绑定。我们可以用这个工具创建和注册Clarity Design System所需的所有自定义事件。

这使得自定义事件能够被Blazor和Razor视图所识别。不幸的是,Blazor只监听全局文档级别的事件。这意味着我们不能像Angular和React等其他框架那样,用TSX将一个特定的自定义事件与一个特定的HTML元素联系起来。这限制了我们为特定的事件/元素组合提供静态类型。

注册我们事件的第二个文件是wwwroot/js/event-handlers.js 。这个文件被链接在我们的根index.html 文件中,也是用自定义元素类型工具生成的。

/**
 * wwwroot/custom-events.js
 * Generated with https://github.com/coryrylan/custom-element-types
 */

 const customEvents = {
  closeChange: true, // cds-dropdown, cds-modal, cds-internal-overlay, cds-internal-popup,
  expandedChange: true, // cds-navigation-group, cds-navigation, cds-tree-item,
  selectedChange: true, // 
};

/**
 * Workaround: Blazor ignores the event target and only listens to global events
 * this is a problem for most custom elements which dispatch CustomEvent types
 * that default to not bubbling.
 */
CustomEvent = class Bubbled extends CustomEvent {
  constructor(event, config) {
    const bubbles = customEvents[event] !== undefined ? customEvents[event] : config.bubbles;
    super(event, { ...config, bubbles });
  }
}

Object.keys(customEvents).map(event => {
  Blazor.registerCustomEventType(event, {
    browserEventName: event,
    createEventArgs: event => {
      return { detail: event.detail };
    }
  });
});

虽然之前的文件注册了事件,但它只在我们的代码视图的C#端注册了这些事件。因此,我们必须调用Blazor.registerCustomEventType API来启用Blazor的JavaScript兼容层上的事件监听器。一旦为每个事件调用,Blazor就会理解我们整个应用程序的自定义事件。

Blazor和事件泡点

不幸的是,Blazor只支持DOM中当前状态下的气泡事件。Blazor不会为单个元素添加事件监听器,而是在DOM的根部添加一个监听器,期望所有发出的事件都能冒泡。这与网络上的大多数UI组件产生了一个相当大的兼容性问题。对于Web组件和许多框架导出的组件,它们使用自定义事件,默认情况下不冒泡。许多框架选择不冒泡,以限制应用程序需要过滤的事件的数量。

由于Blazor的这种限制,你的UI框架/库很可能无法与Blazor一起使用。不过,在Blazor中修复这个问题之前,您可以通过对CustomEvent 类的一个不算太好的补丁来解决这个问题。

/**
 * Workaround: Blazor ignores the event target and only listens to global events
 * this is a problem for most custom elements which dispatch CustomEvent types
 * that default to not bubbling.
 */
CustomEvent = class Bubbled extends CustomEvent {
  constructor(event, config) {
    const bubbles = customEvents[event] !== undefined ? customEvents[event] : config.bubbles;
    super(event, { ...config, bubbles });
  }

event-handlers.js 文件中,这个变通方法是由custom-element-types工具生成的。该补丁将把我们库中任何匹配的自定义事件的名称默认设置为气泡,这样Blazor就会听到该事件。

虽然这对Blazor来说是一个粗糙的地方,但目前可以用custom-element-types生成器来处理这个问题。

Blazor仍处于早期阶段,但看到WASM在浏览器中利用.NET和C#的能力,令人印象深刻。有了Blazor和Web Components,团队将可以灵活地利用.NET生态系统的优秀工具来构建Web UI。请务必查看下面的工作演示链接。