Aspnetcore: Komponen berbasis template

Dibuat pada 27 Mar 2018  ·  60Komentar  ·  Sumber: dotnet/aspnetcore

Misalnya, Anda harus dapat membuat komponen Grid atau ListView yang mengikat setiap item dalam koleksi menggunakan template yang ditentukan.

area-blazor

Komentar yang paling membantu

Pandangan pribadi saya adalah bahwa Razor harus menempel sedekat mungkin dengan idiom HTML, daripada melayang ke arah idiom yang kita lihat di XAML. Itu berarti terlihat lebih seperti hierarki elemen yang Anda lihat di <table> daripada hal-hal khas XAML. Kami bisa menjadi cukup umum untuk mendukung Anda, tetapi saya masih sangat teguh di kamp "Razor adalah HTML" sekarang.

Semua 60 komentar

Secara khusus, ini berarti kita memerlukan beberapa cara untuk memberi sinyal bahwa bagian tertentu dari konten anak dalam suatu komponen harus dikompilasi sebagai Action<RenderTreeBuilder, T> , untuk beberapa tipe T yang harus dapat kita selesaikan secara dinamis dari penggunaan di desain waktu. Kemudian codegen waktu desain kami akan dapat menghasilkan konteks intellisense yang benar saat menulis ekspresi Razor di dalam elemen anak itu.

Contoh:

<MyListWithHeader RowItems=@People>
    <Title>
        Here I'm still in the outer context so can reference properties on the component itself 
        such as <strong i="8">@TitleText</strong>
    </Title>
    <RowTemplate> <!-- Somehow need intellisense to know that RowTemplate is meaningful here, and that its "item" value is taken from the generic type arg on the RowItems property -->
        Here I'm in the context of an Action<RenderTreeBuilder, Person> because we supplied
        an IEnumerable<Person> as the value for RowItems, so I can write @item.FirstName
    </RowTemplate>
</MyListWithHeader>

<strong i="9">@functions</strong>
{
    string TitleText { get; set; }
    IEnumerable<Person> People { get; set; }

    class Person
    {
        public string FirstName { get; set; }
    }
}

Saya tidak yakin seberapa umum kami ingin membuat fitur ini. Mungkin kurang umum daripada yang ditunjukkan dalam sketsa di atas. @rynowak akan menjadi ahli tentang apa yang masuk akal untuk diterapkan di sini. Semua kerumitan ada pada waktu desain/kompilasi. Itu tidak memerlukan dukungan runtime yang dapat saya pikirkan.

Hai mencoba mencapai ini dengan melewati IEnumerable saya sendirisebagai parameter, saya dapat menghasilkan kisi tetapi saya tidak dapat memperbaruinya ketika parameter berubah. Ini kodenya:

<table class="@ClassName">
  <tr>
    <strong i="7">@foreach</strong> (var col in Columns)
    {
      <th>@col</th>
    }
  </tr>

  <strong i="8">@foreach</strong> (System.Data.DataRow row in table.Rows)
  {
    <tr>
      <strong i="9">@foreach</strong> (var col in Columns)
      {
        <td>@row[col]</td>
      }
      <strong i="10">@if</strong> (Select != null)
      {
        <td><a @onclick(() => Select(row))>Seleccionar</a></td>
      }
    </tr>
  }
</table>

@functions{
    public string ClassName { get; set; }
    public List<string> Columns { get; set; }
    public object DataSource { get; set; }
    private System.Data.DataTable table;


    private string Log;

    //call backs it seens I will have many of them

    public Action Change { get; set; }
    public Action<object> Select { get; set; }

    protected void UpdateState()
    {
      int count = ((IEnumerable<object>)DataSource).Count();
      Log = $"Table count rows:{table.Rows.Count} count object ${count}";
      StateHasChanged();
    }

    protected override void OnInit()
    {
      base.OnInit();

      Change += StateHasChanged;

      var enumerable = (IEnumerable<object>)DataSource;
      table = new System.Data.DataTable();

      if (Columns == null)
      {
        Columns = new List<string>();
        Columns.AddRange(enumerable.FirstOrDefault().GetType().GetProperties().Select(x => x.Name).ToList());
      }


      Columns.ForEach(x =>
      {
        table.Columns.Add(x);
      });

      enumerable.ToList()
                .ForEach(x =>
                {
                  var row = table.NewRow();

                  foreach (var property in x.GetType().GetProperties())
                  {
                    row[property.Name] = property.GetValue(x);
                  }

                  table.Rows.Add(row);
                });

      StateHasChanged();
    }
}

@SteveSandersonMS , apakah Title dan RowTemplate dalam contoh Anda dimaksudkan sebagai properti MyListWithHeader atau komponen khusus? Jika itu adalah properti, mungkin ada baiknya memikirkan cara alternatif untuk mengekspresikan ini...

Saya sering bekerja dengan XAML dan ketika saya mulai membuat prototipe dengan Blazor, saya segera merasakan kebutuhan akan fitur ini. Apakah menurut Anda sintaks elemen properti XAML bisa masuk akal dalam skenario ini? Meskipun cukup bertele-tele, ini menyatakan dengan jelas bahwa kami menetapkan properti di sini alih-alih menambahkan komponen anak ke MyListWithHeader . Contoh:

<MyListWithHeader>
    <MyListWithHeader.Title>
       ...
    </MyListWithHeader.Title>
    <MyListWithHeader.RowTemplate>
       ...
    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

Pandangan pribadi saya adalah bahwa Razor harus menempel sedekat mungkin dengan idiom HTML, daripada melayang ke arah idiom yang kita lihat di XAML. Itu berarti terlihat lebih seperti hierarki elemen yang Anda lihat di <table> daripada hal-hal khas XAML. Kami bisa menjadi cukup umum untuk mendukung Anda, tetapi saya masih sangat teguh di kamp "Razor adalah HTML" sekarang.

