Blazor 연구소

간만의 슬로그를 작성하게 되었습니다.

요즘 개인적으로 홈페이지를 리뉴얼하고 있는데, Blazor와 웹 개발에 푹 빠져 있습니다. 그래서 그간의 생각과 정보들을 공유하기 위한 글을 하나 열어두려 합니다.

저와 같은 WPF 개발자 분들은 아무래도 웹에 대한 접근이 쉽지 않을 것이라 생각됩니다. 저도 그런 부분에서 공감하고 쉽게 접근하지 못해왔는데, 작년부터 Blazor 밋업과 같은 여러 다양한 컨퍼런스를 통해서 활발하게 Blazor에 대한 소개와 기술 공유가 있었고, 저 또한 자연스럽게 접할 수 있었던 것 같습니다.

그리고 우리 닷넷데브 커뮤니티에서도 Blazor 기술에 대한 다양한 정보 공유를 통해 많이 도움을 받고 있습니다. 무엇보다 GPT-4.0 도움도 많이 받고 있는데, 요즘에는 특히 HTML, CSS, 반응형웹 등을 구현할 때 아주 유용하게 사용하고 있습니다.

저는 (WPF 개발자 아니랄까봐) Component 활용에 엄청 의존하고 있습니다. 마치 CustomControl처럼 사용하고 있죠. Binding 개념을 사용할 수 있고 논리적으로 WPF와 매우 유사하게 아키텍처를 설계할 수 있기 때문에 심적으로도 안도감과 약간의 자신감을 얻을 수 있었습니다.

그리고 WPF 분야가 아닌 새로운 플랫폼을 접하면서 새삼 느낀 것은, 아무리 좋은 정보도 그것을 받아들이고 조금이나마 이해하기 위해서는 충분한 숙련도가 뒷받침되어야 한다는 것을 깨닫고 다시금 초심을 찾는 중입니다. :smile:

제가 Blazor에서 주로 관심을 두고 있는 부분은 다음과 같습니다.

  • Component 범위와 설계
  • 이미지, svg와 같은 리소스 관리 체계
  • JavaScript 관리 체계
  • 레이아웃과 CSS 규칙

오래전부터 웹으로 만들고 싶은 것들이 많았었는데 저와 비슷한 생각을 갖고 계신 분들도 적극 참여해보시면 좋을 것 같습니다.

완료된 내용들

  1. Component 설계를 통한 SVG 아이콘 사용
  2. JamesIcon 고도화
  3. Blazor에서의 속성 정의
  4. Blazor에서 <input> 요소를 통한 단방향/양방향 데이터 바인딩
  5. InputText와 같은 컴포넌트
  6. 격리된 CSS에서 자식 접근 하기 (razor.css)
  7. 의존성 주입 방식
  8. 중첩된 요소의 포인터 무시하기 (CSS)
  9. 바인딩을 응용한 CSS 조건부 렌더링, 그리고 중첩 CSS
  10. 자바스크립트에서 격리된 CSS 지정 방법
13개의 좋아요

1. Component 설계를 통한 SVG 아이콘 사용

웹 개발에서 SVG 파일은 아이콘, 로고 등의 그래픽 요소로 널리 사용됩니다. 전통적으로, 이러한 SVG 파일들을 각각의 파일로 관리하며 아래와 같이 img 태그를 통해 웹페이지에 통합하는 방법이 일반적입니다.

<img src="/heart.svg"/>

이 방식은 편리해 보일 수 있지만, 파일의 수가 늘어나고 SVG의 스타일링(색상, 크기 조정 등)이 필요할 때 여러 한계를 드러냅니다. SVG가 외부 파일로 분리되어 있기 때문에, CSS를 통한 스타일 관리가 어렵고 재사용성이 떨어지는 문제가 있죠.

이를 해결하기 위해, Blazor 컴포넌트를 이용할수 잇습니다 . 이 방식은 SVG 요소들을 효율적으로 재사용하며, 직접적인 속성과 CSS 등으로 유연하게 관리하는 것이 가능해집니다. 이러 한 방식은 WPF와도 매우 유사해 보입니다.

Blazor Component로 SVG 관리

JamesIcon이라는 Blazor 컴포넌트를 통해 SVG 아이콘을 관리합니다. 이 컴포넌트는 다양한 파라미터를 통해 SVG의 속성을 쉽게 정의할 수 있게 정의할수 있게 됩니다.

