【问题标题】:blazor force full render instead of differential renderblazor 强制完全渲染而不是差分渲染
【发布时间】:2021-02-12 00:16:44
【问题描述】:

我正在尝试使用 this masonry js package 显示图像,但我遇到了困难。

我有一个图片上传表单。用户单击按钮将图像绑定到Model.Images,然后它们会显示在页面上。用户可以在提交表单之前绑定多批图片。每次用户将另一批图像绑定到Model.Images 时,都会保留上一批图像——也就是说,在绑定下一批图像之前,我不会截断Model.Images

我看到的问题是:每次添加 Model.Images 时都需要重新触发 masonry js 脚本,因为 js 仅适用于最近渲染的视图。但是,似乎每次我强制StateHasChanged(); 来自最近添加的批次的图像都会与以前的图像重叠,所以我看到了这个:

第一批图片绑定后

第二批图片绑定后

我认为这与差分渲染有关。如果我将图像移动到辅助对象,截断Model.Images,强制StateHasChanged(),然后重新填充Model.Images,图像将正确并排显示。我很可能只是错误地处理了这个问题。有人可以建议我如何在没有我的 hacky 实现的情况下实现我想要的吗?我在下面粘贴了我的代码的简化版本 - 这是产生上图所示不良结果的实现。

剃须刀组件TriggerMasonry() 事件在 Model.Images 被附加到之后被调用。)

@for (int i = 0; i < Model.Images.Count; j++)
{
    var iCopy = i; //see https://stackoverflow.com/a/56426146/323447
    var uri = $"data:{Model.Images[iCopy].ImageMimeType};base64,{Convert.ToBase64String(Model.Images[iCopy].ImageDataLarge.ToArray())}";

    <div class="grid-item" @ref=MasonryElement>
        <div class="card m-2 shadow-sm" style="position:relative;">
            <img src="@uri" class="card-img-top" alt="..." loading="lazy">
        </div>
    </div>
}


@code {
    ElementReference MasonryElement;

    private async void TriggerMasonry()
    {
        if (Model.Images.Where(x => x.ImageData != null).Any())
        {
            StateHasChanged();
            await JSRuntime.InvokeVoidAsync("initMasonryDelay", MasonryElement);
            StateHasChanged();
        }
    }
}

js

function initMasonryDelay() {

    setTimeout(function () {

        // init Masonry
        var $grid = $('.grid').masonry({
            itemSelector: '.grid-item',
            percentPosition: true,
            columnWidth: '.grid-sizer'
        });

        // layout Masonry after each image loads
        $grid.imagesLoaded().progress(function () {
            $grid.masonry();
        });

    }, 150); //milliseconds
}

css

* {
    box-sizing: border-box;
}

.grid:after {
    content: '';
    display: block;
    clear: both;
}

.grid-sizer,
.grid-item {
    width: 33.333%;
}

@media (max-width: 575px) {
    .grid-sizer,
    .grid-item {
        width: 100%;
    }
}

@media (min-width: 576px) and (max-width: 767px) {
    .grid-sizer,
    .grid-item {
        width: 50%;
    }
}

/* To change the amount of columns on larger devices, uncomment the code below */
@media (min-width: 768px) and (max-width: 991px) {
    .grid-sizer,
    .grid-item {
        width: 33.333%;
    }
}

@media (min-width: 992px) and (max-width: 1199px) {
    .grid-sizer,
    .grid-item {
        width: 25%;
    }
}

@media (min-width: 1200px) {
    .grid-sizer,
    .grid-item {
        width: 20%;
    }
}


.grid-item {
    float: left;
}

    .grid-item img {
        display: block;
        max-width: 100%;
    }

----用户@Brian_Parker的另一个例子---- 我试图应用你的建议,但我仍然没有任何运气。我显然不能理解 JS 互操作性如何与 blazor 组件一起工作。我认为我的问题与不了解 @ref 的工作原理有关。我按照您的建议调整了数组的大小,但我只是创建了一个用空引用填充的适当长度的数组。我意识到这一定是错误的,但我不确定用什么填充它们 - 我尝试用 ElementReference[] imageRefs 替换 object[] imageRefs 并用 imageRefs 填充 ElementReference[i].Id 并将其传递给 JS 函数,但它没有t 改变页面的整体行为。为了让我的代码更容易消化,我恢复到基线。

我在下面粘贴了一个不同的示例,在 UI 上下文中可能更容易理解。如果您能帮助我理解我在这里做错了什么,我将不胜感激:

我有另一个组件,我正在尝试将 Masonry JS 应用于动态生成的图像列表。基本概述:加载时,从服务器拉出 10 张图像并进行渲染。当用户单击“加载更多”按钮时,会从服务器中提取另外 10 个图像并进行渲染。我看到了第二轮图像覆盖第一轮的相同问题。

我的剃须刀组件:

<!-- Masonry -->
<div class="grid">
    <div class="grid-sizer"></div>
    @for (int i = 0; i < pagedThings.Data.Count; i++)
    {
        var iCopy = i; //see https://stackoverflow.com/a/56426146/323447
        <div @key="@pagedThings.Data[iCopy]" class="grid-item" @ref=imageRefs[iCopy]>
            <div class="card">
                @{ 
                    var mimeType = pagedThings.Data[iCopy].ImageThings.FirstOrDefault().ImageMimeTypeLarge;
                    var imgData = pagedThings.Data[iCopy].ImageThings.FirstOrDefault().ImageDataLarge;
                }
                <img src="@String.Format("data:" + mimeType + ";base64,{0}", Convert.ToBase64String(imgData))" loading="lazy">
            </div>
        </div>
    }
</div>

<!-- Load more button -->
<button class="btn" type="button" @onclick="GetMoreThings">
    <span>Load more</span>
</button>

@code {
    PagedResponse<List<Thing>> pagedThings { get; set; }
    private int currentPage = 1;
    private int currentPageSize = 10;
    object[] imageRefs;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            //load Things
            await LoadThings();
        }
        await base.OnAfterRenderAsync(firstRender);
    }

    private async Task LoadThings()
    {
        pagedThings = await ThingService.GetByPageAsync($"api/Things/GetThingsByPage?pageNumber={currentPage}&pageSize={currentPageSize}");
        imageRefs = new object[pagedThings.Data.Count()];
        await JSRuntime.InvokeVoidAsync("masonryAppendDelay", imageRefs[imageRefs.Length - 1]);
        StateHasChanged();
    }

    private async Task GetMoreThings()
    {
        currentPage++;

        var newData = await ThingService.GetByPageAsync($"api/Things/GetThingsByPage?pageNumber={currentPage}&pageSize={currentPageSize}");
        foreach (var item in newData.Data)
        {
            pagedThings.Data.Add(item);
        }
        imageRefs = new object[pagedThings.Data.Count()];
        await JSRuntime.InvokeVoidAsync("masonryAppendDelay", imageRefs[imageRefs.Length - 1]);
        StateHasChanged();
    }
}

我的 JS:

function masonryAppendDelay(element) {

    setTimeout(function () {

        // init Masonry
        var $grid = $('.grid').masonry({
            itemSelector: element,
            percentPosition: true,
            columnWidth: '.grid-sizer'
        });

        // layout Masonry after each image loads
        $grid.imagesLoaded().progress(function () {
            $grid.masonry();
            //$grid.masonry('layout');
        });

    }, 200); //milliseconds
}

我的 css 和原帖中的一样。

【问题讨论】:

  • 您正在调整 imageRefs = new object[pagedThings.Data.Count()];但没有用任何数据填充它。我也不了解 imageRefs 的应用方式。
  • 嘿@sion_corn 您是否对使用 masonry js 的完整 blazor 实现有一个概述? (我的意思是一个演示网站)我很乐意使用它。

标签: javascript c# asp.net-core blazor


【解决方案1】:

您正在循环捕获引用。这只会分配最终循环引用。一旦知道有多少次迭代,就必须调整数组的大小以捕获引用。 refs = new object[someValue]

然后您的 Trigger masonry 函数将必须遍历参考列表。

<div @key="@Model.Images[iCopy]" class="grid-item" @ref=refs[iCopy]>
        <div class="card m-2 shadow-sm" style="position:relative;">
            <img src="@uri" class="card-img-top" alt="..." loading="lazy">
        </div>
</div>


@code { 
    object[] refs;
}

在渲染后为每个引用调用您的 java 脚本。

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        foreach (var obj in refs)
        {
            await JSRuntime.InvokeVoidAsync("masonryAppendDelay", obj);
        }
    }
}

【讨论】:

  • 感谢您的建议,但这对我的情况没有影响。
  • 问题似乎来自 JS - 它似乎只有在调用一次时才有效:await JSRuntime.InvokeVoidAsync("initMasonryDelay"); - 如果我在 second之后第一次调用它> 图像被绑定,然后两个图像都整齐地排列在砖石布局中。如果我每次绑定图像时都调用 JS,那么我会看到重叠问题。
  • @sion_corn 我想我已经发现了这个问题。
  • @brian_parker 感谢您的回复。我仍然不太明白 - 我用另一个示例更新了 OP,显示了我尝试实施您的建议。不幸的是,我仍然无法产生我所追求的行为。
  • @Depechie 你能把你的 blazor 代码放在 BlazorRepl.com 或类似的地方吗?我会帮忙的。
猜你喜欢
  • 2021-04-13
  • 1970-01-01
  • 1970-01-01
  • 2019-11-12
  • 1970-01-01
  • 2021-04-28
  • 1970-01-01
  • 1970-01-01
  • 2021-10-01
相关资源
最近更新 更多