sepenuhnya mendukung ini. jika kita dapat membuat daftar item "ObservableCollection" daripada "IEnumerable" itu akan menjadi bonus. bergerak menuju xaml like pattrens memberi kita sistem templat yang tepat, yang tidak dimiliki html, semua kerangka kerja populer di web ui memiliki sistem templat, yang membuatnya sangat dapat diperluas, jelas blazer juga harus memilikinya, dan alih-alih membuat yang baru akan sangat bagus untuk memiliki satu yang sudah dicintai oleh MS dan banyak pengembang .Net juga, secara teknis masih akan berbasis html/css sehingga semua pengembang html masih akan tertarik pada teknologi karena tidak akan xaml (tidak tahu mengapa html guyx membenci xaml) tetapi hanya akan mengikuti pola xaml.

@DamianEdwards Saya sepenuhnya setuju dengan Anda mengenai "Razor adalah HTML". Namun, nilai atribut dalam HTML selalu ditentukan sebagai atribut dalam tag pembuka. Tidak ada cara untuk (dan tidak perlu) mengekspresikan nilai atribut yang kompleks seperti template sebagai elemen anak. Misalnya kamu tidak bisa menulis

<a>
    <href>http://dot.net</href>
</a>

Jadi saya pikir apa yang kami tuju secara teknis tidak dapat diekspresikan dalam HTML murni.
Berikut ringkasan opsi yang saya lihat saat ini:

  1. Tetap dengan sintaks HTML : Nilai properti hanya dapat ditentukan dalam tag pembuka suatu komponen.

    • Saya pikir itulah yang dipilih React, contoh:
      <MyListWithHeader RowTemplate={(data) => <p>...</p>} />
    • Pro : Sangat dekat dengan sintaks HTML biasa
    • Con : Menjadi berantakan untuk nilai multiline, yaitu template yang lebih kompleks
  2. Tetap menggunakan sintaks HTML : Bergantung pada konteksnya, simpul anak dari suatu komponen dapat mewakili elemen bersarang atau penetapan properti.

    • Pro : Sangat dekat dengan sintaks HTML biasa
    • Con : Mungkin membingungkan karena penetapan properti dan elemen turunan secara sintaksis tidak dapat dibedakan. Dalam contoh, <Title>...</Title> mungkin merupakan komponen yang berdiri sendiri sedangkan <RowTemplate>...</RowTemplate> mungkin merupakan properti (atribut) dari <MyListWithHeader> - tanpa IntelliSense Anda tidak benar-benar tahu
    • Con : Dapat menyebabkan konflik penamaan jika ada komponen dan properti dengan nama yang sama
  3. Ciptakan sintaks baru : <strong i="39">@functions</strong> { } atau sesuatu yang sama sekali berbeda.

    • Pro : Mengizinkan templat yang rumit secara sewenang-wenang sambil tetap dapat dibaca
    • Con : Mungkin mengasingkan pengembang web

Sangat mungkin bahwa saya salah paham tentang sesuatu di sini. Jika demikian, tolong beri tahu saya.

@SvenEV Saya tidak berpikir sintaks xaml akan mengasingkan siapa pun, jika Anda perhatikan, sintaks templat untuk sudut, vue, reaksi, dan jutaan kerangka kerja lainnya, semuanya memiliki cara yang berbeda dan sintaks yang berbeda untuk melakukan hal-hal, masih semuanya dicintai, TIDAK? demikian pula blazor dapat memiliki sintaksnya sendiri, satu-satunya pro dalam kasusnya adalah kita tidak akan menemukan sintaks baru hanya dengan membawa sintaks yang sudah populer ke kerangka kerja ini (maka kurva belajar lebih sedikit untuk xaml devs) tetapi sejauh menyangkut pengembang web, mereka akan mempelajari template blazor apakah blazor mengikuti xaml atau tidak, kurva belajar untuk pengembang web tetap sama, satu-satunya cara pengembang web menghindari kelemahan ini adalah jika blazor menggunakan html langsung, yang menurut saya tidak akan dapat diperluas seperti a model templat. :)

jadi sintaks template yang benar-benar baru memberikan kurva belajar kepada setiap pengembang. tetapi sintaks seperti xaml memecahkan setidaknya masalah setengah pengembang. tetapi xaml juga sangat mirip dengan html, keduanya adalah markup. setelah Anda mempelajarinya, itu akan menjadi sepotong kue, hal yang baik tentang cara xaml adalah, Anda tidak perlu khawatir tentang css sama sekali, semua gaya dilakukan dengan properti lurus pada kontrol. dan bahkan melengkapi penataan juga dilakukan dengan sintaks yang sama, tetapi di sisi lain html dan css adalah bahasa yang sama sekali berbeda dengan sintaks dan struktur yang berbeda dalam melakukan sesuatu.

Tetapi mengapa kita bahkan membutuhkan template? Maksud saya ketika Anda bisa melakukan foreach di Razor. Tampaknya jauh lebih mudah bagi saya daripada templating.
Atau apakah ini karena penggunaan kembali? Saya bisa membayangkan komponen terpisah yang melakukan foreach dalam kasus itu.

@MihaMarkic template menstandardisasi semuanya, meningkatkan penggunaan kembali, dan yang paling penting menyembunyikan semua kode kompleks yang ditulis untuk satu kontrol dan Anda dapat menggunakan kontrol bungkus itu dengan 2 baris kode dan lebih fokus pada logika bisnis Anda alih-alih menciptakan kembali roda.

Yah, saya tidak tahu tentang ini. IMO foreach adalah template, hanya yang lebih kuat dan Anda dapat menempatkan template "foreach" di komponen yang berbeda dan memanggilnya dari mana saja dengan dua baris kode tersebut.