JamesIcon 컴포넌트 설계

@using System.Net.Http
@using Jamesnet.Shared.Local.Helpers
@using Jamesnet.Shared.Local.Models
@inject HttpClient Http
@inject SvgImageRepository SvgImage

<svg class="@Class" @attributes="BuildSvgAttributes()" 
     xmlns="http://www.w3.org/2000/svg">
    <path d="@GetImageData()" fill="@Fill"></path>
</svg>

@code {
    [Parameter] public string Class { get; set; }
    [Parameter] public string Width { get; set; } = "24"; 
    [Parameter] public string Height { get; set; } = "24"; 
    [Parameter] public string Fill { get; set; } = "#000000";
    [Parameter] public string ViewBox { get; set; } = "0 0 24 24";
    [Parameter] public string Data { get; set; }
    [Parameter] public ImageType ImageType { get; set; }

    private Dictionary<string, object> BuildSvgAttributes() => new()
    {
        {"width", Width},
        {"height", Height},
        {"viewBox", ViewBox},
        {"fill", Fill}
    };

    private string GetImageData()
    {
        return !string.IsNullOrEmpty(Data) 
	    ? Data : SvgImage.GetImageData(ImageType);
    }
}

JamesIcon 컴포넌트는 외부에서 제공하는 파라미터를 통해 SVG의 스타일을 정의합니다. 이렇게 함으로써, SVG 파일을 직접 수정하지 않고도 원하는 스타일을 적용할 수 있게 됩니다. 심지어WPF의 DependencyProperty 속성처럼 Binding 방식도 가능해집니다.

확장성 있는 데이터 관리

svg 파일을 사용하는 대신, SvgImageRepository 클래스를 통해 SVG 데이터를 ImageType 열거형을 키로 하는 Dictionary에 저장하여 관리합니다. 이 방식은 새로운 SVG 아이콘을 추가할 때 손쉽게 할 수 있고, 기능적으로도 유연한 확장성을 확보할 수 있습니다.

using Jamesnet.Shared.Local.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Jamesnet.Shared.Local.Helpers
{
    public class SvgImageRepository
    {
        private const string ACCOUNT_CIRCLE = "M12,19.2C9....";
        private const string ACCOUNT_BOX = "M6,17C6,15 10,...";
        private const string EARTH = "M17.9,17.39C17.64...";
        private const string HEART_MULTIPLE = "M13.5,20C6.9,...";
        private const string HEART = "M12,21...";
        private const string HEART_CIRCLE = "M12,2C6.47,...";
        private const string STAR = "M12,17.27...";
        private const string STAR_CIRCLE = "M16.23,...";

        private Dictionary<ImageType, string> svgData = new()
        {
            { ImageType.AccountCircle, ACCOUNT_CIRCLE },
            { ImageType.AccountBox, ACCOUNT_BOX },
            { ImageType.Earth, EARTH },            
            { ImageType.HeartMultiple, HEART_MULTIPLE},
            { ImageType.Heart, HEART},
            { ImageType.HeartCircle, HEART_CIRCLE},
            { ImageType.Star, STAR},
            { ImageType.StarCircle, STAR_CIRCLE},
        };

        public string GetImageData(ImageType imageType)
        {
            if (svgData.TryGetValue(imageType, out string data))
            {
                return data;
            }
            return null;
        }
    }
}

Static 클래스로 사용할 수도 있지만, 이 클래스는 인스턴스 방식으로 설계했기 때문에 Blazor에서 이 클래스를 싱글톤으로 등록하면 @Injection 등록을 통해 어디서든 사용할 수 있게 됩니다.

지금은 직접 클래스에서 SVG 데이터를 관리하도록 했지만, .yml 파일을 두고 데이터를 관리하는 것이 더 효과적으로 보입니다.

CSS를 통한 스타일링

JamesIcon 컴포넌트의 Class 파라미터를 활용하여, 외부 CSS에서 SVG의 시각적 속성을 관리할 수 있습니다. 이는 width, height, margin 등의 속성을 svg 태그에, fill 같은 속성을 path 태그에 적용할 수 있게 합니다.

