blazor强制完全渲染,而不是差分渲染

时间:2020-10-29 14:23:08

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

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

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

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

绑定第一批图像后 after first batch of images is bound

第二批图像绑定后 after second batch of images is bound

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

剃刀组件 (将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的工作方式有关。我按照您的建议调整了数组的大小,但我只是创建一个填充有空引用的适当长度的数组。我意识到这肯定是错的,但是我不确定用什么填充它们-我尝试用object[] imageRefs替换ElementReference[] imageRefs并用imageRefs填充ElementReference[i].Id并将其传递给JS函数,但并没有改变页面的整体行为。为了简化代码,我恢复了基准。

我在一个不同的示例下粘贴,在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与原始帖子中的相同。

1 个答案:

答案 0 :(得分:2)

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

然后,您的触发器砌体功能将必须遍历参考列表。

<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);
        }
    }
}