foreach hanyalah sebuah loop dan ya untuk kontrol yang lebih sederhana kita bisa melakukan ini, seperti untuk ListView dasar ( table ) kita cukup membuat kontrol yang mengambil beberapa input, melakukan foreach di atasnya dan mengeluarkan tabel, dan kemudian kita hanya perlu untuk menempatkan satu tagkira-kira seperti ini dan kemudian kita akan dapat menghasilkan tabel, juga jika kita menginginkan tampilan daftar yang disesuaikan, dengan desain yang sangat modern, sangat interaktif dll, maka kita dapat membuat kontrol komponen lain dengan foreach, dan banyak css cutom, dan banyak lagi kode blazor dalam satu file, mungkin beberapa file, dan bungkus semua itu dalam 1 komponen, lalu gunakan lagi semua itu hanya dengan 1 tag. Anda melihat di mana saya mendapatkan dengan ini? tidak peduli berapa banyak kode yang dimiliki kontrol, semua itu dapat dibungkus menjadi 1 tag :) @MihaMarkic

Ini bukan hanya tentang merender beberapa item dengan template yang sama. Ini juga diperlukan untuk membuat beberapa bagian dari komponen dapat disesuaikan. Saat ini, sebuah komponen hanya dapat memiliki satu "lubang" dalam tata letaknya yang dapat diisi dengan ChildContent , properti yang telah ditentukan sebelumnya, seperti ini:

<div style="some fancy styling">
    <strong i="7">@ChildContent</strong>
</div>

Sekarang bayangkan Anda ingin menulis komponen dengan beberapa "lubang" seperti dialog yang memungkinkan Anda menyesuaikan konten serta header, atau tombol di mana Anda dapat menyesuaikan konten bagian dalam tetapi juga menentukan template untuk menu konteks dan tooltip mungkin. Jadi alangkah baiknya jika Anda bisa menulis sesuatu seperti berikut (apa pun sintaksnya nanti):

<MyButton>
    <h2>I am the button caption (the usual ChildContent)</h2>

    <MyButton.ContextMenu>
        <button>Context menu button 1</button>
        <button>Context menu button 2</button>
    </MyButton.ContextMenu>

    <MyButton.ToolTip>
        This explains <strong>what the button does</strong>. It is displayed when hovering over the button.
    </MyButton.ToolTip>
</MyButton>

@SvenEV Bukankah itu hanya memiliki banyak lubang dan tidak sepenuhnya membutuhkan templat?

ya itu adalah contoh yang baik dari usecase lain, setiap bagian harus dapat disesuaikan.

@MihaMarkic Memang benar, kita berbicara tentang dua hal yang berbeda di sini:

  1. Dukungan untuk banyak lubang ( RenderFragment s / Action<RenderTreeBuilder> ) seperti pada contoh saya
  2. Dukungan untuk template yang memiliki input data ( Action<RenderTreeBuilder, T> ) seperti pada ListView dll.

Saya pikir yang pertama adalah persyaratan untuk yang kedua dan saya berharap kedua fitur akan diimplementasikan di beberapa titik. Itu akan memungkinkan kita untuk membangun beberapa perpustakaan komponen yang kuat.

Masalah mana yang coba dipecahkan ini? Saya hanya ingin tahu karena saya belum pernah melihat hal semacam ini di kerangka kerja SPA lainnya.

Apakah itu bertentangan dengan struktur berbasis komponen? Juga, apakah itu bertentangan dengan praktik pengembangan web normal hanya untuk memaksa struktur yang lebih "seperti aplikasi"?

@AWulkan itu tidak bertentangan dengan struktur komponen normal, tapi ya itu sedikit mengarah ke struktur seperti pengembang aplikasi, tetapi itu tidak berarti itu menjauh dari pengembangan web, karena semua kerangka kerja populer juga melakukan hal yang sama, seluruh tujuan konsep SPA adalah ini. SPA dibuat untuk membuat pengembangan web lebih seperti pengembangan aplikasi. dan sekarang setiap kerangka mengikuti spa. langkah spa selanjutnya adalah sekarang pwa. kamu tahu apa pwa yang benar? itu memperkenalkan beberapa fitur utama dari aplikasi asli seperti notifikasi dan sinkronisasi offline, jadi pada akhirnya pengembang web secara keseluruhan mencoba mengikuti pengembangan aplikasi asli.

@AWulkan Karena Anda sepertinya akrab dengan Vue, saya baru saja melihat dokumen Vue untuk melihat apakah ada yang serupa. Ada dan itu disebut " Slot ", khususnya "Slot Bernama" dan "Slot Cakupan" - itulah tepatnya masalah ini. Itu konsep yang sama. Mungkin ini membantu Anda memahami masalahnya?

@SvenEV Okey saya agak mengerti apa yang Anda maksud. Ini adalah fitur yang cukup khusus sejauh yang saya ketahui. Terima kasih telah mengklarifikasi.

Apakah akan menggunakan draf ini sebagai model?
https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md

Tampaknya slot Vue.js mirip dengan templating data XAML.
Templating menawarkan cara yang ampuh untuk menyesuaikan komponen yang ada, satu kasus yang bisa saya lihat adalah ListView di mana secara default tanpa templating adalah ia hanya mengulangi daftar string dan menampilkannya.

<ListView list=<strong i="7">@ListOfUrlsAsStrings</strong> />

Dengan templating, Anda dapat menggantinya dengan mengatakan, sebagai gantinya tampilkan daftar tag gambar menggunakan Type T diteruskan.

<ListView list=@ListOfUrlsAsStrings>
    <ListView.ListTemplate>
         <img src=<strong i="12">@item</strong> alt="overriden by a template" />
        <CustomComponentHere />
    </ListView.ListTemplate>
</ListView>

Ini akan sangat meningkatkan reusability komponen karena mereka bisa dinamis. Vendor komponen pihak ketiga dapat lebih kreatif dalam membuat komponen dengan cara itu.

ya persis dengan pengikatan templat data menjadi lebih sederhana dan dapat diamati

Kami sangat berharap bahwa komponen Blazor akan mendapatkan dukungan yang baik untuk bersarang dan templating.
Sebelumnya kami telah melakukan sejumlah diskusi dengan @Mehul , @NTaylorMullen , @DamianEdwards tentang topik yang sama untuk Pembantu Tag Razor di mana kami mencoba menjelaskan mengapa kami membutuhkan lebih banyak pendekatan seperti XAML. Masalah GitHub yang relevan: aspnet/Razor#474, ​​aspnet/Razor#576, aspnet/Razor#570.