그리고 공통적인 스타일은 .james-icon 클래스로 정의하고, 아이콘별 개별 스타일은 .earth-icon, .heart-icon 등과 같이 세분화하여 관리할 수 있습니다.

.james-icon {
    width: 40px;
    height: 40px;
}

.heart-icon path {
    fill: red;
}

.earth-icon path {
    fill: blue;
}

이렇게 Blazor 컴포넌트와 CSS를 활용하면, SVG 파일을 효율적으로 관리하며, 스타일링의 유연성을 극대화할 수 있습니다.

Component 사용하기

이제 SVG 아이콘을 더욱 유연하게 웹 페이지에 통합하고, 다양한 스타일링 옵션을 적용할 수 있습니다. 컴포넌트 사용의 특징은 파라미터를 통해 간편하게 SVG의 모양, 크기, 색상 등을 조정할 수 있고, 손쉽게 속성들을 확장할 수 있습니다.

JamesIcon 컴포넌트의 기본 사용법

@page "/example"

@inject SvgImageRepository SvgImage

<JamesIcon Class="james-icon heart-icon" ImageType="Heart" Fill="red"/>
<JamesIcon Class="james-icon earth-icon" ImageType="Earth" Fill="blue"/>

이처럼 Class를 통해 디자인을 정의하거나, ImageType을 통해 아이콘을 선택하는 것이 가능해집니다.

SVG 데이터를 직접 제공하는 경우

<JamesIcon Class="custom-icon" Data="M12,21.35..." Fill="green"/>

SvgImageRepository를 통해 관리되고 있지 않는 데이터를 직접 지정할 수도 있습니다. 이는 테스트를 통해 먼저 확인이 필요한 경우 유용하게 사용할 수 있습니다.

이제 Component를 통해 SVG 기반의 아이콘 요소들을 더욱 효과적으로 제어할 수 있게 해줍니다.

아이콘 마구 쓰기…

(샘플 프로젝트는 추후에 GitHub와 누겟을 통해 공유 예정입니다.)

5개의 좋아요

코드 공유 감사합니다.

제 경험에 비춘 코멘트를 허락하신다면,

레이저 요소의 재사용성은 RCL 로 묶어 둘 수 있는 구조가 좌우했던 것 같습니다.

즉, JameIcon 을 베이스로 두고, 파생 아이콘들을 별도의 프로젝트로 만드는 것이죠.

