Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(TreeView): improve cache for TreeViewNode #4677

Merged
merged 13 commits into from
Nov 17, 2024
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Components/Table/Table.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ protected override void OnInitialized()
base.OnInitialized();

// 初始化节点缓存
TreeNodeCache ??= new(Equals);
TreeNodeCache ??= new(this);
OnInitLocalization();

// 设置 OnSort 回调方法
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ protected override void OnInitialized()
base.OnInitialized();

// 初始化节点缓存
TreeNodeStateCache ??= new(Equals);
TreeNodeStateCache ??= new(this);
NotSetOnTreeExpandErrorMessage = Localizer[nameof(NotSetOnTreeExpandErrorMessage)];
}

Expand Down
58 changes: 28 additions & 30 deletions src/BootstrapBlazor/Misc/ExpandableNodeCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,32 @@ namespace BootstrapBlazor.Components;
/// </summary>
/// <typeparam name="TNode"></typeparam>
/// <typeparam name="TItem"></typeparam>
/// <remarks>
/// 构造函数
/// </remarks>
public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer) where TNode : IExpandableNode<TItem>
public class ExpandableNodeCache<TNode, TItem> where TNode : IExpandableNode<TItem>
{
/// <summary>
/// 所有已展开行集合 作为缓存使用
/// </summary>
protected List<TItem> ExpandedNodeCache { get; } = new(50);
protected HashSet<TItem> ExpandedNodeCache { get; }

/// <summary>
/// 所有已收缩行集合 作为缓存使用
/// </summary>
protected List<TItem> CollapsedNodeCache { get; } = new(50);
protected HashSet<TItem> CollapsedNodeCache { get; }

/// <summary>
/// 对象比较器
/// </summary>
protected IEqualityComparer<TItem> EqualityComparer { get; } = new ModelComparer<TItem>(comparer);
protected IEqualityComparer<TItem> EqualityComparer { get; }

/// <remarks>
/// 构造函数
/// </remarks>
public ExpandableNodeCache(IModelEqualityComparer<TItem> comparer)
{
EqualityComparer = new HashSetComparer<TItem>(comparer);
ExpandedNodeCache = new(50, EqualityComparer);
CollapsedNodeCache = new(50, EqualityComparer);
}
/// <summary>
/// 节点展开收缩状态切换方法
/// </summary>
Expand All @@ -42,13 +48,10 @@ public virtual async Task ToggleNodeAsync(TNode node, Func<TNode, Task<IEnumerab
if (node.IsExpand)
{
// 展开节点缓存增加此节点
if (!ExpandedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
{
ExpandedNodeCache.Add(node.Value);
}
ExpandedNodeCache.Add(node.Value);

// 收缩节点缓存移除此节点
CollapsedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
CollapsedNodeCache.Remove(node.Value);

// 无子项时通过回调方法延时加载
if (!node.Items.Any())
Expand All @@ -64,13 +67,10 @@ public virtual async Task ToggleNodeAsync(TNode node, Func<TNode, Task<IEnumerab
else
{
// 展开节点缓存移除此节点
ExpandedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
ExpandedNodeCache.Remove(node.Value);

// 收缩节点缓存添加此节点
if (!CollapsedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
{
CollapsedNodeCache.Add(node.Value);
}
CollapsedNodeCache.Add(node.Value);
}
}

Expand All @@ -85,20 +85,18 @@ public async Task CheckExpandAsync(TNode node, Func<TNode, Task<IEnumerable<IExp
if (node.IsExpand)
{
// 已收缩
if (CollapsedNodeCache.Contains(node.Value, EqualityComparer))
if (CollapsedNodeCache.Contains(node.Value))
{
node.IsExpand = false;
}
else if (!ExpandedNodeCache.Contains(node.Value, EqualityComparer))
{
// 状态为 展开
ExpandedNodeCache.Add(node.Value);
}

// 状态为 展开
ExpandedNodeCache.Add(node.Value);
}
else
{
var needRemove = true;
if (ExpandedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
if (ExpandedNodeCache.Contains(node.Value))
{
// 原来是展开状态,
if (node.HasChildren)
Expand All @@ -119,7 +117,7 @@ public async Task CheckExpandAsync(TNode node, Func<TNode, Task<IEnumerable<IExp
}
if (needRemove)
{
ExpandedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
ExpandedNodeCache.Remove(node.Value);
}
}
}
Expand All @@ -132,7 +130,7 @@ public async Task CheckExpandAsync(TNode node, Func<TNode, Task<IEnumerable<IExp
/// <param name="ret">查询结果 查无资料时为 null</param>
/// <returns>是否存在 <paramref name="target"/></returns>
/// <remarks>采广度优先搜寻</remarks>
public bool TryFind(IEnumerable<TNode> items, TItem target, [MaybeNullWhen(false)] out TNode ret)
public bool TryFind(List<TNode> items, TItem target, [MaybeNullWhen(false)] out TNode ret)
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
{
ret = Find(items, target);
return ret != null;
Expand All @@ -145,7 +143,7 @@ public bool TryFind(IEnumerable<TNode> items, TItem target, [MaybeNullWhen(false
/// <param name="target"></param>
/// <returns>查询结果 查无资料时为 null</returns>
/// <remarks>采广度优先搜寻</remarks>
private TNode? Find(IEnumerable<TNode> items, TItem target) => Find(items, target, out _);
private TNode? Find(List<TNode> items, TItem target) => Find(items, target, out _);

/// <summary>
/// 在全部树状结构 <paramref name="source"/> 中寻找指定 <paramref name="target"/>
Expand All @@ -155,14 +153,14 @@ public bool TryFind(IEnumerable<TNode> items, TItem target, [MaybeNullWhen(false
/// <param name="degree">树状阶层,起始为0</param>
/// <returns>查询结果 查无资料时为 null</returns>
/// <remarks>采广度优先搜寻</remarks>
public TNode? Find(IEnumerable<TNode> source, TItem target, out int degree)
public TNode? Find(List<TNode> source, TItem target, out int degree)
{
degree = -1;
var ret = source.FirstOrDefault(item => EqualityComparer.Equals(item.Value, target));
if (ret == null)
{
var children = source.SelectMany(e => e.Items.OfType<TNode>());
if (children.Any())
var children = source.SelectMany(e => e.Items.OfType<TNode>()).ToList();
if (children.Count != 0)
{
ret = Find(children, target, out degree);
}
Expand Down
32 changes: 32 additions & 0 deletions src/BootstrapBlazor/Misc/HashSetComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// 模型比较器
/// </summary>
/// <typeparam name="TItem"></typeparam>
public class HashSetComparer<TItem>(IModelEqualityComparer<TItem> comparer) : IEqualityComparer<TItem>
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Equals 方法
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public bool Equals(TItem? x, TItem? y) => comparer.Equals(x, y);

/// <summary>
/// GetHashCode 方法
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int GetHashCode([DisallowNull] TItem obj)
{
var keyValue = Utility.GetKeyValue<TItem, object>(obj, comparer.CustomKeyAttribute);
return keyValue?.GetHashCode() ?? obj.GetHashCode();
}
}
13 changes: 2 additions & 11 deletions src/BootstrapBlazor/Misc/ModelComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,8 @@ namespace BootstrapBlazor.Components;
/// 模型比较器
/// </summary>
/// <typeparam name="TItem"></typeparam>
public class ModelComparer<TItem> : IEqualityComparer<TItem>
public class ModelComparer<TItem>(Func<TItem, TItem, bool> comparer) : IEqualityComparer<TItem>
{
private readonly Func<TItem, TItem, bool> _comparer;
/// <summary>
/// 构造函数
/// </summary>
public ModelComparer(Func<TItem, TItem, bool> comparer)
{
_comparer = comparer;
}

/// <summary>
/// Equals 方法
/// </summary>
Expand All @@ -32,7 +23,7 @@ public bool Equals(TItem? x, TItem? y)
if (x != null && y != null)
{
// 均不为空时走 comparer 方法判断
ret = _comparer(x, y);
ret = comparer(x, y);
}
else
{
Expand Down
Loading