Kami mengembangkan komponen UI seperti kisi data dan markup tipikal untuk mengonfigurasi widget dapat menjadi seperti ini:

<TabPanel>
    <Grid>
        <DataSource>
            <More about data source />
        </DataSource>

        <Column />
        <Column>
            <Lookup>
                <DataSource>
            . . .
            <CellTemplate>
                arbitrary html
            </CellTemplate>
        </Column>

        <DetailTemplate>
            <div>arbitrary content</div>
            <ThirdPartyComponent />

            <Grid>
                detail grid configuration with own templates (and there may be more levels)
            </Grid>
        </DetailTemplate>
    </Grid>
</TabPanel>

Untuk hierarki seperti itu, pengembang ingin memiliki kesadaran konteks dan kecerdasan yang akurat.

@AlekseyMartynov dapatkah kita memiliki xaml seperti styling + animasi juga dalam jangka panjang?

Saya sangat senang ide ini mendapatkan daya tarik. Sejak MS mempelopori ide kontrol templateable dengan Silverlight, WPF dan UWP, masuk akal untuk menjadikan ini bagian dari Blazor.

Satu pertanyaan adalah konteks data/kode. Ketika template cshtml mereferensikan kode dalam objek C# terikat yang mungkin tidak akan berada dalam konteks yang sama dengan komponen yang lebih besar. Jadi, bukankah semacam pengikatan data dinamis diperlukan? Atau akankah templat memiliki persyaratan TargetType yang ketat sehingga parser tahu cara menyelesaikan referensi ke anggota objek terikat data?

Pada catatan itu, bagaimana dengan pengikatan data dinamis dan properti ketergantungan? Sejauh yang saya tahu tidak akan ada yang mencegah ini sekarang. Atau apakah refleksi akan menjadi masalah jika C# atau IL akhirnya dikompilasi menjadi wasm daripada ditafsirkan oleh mono?

Menerapkan apa yang disarankan @AlekseyMartynov (atau semacam variasi) menurut saya penting untuk keberhasilan Blazor. Dalam praktiknya, banyak pengembang yang menulis aplikasi perusahaan bergantung pada sekumpulan komponen pihak ketiga yang mudah digunakan dan saya berharap proyek ini akan segera pada titik ketika vendor yang berbeda dapat mulai mengembangkan komponen Blazor.

Dalam proyek saya Xamzor, saya telah menerapkan beberapa dukungan templating terbatas seperti ini:

<ListView ItemTemplate="typeof(MyCustomTemplate)" ... />

di mana MyCustomTemplate adalah komponen Blazor lain dengan properti "DataContext" yang ditetapkan oleh ListView. Meskipun ini bekerja cukup baik, tetap diinginkan untuk mendefinisikan template inline daripada dalam komponen terpisah, terutama jika template kecil dan sederhana.

@SvenEV Saya setuju bahwa dalam kasus templat sederhana, templat sebaris akan membantu tetapi kami masih harus memiliki opsi untuk templat komponen terpisah seperti yang Anda terapkan dalam proyek Anda, karena itu membantu kami dalam pemisahan masalah dan kegunaan kembali templat :)

Tiga hal di kepala saya:

  1. Jadikan komponen secara hierarkis asli (mis. properti ParentComponentType; sehingga Anda dapat menerapkan MyTableComponent -> MyRowComponent -> MyColumnComponent). Jika ParentComponentType adalah null, komponen tersebut dapat menjadi anak dari tipe komponen apa pun (mis. MyTableComponent.ParentComponentType dapat menjadi null).

Hirarki ditegakkan melalui deklarasi dalam definisi komponen seperti:

```C#
// Di MyRowComponent
Ketik ParentComponentType { dapatkan; } = typeof(MyTableComponent);
...

// In MyColumnComponent
Type ParentComponentType {get; } = typeof(MyRowComponent); 
...
Thus, MyColumnComponent can only be created within a MyRowComponent, and MyRowComponent can only be created within a MyTableComponent.

2. Allow components to be extended via ITemplateable interface. When the ITemplateable.Template [Tag] property is valued, it overrides the component's default rendering. Internally, it's something like:

```C#
    if (component is ITemplateable)
    {
        if (component.Template != null)
        {
            … return template rendering ...
        }
    }
    … return default rendering ...
  1. Buat antarmuka ITemplate yang dapat diimplementasikan sebagai kelas dasar opsional BlazorTemplate, yang dapat digunakan untuk mengobjektifikasi pemrograman dengan template.

Contoh penggunaan di mana MyColumnComponent adalah ITemplateable:

    <MyTableComponent>  
        <MyRowComponent>  
            <MyColumnComponent>  
                <Template>  
                    …  template codes here; can contain a call to another component ...
                </Template>  
            </MyColumnComponent>  
        </MyRowComponent>  
    </MyTableComponent>  

Dengan MyColumnComponent didefinisikan sebagai ITemplateable, ini mendukung berisi tag <Template> … </Template> . Entri dalam tag ini menggantikan rendering default MyColumnComponent.

Secara internal, MyColumnComponent mungkin memiliki/menggunakan implementasi BlazorTemplate untuk memungkinkannya menangani perilaku, peristiwa, dan fitur templat khusus secara terprogram.

Saya memilih Sintaks Elemen Properti

<Button>
  <Button.ContextMenu>
    <ContextMenu>
      <MenuItem Header="1">First item</MenuItem>
      <MenuItem Header="2">Second item</MenuItem>
    </ContextMenu>
  </Button.ContextMenu>

  Right-click me!
</Button>

Sebagai alternatif untuk

<Button ContextMenu=@GetContextMenuRenderFragment()>
  Right-click me!
</Button>

Saya menyukainya, tetapi bukankah para puritan web tidak akan menganggap ini terlalu mirip XAML?

Tidak, mereka akan baik-baik saja, property element syntax adalah alternatif dari html attribute syntax (bukan pengganti). Tapi saya keluar dari berapa banyak kemungkinan yang disediakan untuk pengembang komponen (UI mengontrol pengembang perpustakaan)