// AccountCircle.razor.cs
public partial class AccountCircle : JamesIcon
{
   protected override string Data => "M12,19.2C9....";
// ...

위 아이콘들은 JamesIcons 라는 RCL(Razor Component Library) 프로젝트에 모아 두거나 누겟 패키지로 패킹합니다.

JamesIcons.csproj
   JamesIconBase.razor
   AccountCircle.razor
   ...

이 프로젝트에 아이콘들을 추가할 때, 기존의 코드인 SvgImageRepository를 수정할 필요가 없이 확장할 수 있습니다.

사용자는 아래와 같이 사용하는 것이죠.

@using JamesIcons

<AccountCircle Fill="green"/>

그리고, parameter 값을 string 으로 두는 것은 사용자 입장에서 매우 난감할 수 있습니다.
예를 들어, Fill 값은 색을 지칭하는 문자열이어야 하는데, 이를 일반 문자열 형식으로 지정하면 사용자에게 아무런 가이드를 주지 못하고 때로는 에러를 유발합니다.

문자열 대신에 닷넷의 Color 형식을 사용하는 방법이 있습니다.

<svg ... fill="@FillCode"></path>
[Parameter] public Color Fill { get; set; } = "#000000";

private string FillCode => Fill.AsCodeString();

마지막으로, class 값을 문자열 파라미터(Class)로 받는 것은 신중할 필요가 있습니다.
앞서 설명한 가이드를 받지 못하는 문제(인텔리센스가 동작하지 않음 포함)도 있지만, 요소에 적용할 수 있는 class 값을 제한하지 않으면 예상치 않게 동작할 수 있기 때문입니다.

이는 Bootstrap 처럼 많은 css 라이브러리들이 고려하는 측면이기도 합니다.
블레이저는 아직 부모요소에서 지정한 class 값을 자식 요소로 전달하는 수단을 제공하지 않기 때문에, 유효한 클래스 값을 파라미터로 지정하는 방법이 그나마 최선이었던 것 같습니다.

5개의 좋아요

@BigSquare 앗 좀 쉬려고 하던 참인데 이렇게 귀한 답변 주시네요! 밤에 잘 반영해서 글 이어가보도록 하겠습니다, 고맙습니다.

3개의 좋아요

(자주 쓰기 위해 대충… 씁니다)

2. JamesIcon 고도화

처음으로 설계했던 JamesIcon Component를 좀 더효율적으로 관리하기 위해 몇 가지 변경 작업이 진행됩니다.

YML 관리 도입하기


SVG 데이터 리소스를 관리하기 위한 방법 중, YML을 통한 관리 방식이 있습니다. 기존의 클래스를 통한 관리방식에서 아래와 같이 .yml 파일을 통해 리소스를 등록하는 방식으로 변경해 봅시다.

Geometies.yml

images:
  ACCOUNT_CIRCLE: "M12,19.2C9.5,19.2 7.2..."
  ACCOUNT_BOX: "M6,17C6,15 10,13.9 12,13..."
  EARTH: "M17.9,17.39C17.64,16.59 16.89,..."
  ACCOUNT_SUPERVISOR_CIRCLE: "M12,2C6.47..."
  HEART_MULTIPLE: "M13.5,20C6.9,13.9 3.5..."
  HEART: "M12,21.35L10.55,20.03C5.4,15.3..."
  HEART_OUTLINE: "M12.1,18.55L12,18.65L1..."
  HEART_CIRCLE: "M12,2C6.47,2 2,6.5 2,12..."
  STAR: "M12,17.27L18.18,21L16.54,13.97L..."
  STAR_CIRCLE: "M16.23,18L12,15.45L7.77,..."
  STAR_OUTLINE: "M12,15.39L8.24,17.66L9...."
  COG: "M12,15.5A3.5,3.5 0 0,1 8.5,12A3...."
  PEN: "M20.71,7.04C21.1,6.65 21.1,6 20...."
  PLAYLIST_EDIT: "M3 6V8H14V6H3M3 10V12H..."
  PLUS: "M19,13H13V19H11V13H5V11H11V5H13..."

YML은 Json 포멧보다 더 단순한 구조를 가지며 지금처럼 단순한 구조에서 효율적으로 리소스를 관리할 수 있습니다.

그리고 파일의 빌드 옵션을 Embedded resource 타입으로 지정하면 Asembly를 통해 손쉽게 파일을 읽어올 수 있습니다. 그리고 YmlDotnet 누겟 패키지를 통해 데이터를 Deserialize 합니다.

using var stream = Assembly
    .GetExecutingAssembly()
    .GetManifestResourceStream(resourcePath);
...
// 기존과 동일하게 Dictionary 형태로 만들어서 사용합니다.
_svgData = yamlData["images"];

이 방식은 WPF에서도 리소스를 관리할 때 정말 많이 사용했었죠, 그럼 소스코드 전체도 한번 살펴봅시다.

Iconrepository.cs 전체 소스코드

using Jamesnet.Shared.Local.Models;
using System.Reflection;
using System.Text;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

public class IconRepository
{
    private Dictionary<string, string> _svgData = new();

    public IconRepository()
    {
        LoadYmlData("Jamesnet.Shared.Local.Datas.Geometries.yml");
    }

    private void LoadYmlData(string resourcePath)
    {
        using var stream = Assembly
            .GetExecutingAssembly()
            .GetManifestResourceStream(resourcePath);

        if (stream == null) return;

        using var r = new StreamReader(stream, Encoding.UTF8);
        var deserializer = new DeserializerBuilder()
            .WithNamingConvention(UnderscoredNamingConvention.Instance)
            .Build();

        var yamlData = deserializer
            .Deserialize<Dictionary<string, Dictionary<string, string>>>(r);

        if (yamlData?.ContainsKey("images") == true)
        {
            _svgData = yamlData["images"];
        }
    }

    public string GetImageData(IconType imageType)
    {
        return _svgData.TryGetValue(ConvertEnumToYmlKey(imageType), 
            out var data) ? data : "Image data not found.";
    }