Saya menyukainya juga karena ini menyampaikan lebih jelas bagaimana anak berhubungan dengan orang tua, ini sangat bagus di Templat karena Anda akan dapat membedakan bahwa itu adalah templat data dari orang tua daripada komponen khusus yang tidak ada hubungannya dengan komponen induk.

Seperti ini:

<List>
    <List.DataTemplate>
      <!-- Template Here -->
    </List.DataTemplate>
    <ComponentWithNoRelationToTheList />
</List>

Ini memberi Anda gambaran bahwa DataTemplate terkait dengan Daftar daripada templat kustom secara keseluruhan. Meskipun saya setuju seperti XAML, itu seharusnya hanya alternatif.

Bagaimana jika "Templat" adalah kata kunci yang dicadangkan/khusus untuk komponen Blazor yang dapat ditempa? Ketika digunakan sebagai tag, dapat berisi kode template. Saat digunakan sebagai atribut, ia dapat menentukan template bernama, yang dapat berisi kode template.

jadi, mari kita bayangkan komponen seperti:

ComponentTemplateTest.cshtml

<strong i="7">@functions</strong>
{
  [Parameter]
  public RenderFragment Template { get; set; }

  [Parameter]
  public RenderFragment<int> TemplateWithInt { get; set; }

  [Parameter]
  public RenderFragment<TestObject> TemplateWithObject { get; set; }

  public class TestObject
  {
      public string PropString { get; set; }
      public int PropInt { get; set; }
  }

  private TestObject testobject = new TestObject();
}

<div>
    <div><button onclick="@((x) => this.testobject.PropInt++)">test</button></div>
    <div>@Template</div>
    <div>@((RenderFragment)(x => TemplateWithInt(x, testobject.PropInt)))</div>
    <div>@((RenderFragment)(x => TemplateWithObject(x, testobject)))</div>
</div>

kita bisa menggunakannya sebagai

<ComponentTemplateTest>
    <Template>
        test simple template
    </Template>

    <TemplateWithInt params="param1">
        <strong i="11">@param1</strong>
    </TemplateWithInt>

    <TemplateWithObject params="paramObject">
        @paramObject.PropInt
    </TemplateWithObject>
</ComponentTemplateTest>

kita membutuhkan atribut seperti params karena Blazor menulis kode sebagai

            builder.OpenComponent<StandaloneApp.ComponentTemplateTest>(16);
            builder.AddAttribute(17, "template", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
                builder2.AddContent(18, "\n        test simple template\n    ");
            }
            ));
            builder.AddAttribute(19, "templatewithint", (Microsoft.AspNetCore.Blazor.RenderFragment<System.Int32>)((builder2, param1) => {
                builder2.AddContent(20, param1);
            }
            ));
            builder.AddAttribute(21, "templatewithobject", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.ComponentTemplateTest.TestObject>)((builder2, paramObject) => {
                builder2.AddContent(22, "\n        ");
                builder2.AddContent(23, paramObject.PropInt);
                builder2.AddContent(24, "\n    ");
            }
            ));
            builder.CloseComponent();

itu akan cukup? sesuatu yang hilang?

@uazo Perhatikan saja, bahwa tanpa Sintaks Elemen Properti tidak mungkin membedakan antara property dan component .

@danieldegtyarev Saya pikir kita berbicara di sini tentang dua hal yang berbeda ... tujuan saya hanya template untuk komponen untuk saat ini ...

lihat https://github.com/uazo/Blazor/tree/dev-experimental-templated-component
Anda perlu membuat dan memasang ekstensi itu...

"Templat" yang digunakan sebagai tag bersifat deklaratif dan sebaris secara alami:

<MyCom MyComProp="MyComPropValue">
    <Template>
        Components, @MyCom.MyComProp, HTML, content and <strong i="6">@values</strong>
    </Template>
</MyCom>

"Templat" digunakan sebagai tag untuk menetapkan "templat bernama". Secara internal, template bernama akan memiliki akses ke komponen induknya, sehingga sesuatu seperti MyCom.MyComProp juga dimungkinkan:

<MyCom MyComProp="MyComPropValue">
    <Template Name="MyTemplate {Content: @content; Values: @values}" />
</MyCom>

"Templat" digunakan sebagai atribut untuk menetapkan "templat bernama". Secara internal, templat bernama akan memiliki akses ke komponen induknya, sehingga sesuatu seperti MyCom.MyComProp juga dimungkinkan:

<MyCom 
    MyComProp="MyComPropValue"
    Template="MyTemplate {Content: @content; Values: @values}" />

"Templat" yang digunakan baik sebagai tag atau sebagai atribut harus menghasilkan kompilasi yang sama, sehingga komponen akan merender info template yang disediakan, yang hanya menggantikan render default komponen.

Sebuah "template bernama" adalah template yang dapat digunakan kembali. Membuat "templat bernama" hampir seperti membuat komponen. Namun, perbedaan besar adalah bahwa template bernama hanya dapat digunakan melalui dukungan komponen untuk "Template".

Sebuah template dapat berisi komponen, tetapi pemeriksaan perlu dilakukan untuk mencegah potensi loop tak terbatas ketika, misalnya, sebuah komponen yang menggunakan template yang menggunakan komponen yang sama dikodekan... mungkin kesalahan desain/waktu kompilasi?

Pertanyaan: Bisakah template bernama mewarisi dari template lain? Bisakah templat bernama dibatasi untuk digunakan hanya dengan jenis komponen tertentu?

Saya pikir kalian harus menggunakan sintaks HTML sebanyak mungkin, jangan memperkenalkan hal-hal baru. Tidak ada sintaks Xamarin xaml atau komponen webforms asp.net jelek (asp repeater dan sebagainya).
Saya dari dunia asp.net mvc, saya ingat kegembiraan dan kebebasan ketika saya beralih dari "asp.net webforms" ke "asp.net mvc", tidak ada lagi komponen aneh seperti asp: repeater dan sebagainya.
Tolong tolong, jangan biarkan BLAZOR menjadi "asp.net webforms" baru.

Saya setuju dengan @GoranHalvarsson . Templat di Blazor seharusnya hanya implementasi dari RenderFragment generik, dengan use case yang sama (contoh pertama di postingan @etmendz ) .

Untuk itu, saya telah memperbarui cabang saya, sekarang dengan dukungan penuh untuk intellisense di visual studio (params dan anak yang diizinkan).

dapatkah seseorang memberi saya umpan balik?

@GoranHalvarsson saya setuju. MVP bisa untuk komponen Blazor agar memiliki dukungan template dasar. Dalam pengertian ini, "Templat" dapat berisi kode HTML, komponen, konten, dan Razor. Pada dasarnya, komponen+templat Blazor masih harus dikenali sebagai kode HTML+Razor. Sintaks deklaratif sesuai dengan tagihan ini, yang dapat berupa kata kunci <Template> atau @template , misalnya.

Item fase berikutnya dapat berupa API/pustaka ekstensibilitas Blazor yang memungkinkan pembuat komponen untuk mempromosikan merek mereka sendiri -- misalnya: sintaks seperti HTML helper (Progress Telerik/Kendo UI) atau sintaks seperti Xamarin/XAML (ooui.wasm, Platform .Uno) dapat berupa pengiriman pihak ke-3/tingkat vendor. Dalam Blazor, programabilitas template dan fleksibilitas kompilasi dapat dikerjakan secara erat dengan mitra pembuat komponen untuk mengatasi masalah/kebutuhan/persyaratan tertentu.

Ada saran di utas ini tentang templat yang dapat digunakan kembali. Template yang dapat digunakan kembali atau "template bernama" dapat mempromosikan penggunaan kembali kode. Meskipun, sejujurnya, saya tidak melihat banyak nilai di dalamnya dan dapat menjadi prioritas rendah. Lebih mungkin, jika itu dimaksudkan untuk digunakan kembali, maka mungkin itu harus menjadi komponen? ;-)

@uazo IMHO, berdasarkan contoh Anda, meneruskan params ke <Template> tampaknya mengalahkan sifat deklaratif hanya dengan menggunakan parameter/nilai langsung di dalam tag <Template> .

Saya juga ragu tentang membiarkan komponen mendukung beberapa properti template. Dalam pengertian ini, membuatnya terlalu gaya bebas. Saya pikir Blazor harus memiliki cara yang lebih sederhana dan lebih standar untuk mendukung templat komponen, mungkin melalui antarmuka ITemplate dan beberapa tweak kompiler di sana-sini.

@SteveSandersonMS untuk https://github.com/aspnet/Blazor/issues/404#issuecomment -377229910
dengan kode aspnet/AspNetCore#15829 akan menjadi

DaftarSayaDenganHeader.cshtml

<strong i="10">@functions</strong>
{
    [Parameter]
    public RenderFragment Title { get; set; }

// NOT MANDATORY
    [Parameter]
    public RenderFragment ChildContent { get; set; }
// *******************

    [Parameter]
    public IEnumerable<example.Person> RowItems { get; set; }

    [Parameter]
    public RenderFragment<example.Person> RowTemplate { get; set; }
}

<div>
    <strong i="11">@Title</strong>
</div>

<strong i="12">@foreach</strong> (var item in RowItems)
{
    <div>
    @RowTemplate.Render(item)
    </div>
}

<strong i="13">@ChildContent</strong>

contoh.cshtml

<strong i="17">@page</strong> "/component-test"

<MyListWithHeader RowItems=@People>
    <MyListWithHeader.Title>
        Here I'm still in the outer context so can reference properties on the component itself
        such as <strong i="18">@TitleText</strong>
    </MyListWithHeader.Title>

    <MyListWithHeader.RowTemplate WithParams="item">
        <!-- Somehow need intellisense to know that RowTemplate is meaningful here, and that its "item" value is taken from the generic type arg on the RowItems property -->
        Here I'm in the context of an Action RenderTreeBuilder, Person
        because we supplied
        an IEnumerable Person as the value for RowItems, so I can write <b>@item.FirstName</b>
    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

<strong i="19">@functions</strong>
{
  string TitleText { get; set; }
  IEnumerable<Person> People { get; set; }

  protected override void OnInit()
  {
      var people = new List<Person>();
      people.Add(new Person { FirstName = "uno" });
      people.Add(new Person { FirstName = "due" });
      people.Add(new Person { FirstName = "tre" });

      this.People = people;
  }

  public class Person
  {
      public string FirstName { get; set; }
  }
}

dengan dukungan penuh untuk intellisense!

@uazo Sangat sangat bagus! Hanya ada satu hal: mari kita hapus WithParams="item" dan cukup gunakan @item untuk mengakses 'konteks' template

uhm.. tapi apakah item sudah ada di komponen example ?
Karena nama variabel yang digunakan untuk mengakses 'konteks' template tidak dapat ditemukan, jadi dengan WithParams , pengembang dapat memutuskan nama variabel.

apakah Anda memikirkan nama default berkabel (seperti ChildContent ), dengan atau tanpa opsional WithParams ? Atau sintaks seperti <MyListWithHeader.RowTemplate WithParams=@item> ?

Melihat ke belakang, nama default tidak cocok untuk templat bersarang...

Mari berpikir. Template adalah pembuatan kode dengan satu parameter. Template hanya memiliki akses ke item ini. Jadi jika kita 'reserve' variabel item - aman dan tidak bisa bentrok. Template bersarang memiliki cakupannya sendiri dan menggunakan @item masih aman.

BTW, sepertinya template ini- scope harus memiliki blok @functions juga

mari kita

<MyListWithHeader RowItems=@People>
    <MyListWithHeader.Title>
        ...<strong i="6">@TitleText</strong>
    </MyListWithHeader.Title>

    <MyListWithHeader.RowTemplate WithParams="item">
        ... <b>@item.FirstName</b>

        <MyListWithHeader>
            <MyListWithHeader.RowTemplate WithParams="item">
                @item.FirstName
            </MyListWithHeader.RowTemplate>
        </MyListWithHeader>

    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