    private string ConvertEnumToYmlKey(IconType imageType)
    {
        var enumName = imageType.ToString();
        var sb = new StringBuilder();
        foreach (var ch in enumName)
        {
            if (char.IsUpper(ch) && sb.Length > 0) sb.Append('_');
            sb.Append(char.ToUpper(ch));
        }
        return sb.ToString();
    }
}

재미있지 않나요? 여전히 웹 개발을 해본 경험이 없는데도 Blazor를 통해 이렇게 재밌게 홈페이지를 만들 수 있는 것이 매력입니다.

Component 상속


먼저 @BigSquare 님의 글을 몇 번, 읽어보면 좋습니다.


파생된 EarthIcon.cs

using Jamesnet.Shared.Local.Models;

namespace Jamesnet.Client.Shared.Icons
{
    public class EarthIcon : JamesIcon
    {
        public override IconType IconType { get; set; } = IconType.Earth;
    }
}

우아하죠, 사실 @BigSquare 언급해주시기 전까지도 Component를 클래스로 인식조차 못했습니다. (현타)

<ul class="article-statistics">
    <li><EarthIcon Class="stat-icon earth"/> 17</li>
    <li><AccountSupervisorCircleIcon Class="stat-icon account" /> 1,294,427</li>
    <li><HeartCircleIcon Class="stat-icon heart" /> 114,425</li>
    <li><StarCircleIcon Class="stat-icon star" /> 1,926</li>
</ul>

조금씩 정돈되어 가는 느낌입니다. (갈대처럼 계속 바뀌겠죠.)

5개의 좋아요

3. Blazor에서의 속성 정의


Blazor에서는 Binding 속성에 대해 아주 관대합니다. (WPF와 비교하여)

private string Name { get; set; }
private string Address;

접근제한자 타입을 private으로 사용하더라도 바인딩이 가능합니다. 실제로 code와 html 영역이 한 곳에 있기 때문일까요? 그리고 실제로도 private이 더 자연스러워 보입니다.

그리고 컴포넌트의 경우 DependencyProperty 처럼 외부에서 속성으로 사용하고 싶은 경우에는 [Parameter] 어트리뷰트를 이용하면 됩니다.

[Parameter] public UserItem SelectedItem { get; set; }

물론 이 때는 public 접근제한자도 함께 사용해야 합니다.

3개의 좋아요

4. Blazor에서 <input> 요소를 통한 단방향/양방향 데이터 바인딩


1. <input> 요소에서의 단방향 바인딩

단방향 바인딩은 데이터가 한 방향으로만 흐르는 구조입니다. Blazor에서 input 요소에 단방향 바인딩을 적용할 때는 value 속성에 C# 변수를 할당함으로써 구현할 수 있습니다. 이 경우, UI 요소는 C# 변수의 값을 표시하지만, 사용자 입력에 의한 변수의 업데이트는 발생하지 않습니다.

단방향 바인딩

<input type="text" value="@UserInfo.UserName" />

이 코드를 보면, 사용자가 입력 필드에 값을 입력하더라도 UserName 변수는 변경되지 않습니다. 이 방식은 변수의 초기값을 입력 필드에 표시할 때 유용합니다.

양방향과 비교하기 위해 input 요소를 사용한 것이지, 사실 이 엘리먼트에 단방향을 사용할 일은 거의 없겠져 :smile:

따라서 우리가 일반적으로 사용하는 바인딩이 모두 이와 동일한 방식이기도 합니다.

2. <input> 요소에서의 양방향 바인딩

양방향 바인딩은 데이터가 두 방향으로 흐르며, 사용자 입력과 프로그램 상태가 실시간으로 동기화됩니다. Blazor에서 input 요소에 양방향 바인딩을 적용하려면 @bind 지시어를 사용합니다. 이 구성은 입력 필드의 값이 변경될 때마다 연결된 변수도 (내부적으로) 함께 업데이트되고, 변수의 값이 프로그램적으로 변경되면 입력 필드에도 그 변경이 반영됩니다.

양방향 바인딩

<input type="text" @bind="UserInfo.UserName" />

UserName 변수는 input 요소의 값과 직접 연결됩니다. 사용자가 입력 필드에 텍스트를 입력하면, 그 입력값은 자동으로 UserName 변수에 반영되어, 다른 부분에서도 해당 값을 사용할 수 있습니다.

WPF로 비유하자면 아래와도 같습니다.

<TextBox Text="{Binding UserName, Mode=TwoWay}"/>

3. @bind?

@bind가 나오면 양방향 바인딩이구나…

Blazor에서 input 요소를 사용한 데이터 바인딩은 애플리케이션에서 동적인 상호작용을 구현하는 데 매우 유용하게 사용됩니다. 이를 통해 단순히 초기 바인딩만으로도 사용자에 의해 변경된 값을 손쉽게 관리할 수 있습니다.


스포일러:

다음 회에서는 input 및 기타 요소에 기본으로 제공되는 @bind를 넘어서, 컴포넌트에서 직접 양방향 바인딩 속성을 만드는 방법에 대해 살펴볼 예정입니다. EventCallback

5개의 좋아요

블레이저를 언급할 때, 아래의 두 개를 지칭하는 용어의 구분이 좀 필요합니다.