akan menjadi penyaji sebagai

protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
{
    base.BuildRenderTree(builder);
    builder.OpenComponent<StandaloneApp.Pages.testtemplated.MyListWithHeader>(0);
    builder.AddAttribute(1, "RowItems", Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck<System.Collections.Generic.IEnumerable<StandaloneApp.Pages.testtemplated.example.Person>>(People));
    builder.AddAttribute(2, "Title", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
        builder2.AddContent(3, "\n        Here I\'m still in the outer context so can reference properties on the component itself\n        such as ");
        builder2.AddContent(4, TitleText);
        builder2.AddContent(5, "\n    ");
    }
    ));
    builder.AddAttribute(6, "RowTemplate", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.Pages.testtemplated.example.Person>)((builder2, item) => {
        builder2.AddContent(7, "\n        ");
        builder2.AddContent(8, "\n        Here I\'m in the context of an Action RenderTreeBuilder, Person\n        because we supplied\n        an IEnumerable Person as the value for RowItems, so I can write ");
        builder2.OpenElement(9, "b");
        builder2.AddContent(10, item.FirstName);
        builder2.CloseElement();
        builder2.AddContent(11, "\n\n        ");
        builder2.OpenComponent<StandaloneApp.Pages.testtemplated.MyListWithHeader>(12);
// line 43
        builder2.AddAttribute(13, "RowTemplate", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.Pages.testtemplated.example.Person>)((builder3, item) => {
            builder3.AddContent(14, "\n                ");
            builder3.AddContent(15, item.FirstName);
            builder3.AddContent(16, "\n            ");
        }
        ));
        builder2.CloseComponent();
        builder2.AddContent(17, "\n\n    ");
    }
    ));
    builder.CloseComponent();
}

dengan

Error   CS0136  A local or parameter named 'item' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter

StandaloneApp\obj\Debug\netstandard2.0\Pages\testtemplated\example.g.cs line 43

Ya, tentu, Anda harus menyelesaikannya selama codegen.
Tapi... Saya pikir template harus dikompilasi ke component (mungkin komponen khusus) daripada RenderFragment karena kita perlu mengelola dengan statusnya, misalnya untuk memanggil StateHasChanged() ketika @item.FirstName bermutasi.

Ya, tentu, Anda harus menyelesaikannya selama codegen.

Saya tidak berpikir itu mungkin.
Selama codegen (yang dimodifikasi dengan pisau cukur) semua node disilangkan, beberapa adalah html, yang lain adalah c# node: yang dengan c# akan salah karena variabel tidak akan ditentukan dan oleh karena itu jenis variabel tidak akan diketahui ( dan kita perlu memulihkan variabel bertipe T dari RenderFragment<T> , karena kita tahu T ) ... jadi saya tidak tahu apa nama variabel yang digunakan.
Anda mungkin bisa mencari variabel yang tidak terdefinisi itu, tetapi tampaknya berantakan

Tapi... Saya pikir template harus dikompilasi ke komponen (mungkin komponen khusus) daripada RenderFragment karena kita perlu mengelola dengan statusnya, misalnya untuk memanggil StateHasChanged() [email protected] bermutasi.

Saya tidak berpikir perlu untuk memperkenalkan hal lain.
Jika Anda membutuhkan komponen gunakan BlazorComponent , jika Anda membutuhkan "properti template" gunakan RenderFragment<T>
Lagi pula, StateHasChanged() untuk RenderFragment dipanggil oleh komponen yang mendasarinya StateHasChanged() , saya tidak perlu memaksakan perubahan dengan cara lain apa pun

Maaf mengganggu di sini karena kedengarannya seperti percakapan yang bagus - tapi -

Bukankah ini akan diselesaikan dengan konsep Konteks Data seperti WPF/UWP? Kemudian di templat Anda, Anda akan selalu merujuk this.DataContext.[Member] .

@peter0302 Saya pribadi menyukai ide ini...

Tantangannya tetap diketik dengan kuat. Tentu saja ini diatasi dalam XAML dengan pengikatan yang dilakukan pada saat run time. Templat data di sini mungkin harus mendukung pengetikan melalui obat generik. Tapi pasti bisa.

Kemudian lagi saya akan mendukung mekanisme pengikatan runtime yang sepenuhnya dinamis juga.

Anda bisa pergi untuk menyatakan tipe data entah bagaimana melalui deklarasi. Agak mirip dengan x:DataType pada XAML, meskipun menyimpulkan itu harus lebih ideal dan kurang rentan terhadap kesalahan.

@uazo Umpan balik saya adalah tampilannya sangat bagus! Berikut adalah pemikiran saya...

1) Bahwa properti bertipe RenderFragment ditetapkan dengan nama properti yang memenuhi syarat dalam tag seperti <MyListWithHeader.Title /> terasa ok bagi saya, tetapi penanganannya mematahkan intuisi saya sebelumnya bahwa properti dengan ParameterAttribute disetel melalui html-atribut. Bagaimana kalau memiliki c#-atribut lain untuk properti tersebut untuk membedakan perilaku itu? Suka [ContentParameter] ?

2) Bahwa properti bertipe RenderFragment<Person> ditetapkan dengan tag properti dengan atribut
<MyListWithHeader.RowTemplate WithParams="item" /> terasa sedikit aneh pada awalnya. Ini bukan atribut yang menyetel properti parameter, tetapi nama variabel yang digunakan dalam ekspresi lambda selama pembuatan kode lokal ke templat itu. Di sini sekali lagi akan lebih baik untuk memiliki sintaks lain untuk membedakannya. Tetapi saya tidak yakin seberapa kreatif seseorang agar sesuai dengan perkakas dan kompiler. <MyListWithHeader.RowTemplate params(item) /> mungkin?

3) Jika saya ingin menampilkan <MyListWithHeader RowItems=<strong i="16">@PeopleOnline</strong> /> dan <MyListWithHeader RowItems=<strong i="18">@PeopleOffline</strong> /> , apakah saya harus menentukan template lagi? Apakah ada cara untuk menggunakannya kembali? Saya berpikir mungkin membuat beberapa inisialisasi model-driven melalui mekanisme ini, mirip dengan EditorFor dengan pemilih properti yang berbeda menggunakan template yang sama.