  1. Html element
  2. Razor component

영어로는 쉽게 구분이 가지만, 이를 한글로 표현하다 보면 “요소” 라는 한 단어로 쉽게 퉁치기 쉽습니다.

대부분의 경우 구분이 중요하지 않고, 문맥 상 구분이 어렵지도 않지만, 특히 데이터 바인딩 문맥에서는 조금 주의가 필요합니다.

저는 보통

Html 요소를 "태그"로,
레이저 요소를 "요소"로,
C# 코드를 "코드"라고 부릅니다.

이 용어에 입각하면,

아래는 태그와 코드 사이의 바인딩이고,

<input type=“text” @bind=“UserInfo.UserName” />

아래는 요소와 코드 사이의 바인딩입니다.

<InputText @bind-Value=“UserInfo.UserName” />

기본적으로 서로 문법이 다릅니다.
저도 초창기에 이점에 대해 명확한 인지를 못해서, 많이 헷갈려 했죠.

이 슬로그를 이어 나가실 때, 이점을 참고하신다면, 읽는 분들이 좀 더 수월하고 명확하게 받아 들일 것 같습니다.

참고로, 아래의 언급을 엄밀히 따지자면, 요소와 코드의 바인딩이라고 할 수 있습니다.

TextBox 는 XML 태그가 아니라 XAML 콘트롤이기 때문이죠.

4개의 좋아요

InputText는 input type=text를 래핑한 컴포넌트군요.

쉬어가는 타임으로 InputText와 같은 컴포넌트를 더 찾아보겠습니다.


image

안녕하세요, 닷넷데브 여러분! 저는 GPT-4입니다. Blazor 연구소 Slog에 참여하게 되어 정말 기쁩니다.

Blazor는 ASP.NET Core를 기반으로 하는 웹 프레임워크로, C#과 Razor 템플릿을 사용하여 대화형 웹 UI를 구축할 수 있습니다. Blazor에서는 다양한 입력 컴포넌트를 제공하며, 이러한 컴포넌트를 사용하여 데이터를 입력받고, 폼을 관리할 수 있습니다. 여기 Blazor에서 자주 사용되는 몇 가지 입력 컴포넌트를 나열하겠습니다:

  1. InputText: 텍스트 데이터 입력을 위한 표준 HTML input 요소를 래핑합니다. @bind-Value를 사용하여 데이터 바인딩을 지원합니다.
  2. InputTextArea: 여러 줄의 텍스트 입력을 위한 HTML textarea 요소를 래핑합니다.
  3. InputNumber: 숫자 데이터를 입력받는 HTML input 요소를 래핑합니다. 정수 및 부동 소수점 숫자를 지원합니다.
  4. InputDate: 날짜 데이터를 입력받는 HTML input 요소를 래핑합니다. DateTime 형식의 데이터 바인딩을 지원합니다.
  5. InputCheckbox: 체크박스 입력을 위한 HTML input 요소를 래핑합니다. 불리언 데이터 바인딩을 지원합니다.
  6. InputRadio: 라디오 버튼 그룹을 생성하여 옵션 중 하나를 선택할 수 있도록 합니다. 각 라디오 버튼은 별도의 값을 가질 수 있습니다.
  7. InputSelect: 드롭다운 목록을 제공하는 HTML select 요소를 래핑합니다. 이를 통해 사용자는 여러 옵션 중 하나를 선택할 수 있습니다.
  8. InputFile: 파일 선택과 데이터 바인딩을 위한 입력 요소입니다. 파일 업로드 시에 사용됩니다.

이러한 컴포넌트들은 Blazor의 폼 및 데이터 바인딩 기능과 함께 사용하여 강력한 사용자 인터페이스를 구성할 수 있습니다. 또한, 커스텀 컴포넌트를 만들어 특정한 요구 사항을 충족시킬 수도 있습니다.


다시 돌아와서,

저처럼 웹 개발 환경에 익숙하지 않으신 분들은 이 컴포넌트 컨트롤들을 사용해보셔도 좋을 것 같습니다. 덤으로 무엇이 래핑되어 있는지 살펴보면서 사용하면 정말 좋겠죠. 단, CSS와 스크립트를 얼마나 자유롭게 사용할 수 있는지도 기회가 된다면 한번 적용해서 살펴볼 예정입니다.

MudBlazor 컴포넌트는 실제로 원하는 대로 잘 안 되더라구요. 초보자가 하기에는 너무 어렵다는 생각입니다, 그래서 모두 폐기한 상태입니다.

4개의 좋아요

저역시도 여러 컴포넌트 전전하다가 가벼운 QuickGrid와 Fluent-UI Blazor의 Dialog만 커스터마이징해서 사용합니다. 2개만 있으면 나머지는 특별한게 없는 것 같습니다.

그리고, 개인적으로는 Html Element는 태그나 요소, Razor Component는 컴포넌트, 구성요소로 해석해서 사용하고 있습니다.

요소는 더 이상 간단히 나눌 수 없는 것을 나타낼때 사용하는 말이고 컴포넌트는 요소와 기능들을 이용해서 구성가능하기에 저는 그렇게 사용합니다.

가끔 마소의 기계적 한국어 번역보다는 영어 원문을 보면서 훨씬 이해하기 편하다는 생각을 합니다.

5개의 좋아요

6. 격리된 CSS에서 자식 접근 하기 (razor.css)


이 방법을 몰라서 dotnet/aspnetcore 레포 문 앞까지 다녀왔습니다. (눈물)

결론부터 말하면 격리된 CSS에서도 아래와 같이 ::deep을 통해 자식 요소를 찾는 것이 가능합니다.

::deep

아래 razor.cs 내용을 통해 이를 확인할 수 있습니다.

.vote {
    ...
}
    .vote:hover {
        ...
    }
    .vote ::deep svg {
        width: 14px;
        height: 14px;
        margin-right: 2px;
    }
        .vote ::deep svg path {
            fill: #CCCCCC;
        }

.vote-checked {
   ...
}
    .vote-checked ::deep svg path {
        fill: #0969DA !important;
    }

svg, path는 .vote 컴포넌트의 내부에 자리하고 있기 때문에, 격리된 CSS에서는 기본적으로 접근을 허용하지 않습니다.

그래서 이 때 ::deep을 통해 하위 요소의 접근이 가능해집니다.


이 방법은 @naratteu 님의 도움을 받았습니다.

이제, 저는 글로벌 css에 의존하지 않고도 컴포넌트를 분산화 시킬 수 있겠네요.

다음 스포일러는 지금까지 연구된 기술을 바탕으로 ToggleButton을 한번 만들어 볼 계획입니다. (이미 만들어 놨지만…) 독립된 컴포넌트, 양방향 바인딩과 razor.css를 통해 제가 어떻게 컴포넌트를 설계 했을지 살펴보는 시간이 되겠습니다.

image

3개의 좋아요

7. 의존성 주입 방식


Blazor의 독창적인 구조답게 의존성 주입을 사용하는 방법도 다양하게 지원합니다. 그리고 이 기능은 ComponentBase를 통해 파생된 모든 곳에서 사용할 수 있습니다.

_Imports.razor에서 사용

@Inject JSRuntime JSRuntime

공용 컴포넌트인 _Imports.razor에서는 using 지시어 뿐만 아니라 이렇게 Inject 까지도 선언이 가능합니다.

클래스에서 사용

public class JamesComponent : ComponentBase
{
    [Inject] protected JSRuntime JSRuntime { get; set; }
}

클래스에서의 사용도 마찬가지입니다. 여기서 만약 WPF처럼 클래식하게 생성자를 통해 의존성 주입을 시도하게 되면, 실제 파생된 컴포넌트에서 당연하게도 에러가 납니다.

생성자를 통한 의존성 주입 (에러)

public class JamesComponent : ComponentBase
{
    public JamesComponent(JSRuntime JSRuntime)
    {

    }
}

에러 내용

Severity Code Description Project File Line Suppression State
Error CS7036 There is no argument given that corresponds to the required parameter ‘JSRuntime’ of ‘JamesComponent.JamesComponent(IJSRuntime)’ Jamesnet.Client C:\jamesworks\jamesnetdev\src\Jamesnet\Client\Microsoft.CodeAnalysis.Razor.Compiler.SourceGenerators\Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator\Pages_Detail_Items_CommentContext_razor.g.cs 180 Active

@code 에서 사용

클래스 에서 사용하는 방법과 동일합니다.

컴포넌트에서 사용

Imports.razor에서 사용하는 방법과 동일합니다.


Blazor의 힘은 Inject에서 나오니 다양하게 연구해보면 아주 좋겠습니다.

4개의 좋아요

짧,

8. 중첩된 요소의 포인터 무시하기 (CSS)


Html에서도 WPF의 IsHitTestVisible = false 처럼 포인터 이벤트를 무력화? 시킬 수 있습니다.

WPF에서

IsHitTestVisible = false;

CSS에서

pointer-events: none;

그럼 언제 쓰냐, 아래처럼 말이죠.


속성이 워낙 많으니, 필요할 때마다 메모 하면서 (해봅시다...)
3개의 좋아요

짧,

9. 바인딩을 응용한 CSS 조건부 렌더링, 그리고 중첩 CSS


Blazor에서 바인딩과 CSS를 사용하여 조건부로 HTML 요소를 렌더링하고 스타일을 유연하게 정의할 수 있습니다. 특히 이미 정의된 스타일이 존재하더라도 중첩해서 이를 추가할 수 있습니다.

HTML

<div class="comment @(comment.IsEditMode ? "block" : "")">
    ...
</div>

위와 같이 바인딩을 통한 조건부 렌더링, 그리고 스타일을 중첩시켜 더욱 풍부하고 유연한 처리가 가능해집니다.

CSS

.comment {
    ...
    display: none;
}


.block {
    display: block;
}

Blazor의 격리된 CSS 구조를 통해 중첩 스타일 활용이 아주 재밌네요, 여러분도 다양하게 활용해보면 좋을 것 같습니다.

3개의 좋아요

@bind@bind-value 는 어떤 차이인가요?

1개의 좋아요

@산업역군 이 글 살펴보시면 좋을 것 같습니다!

c# - @bind와 @bind 값의 차이점 - 스택 오버플로 (stackoverflow.com)

1개의 좋아요

감사합니다!

1개의 좋아요

10. 자바스크립트에서 격리된 CSS 지정 방법


때로는 자바스크립트를 이용해 동적으로 엘리먼트 요소(tag)를 생성하고 함께 CSS를 지정하게 됩니다. 이때 생성 자체는 문제가 없지만, 지정해야 할 CSS가 특정 컴포넌트의 격리된 CSS를 대상으로 한다면 문제가 될 수 있습니다.

브라우저 디버깅을 통해 렌더링된 HTML을 살펴보면, 각 요소마다 해당 컴포넌트를 식별하기 위한 값이 할당됩니다.

<div b-27v7hnfczn>
    <div b-s2bwlm14yc>
        <button b-s2bwlm14yc/>
    </div>
</div>

각 컴포넌트는 동일한 식별 값을 부여받습니다. 따라서 자바스크립트를 통해 해당 컴포넌트에 어떠한 요소를 추가하고 격리된 올바른 CSS를 지정하더라도 컴포넌트로부터 부여받은 값이 없으면 CSS 스타일 설정이 적용되지 않습니다.

이를 해결하기 위해 아래와 같이 코드를 작성했습니다.

function createNewPage() {
    const page = document.createElement('div');
    page.className = 'page';
    page.setAttribute('b-s2bwlm14yc', ''); 
    return page;
}

setAttribute 함수를 통해 컴포넌트 식별 값을 반드시 포함시켜야 합니다. 예시 코드에서는 하드코딩 했지만, 이를 자동으로 가져오기 위해서는 전략이 필요해 보입니다.

이와 관련하여 더 살펴보고 싶으신 분은 여기 CSS Isolation 관련 문서를 참고해보시기 바랍니다.

2개의 좋아요