Mungkin agak berisiko untuk membahas implementasi ketika saya baru mengenal kerangka kerja tetapi…

jika Anda menggunakan atribut parameter terpisah untuk menetapkan templat seperti yang dibahas pada poin 1 di atas, maka Anda dapat menambahkan flag Boolean (default true) untuk mengatakan apakah nilai default (jika tidak diberikan secara eksplisit) harus diterapkan. Kemudian Anda dapat menyelesaikan poin 3 di atas dengan memperkenalkan antarmuka seperti IDefaultTemplateProvider yang dapat digunakan untuk memberikan definisi untuk tag-properti-templat secara terpisah. Kemudian itu dapat ditemukan melalui refleksi dengan cara yang sama seperti Anda menemukan IComponent(s) dan menggunakannya dalam kasus di mana flag use-default disetel, dan pengguna tidak menyediakannya. Anda dapat mengizinkan beberapa komponen template mengimplementasikan antarmuka tetapi memberikan pengecualian yang tidak ambigu jika tag properti ditemukan beberapa kali. Dengan cara itu Anda dapat memberikan templat standar ke komponen generik khusus untuk aplikasi yang digunakan beberapa kali dan mendapatkan resolusi pada waktu desain, mungkin?

Saya pikir menggambarkan pengalaman belajar saya di sekitar ini memberi Anda latar belakang yang baik…, dan saya memiliki beberapa pemikiran untuk didiskusikan…

Saya ingin membuat konfigurasi komponen berbasis model melalui meta-data dan file sumber daya untuk mengambil teks terjemahan statis, validasi, dan sejenisnya. Saya berpikir mekanisme templating ini bisa menjadi hal untuk mewujudkannya. Untuk mengevaluasinya saya membuat interface public interface IRenderFragment<T> { RenderFragment UsingDataItem(T item); } dan membuat komponen biasa yang juga mengimplementasikan interface tersebut untuk digunakan sebagai template. Kemudian buat komponen generik menggunakannya seperti ini

  <InputField For=@DataSource(Model, x => x.NewTodo) Template="@InputBasic.Template" />

Jadi, dengan cara ini komponen InputField memiliki informasi tentang cara mengambil meta-data, dll dan dapat memasukkannya ke dalam model yang dirender dalam template yang Anda berikan. Ini bekerja dengan sangat baik.

Di XAML ini adalah satu-satunya mekanisme untuk merender sesuatu ketika ada perilaku sekitar yang diimplementasikan dalam suatu komponen, seperti dalam tampilan daftar maka Anda perlu menyediakan templat item. Di Blazor itu tidak perlu karena Anda dapat menggunakan kode dengan loop untuk memiliki perilaku di sekitarnya. Saya kemudian menyadari bahwa saya bisa melakukan hal yang sama di sini. Saya bisa saja mengambil model dari meta-data dalam kode dan memasukkannya ke dalam komponen normal. @{ var inputModel = InputModelHelper.GetFor(ViewModel, x => x.NewTodo); } <InputBasic DataItem="@inputModel"/>

Mungkin jelas bagi sebagian orang… jadi template sebenarnya tidak mutlak diperlukan untuk kasus penggunaan saya. Tapi itu membuat saya bertanya-tanya apa yang sebenarnya saya inginkan. Mungkin lebih sejalan dengan menyuntikkan atribut dan konten ke dalam elemen yang ada? Dan mungkin melakukannya dengan dapat mendefinisikan atribut khusus seperti:

  <label For=”@DataSource(Model, x => x.NewTodo)”></label>
  <input For=”@DataSource(Model, x => x.NewTodo)” />

Untuk menguji apakah mungkin untuk menambahkan simpul atribut ke dalam elemen yang ada, saya membuat fungsi (mengembalikan string kosong untuk simpul konten itu) setelah tag terbuka <label>@For(x => x.NewTodo)</label> yang memperluas proses rendering dengan menambahkan simpul atribut (dan konten node dalam hal elemen label) ke builder. (Pembangun ditangkap dalam metode BuildRenderTree.) Pembangun memiliki posisi simpul label sehingga orang dapat dengan mudah melihat dalam buffernya jika atribut telah ditetapkan dan karenanya tidak boleh ditimpa oleh mekanisme ini. Ini benar-benar bekerja. Ini sedikit lebih sulit untuk elemen input karena ini adalah elemen batal (perlu dibuka kembali dan fungsinya tidak dapat ditempatkan di antara atribut), tetapi sebagai bukti konsep tampaknya berfungsi. Bagaimana seseorang dapat membuat ini didukung dan apakah itu masuk akal?

Saya kira fungsi kelanjutan/ekstensi render ini dapat dikaitkan di beberapa tempat. Untuk membuat Anda berpikir ... mungkin memiliki kemampuan untuk menyuntikkan "RenderElementExtensionHandler" dan jika suatu komponen memiliki properti seperti itu, maka panggilan untuk itu dikompilasi ke posisi ketika semua atribut telah ditambahkan dan setelah menutup kode yang dihasilkan pada elemen-elemen yang memiliki kustom atribut, seperti Untuk dalam kasus ini.
Apakah ini sesuatu yang dapat digunakan untuk hal-hal lain atau ini adalah kasus tepi? Apakah ada ide lain untuk memperluas perilaku ke elemen yang ada?

Maaf kalau agak panjang…

Mengapa tidak membuat alat peraga?

<List RenderHeader={(text) => (<h4>{text}</h4>)}>
    {({ item, index }) => (
        <li value={index} class={item.Type}>
            {item.Content}
        </li>)}
</List>

Selesai:
b2c8b1aec98e65054c590d356fdf15e6a5e6a822
409026c2f41926d85d18ea2abf345453fa083e34
ce11f517d370b501433a62a56fbd4be1704000ad

Apakah halaman ini membantu?
0 / 5 - 0 peringkat