summaryrefslogtreecommitdiff
path: root/2025/07/31/svelte-lazyquery/index.html
diff options
context:
space:
mode:
authormuqiuhan <[email protected]>2025-09-09 06:17:00 +0000
committermuqiuhan <[email protected]>2025-09-09 06:17:00 +0000
commitd5de65fdb1802cdf498d65d93397f290813c377c (patch)
tree92fe61a76d20d1203203665d4b48e7f151f088bd /2025/07/31/svelte-lazyquery/index.html
parent48efa2dfde7c263f84ee5bb0872747034908d607 (diff)
downloadblog-d5de65fdb1802cdf498d65d93397f290813c377c.tar.gz
deploy: 9665097f0fa0dae9f92123fac54d26c0818758a5
Diffstat (limited to '2025/07/31/svelte-lazyquery/index.html')
-rw-r--r--2025/07/31/svelte-lazyquery/index.html35
1 files changed, 21 insertions, 14 deletions
diff --git a/2025/07/31/svelte-lazyquery/index.html b/2025/07/31/svelte-lazyquery/index.html
index a90029c0..436b509b 100644
--- a/2025/07/31/svelte-lazyquery/index.html
+++ b/2025/07/31/svelte-lazyquery/index.html
@@ -195,41 +195,47 @@
<p>在前端,一个常见的性能瓶颈是初始加载时请求了用户当前 viewpoint 之外的非必要数据。惰性加载(Lazy Loading)是一种关键策略,它将数据获取推迟到组件进入 viewpoint 时才执行。本文将阐述如何结合使用 Svelte 5、<code>@tanstack/svelte-query</code> 和 <code>runed.dev</code> 的 <code>useIntersectionObserver</code>,构建一个通用的、可复用的惰性加载组件。</p>
<p><strong>核心依赖与环境</strong></p>
<ul>
-<li><p><strong>Svelte 5:</strong> 本实现依赖于 Svelte 5 的符文(Runes)特性,它提供了更精细、更直观的状态管理能力。</p>
+<li>
+<p><strong>Svelte 5:</strong> 本实现依赖于 Svelte 5 的符文(Runes)特性,它提供了更精细、更直观的状态管理能力。</p>
</li>
-<li><p><strong>@tanstack&#x2F;svelte-query:</strong> TanStack Query 的 Svelte 适配版,用户获取数据。</p>
+<li>
+<p><strong>@tanstack/svelte-query:</strong> TanStack Query 的 Svelte 适配版,用户获取数据。</p>
</li>
-<li><p><strong>runed.dev:</strong> 一个提供多种 Svelte 5 实用工具的库,本文主要使用其 <code>useIntersectionObserver</code>。</p>
+<li>
+<p><strong>runed.dev:</strong> 一个提供多种 Svelte 5 实用工具的库,本文主要使用其 <code>useIntersectionObserver</code>。</p>
</li>
</ul>
-<h4 id="设计哲学:分离关注点"><a href="#设计哲学:分离关注点" class="headerlink" title="设计哲学:分离关注点"></a><strong>设计哲学:分离关注点</strong></h4><p>该方案的核心思想是将“何时加载”与“如何加载”这两个关注点进行解耦。</p>
+<h4 id="设计哲学:分离关注点"><a class="header-anchor" href="#设计哲学:分离关注点">¶</a><strong>设计哲学:分离关注点</strong></h4>
+<p>该方案的核心思想是将“何时加载”与“如何加载”这两个关注点进行解耦。</p>
<ol>
-<li><p><strong>何时加载 (When to Load):</strong> 组件的可见性决定了数据加载的时机。我们利用 <code>Intersection Observer API</code> 来精确、高效地监听一个元素是否进入 viewpoint。<code>runed.dev</code> 库为此提供了名为 <code>useIntersectionObserver</code> 的便捷封装。</p>
+<li>
+<p><strong>何时加载 (When to Load):</strong> 组件的可见性决定了数据加载的时机。我们利用 <code>Intersection Observer API</code> 来精确、高效地监听一个元素是否进入 viewpoint。<code>runed.dev</code> 库为此提供了名为 <code>useIntersectionObserver</code> 的便捷封装。</p>
</li>
-<li><p><strong>如何加载 (How to Load):</strong> 数据获取、缓存、同步和状态管理的复杂性由 <code>@tanstack/svelte-query</code> (TanStack Query) 处理。它提供了一套强大的工具集来管理异步数据。</p>
+<li>
+<p><strong>如何加载 (How to Load):</strong> 数据获取、缓存、同步和状态管理的复杂性由 <code>@tanstack/svelte-query</code> (TanStack Query) 处理。它提供了一套强大的工具集来管理异步数据。</p>
</li>
</ol>
<p>通过将这两者结合,可以创建一个名为 <code>LazyQuery</code> 的抽象组件。该组件内部处理可见性检测,并根据检测结果动态控制 TanStack Query 的执行,而将具体的查询逻辑(<code>queryFn</code>)和键(<code>queryKey</code>)完全交由使用者定义。</p>
-<h4 id="LazyQuery-组件的实现"><a href="#LazyQuery-组件的实现" class="headerlink" title="LazyQuery 组件的实现"></a><code>LazyQuery</code> 组件的实现</h4><blockquote>
+<h4 id="LazyQuery-组件的实现"><a class="header-anchor" href="#LazyQuery-组件的实现">¶</a><code>LazyQuery</code> 组件的实现</h4>
+<blockquote>
<p>目标:封装惰性加载逻辑,并向外暴露一个标准的 TanStack Query 接口。</p>
</blockquote>
<p>一、对组件的接口类型做如下定义:</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="keyword">type</span> &#123; <span class="title class_">CreateQueryOptions</span>, <span class="title class_">QueryKey</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@tanstack/svelte-query&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="keyword">type</span> &#123; <span class="title class_">Snippet</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;svelte&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Props</span>&lt;</span><br><span class="line"> <span class="title class_">TQueryFnData</span> = <span class="built_in">unknown</span>,</span><br><span class="line"> <span class="title class_">TError</span> = <span class="title class_">Error</span>,</span><br><span class="line"> <span class="title class_">TData</span> = <span class="title class_">TQueryFnData</span>,</span><br><span class="line"> <span class="title class_">TQueryKey</span> <span class="keyword">extends</span> <span class="title class_">QueryKey</span> = <span class="title class_">QueryKey</span></span><br><span class="line">&gt; = &#123;</span><br><span class="line"> <span class="comment">/// 一个标准的 TanStack Query 配置对象</span></span><br><span class="line"> <span class="attr">queryOptions</span>: <span class="title class_">CreateQueryOptions</span>&lt;<span class="title class_">TQueryFnData</span>, <span class="title class_">TError</span>, <span class="title class_">TData</span>, <span class="title class_">TQueryKey</span>&gt;;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// Svelte 5 的 `Snippet` 类型,它允许父组件向子组件传递一段可执行的 UI 模板。</span></span><br><span class="line"> <span class="comment">/// 这个 `Snippet` 会接收到 `createQuery` 返回的完整查询对象 `query`,</span></span><br><span class="line"> <span class="comment">/// 从而可以访问 `data`, `isLoading`, `error` 等所有状态。</span></span><br><span class="line"> <span class="attr">children</span>: <span class="title class_">Snippet</span>&lt;&#123;</span><br><span class="line"> <span class="attr">query</span>: <span class="title class_">ReturnType</span>&lt;<span class="keyword">typeof</span> createQuery&lt;<span class="title class_">TQueryFnData</span>, <span class="title class_">TError</span>, <span class="title class_">TData</span>, <span class="title class_">TQueryKey</span>&gt;&gt;;</span><br><span class="line"> &#125;&gt;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
-
<p>二、组件逻辑:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">import</span> &#123; createQuery, type <span class="title class_">CreateQueryOptions</span>, type <span class="title class_">QueryKey</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@tanstack/svelte-query&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">import</span> &#123; useIntersectionObserver &#125; <span class="keyword">from</span> <span class="string">&#x27;runed&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">import</span> type &#123; <span class="title class_">Snippet</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;svelte&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="comment">// [上述的类型定义]</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">let</span> &#123; queryOptions, children &#125;: <span class="title class_">Props</span> = $props();</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">let</span> <span class="attr">el</span>: <span class="title class_">Element</span>;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> &#123; isIntersecting &#125; = <span class="title function_">useIntersectionObserver</span>(</span></span><br><span class="line"><span class="language-javascript"> <span class="function">() =&gt;</span> el,</span></span><br><span class="line"><span class="language-javascript"> &#123;</span></span><br><span class="line"><span class="language-javascript"> <span class="attr">rootMargin</span>: <span class="string">&#x27;200px&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript"> &#125;</span></span><br><span class="line"><span class="language-javascript"> );</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> query = <span class="title function_">createQuery</span>(&#123;</span></span><br><span class="line"><span class="language-javascript"> ...queryOptions,</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">get</span> <span class="title function_">enabled</span>() &#123;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">return</span> ($state.<span class="title function_">is</span>(isIntersecting) &amp;&amp; (queryOptions.<span class="property">enabled</span> ?? <span class="literal">true</span>));</span></span><br><span class="line"><span class="language-javascript"> &#125;</span></span><br><span class="line"><span class="language-javascript"> &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">bind:this</span>=<span class="string">&#123;el&#125;</span>&gt;</span></span><br><span class="line"> &#123;#snippet children(&#123; query &#125;)&#125;</span><br><span class="line"> &#123;/snippet&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>
-
<ul>
<li>我们创建一个 <code>div</code> 元素作为哨兵(sentinel),并用 <code>bind:this=&#123;el&#125;</code> 将其 DOM 引用绑定到变量 <code>el</code>。</li>
-<li><code>useIntersectionObserver</code> 接收一个返回目标元素的函数 <code>() =&gt; el</code>。<ul>
+<li><code>useIntersectionObserver</code> 接收一个返回目标元素的函数 <code>() =&gt; el</code>。
+<ul>
<li>它返回一个响应式的状态对象,其中 <code>isIntersecting</code> 是一个布尔值的符文(rune),当 <code>div</code> 元素进入 viewpoint 时为 <code>true</code>,否则为 <code>false</code>。</li>
</ul>
</li>
-<li><code>rootMargin: &#39;200px&#39;</code> 是一个优化选项,它会在元素距离 viewpoint 还有 200px 时就触发加载,从而提升用户体验。</li>
+<li><code>rootMargin: '200px'</code> 是一个优化选项,它会在元素距离 viewpoint 还有 200px 时就触发加载,从而提升用户体验。</li>
</ul>
-<h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><p>使用 <code>LazyQuery</code> 组件非常直观。开发者只需关注数据获取的业务逻辑,而无需关心惰性加载的实现细节,假设有一个获取图表数据的场景:</p>
+<h4 id="使用"><a class="header-anchor" href="#使用">¶</a>使用</h4>
+<p>使用 <code>LazyQuery</code> 组件非常直观。开发者只需关注数据获取的业务逻辑,而无需关心惰性加载的实现细节,假设有一个获取图表数据的场景:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">import</span> &#123; <span class="title class_">QueryClientProvider</span>, <span class="title class_">QueryClient</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@tanstack/svelte-query&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">import</span> <span class="title class_">LazyQuery</span> <span class="keyword">from</span> <span class="string">&#x27;./LazyQuery.svelte&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">import</span> <span class="title class_">CopdGoldGradingChart</span> <span class="keyword">from</span> <span class="string">&#x27;./CopdGoldGradingChart.svelte&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> queryClient = <span class="keyword">new</span> <span class="title class_">QueryClient</span>();</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="comment">// Define the query configuration object, just like with a standard `createQuery`.</span></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> queryOptions = &#123;</span></span><br><span class="line"><span class="language-javascript"> <span class="attr">queryKey</span>: [<span class="string">&#x27;goldGradingData&#x27;</span>],</span></span><br><span class="line"><span class="language-javascript"> <span class="attr">queryFn</span>: <span class="title function_">async</span> () =&gt; &#123;</span></span><br><span class="line"><span class="language-javascript"> <span class="comment">// Simulate a network request</span></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> <span class="built_in">setTimeout</span>(resolve, <span class="number">1500</span>));</span></span><br><span class="line"><span class="language-javascript"> <span class="comment">// In a real application, this would be an API call</span></span></span><br><span class="line"><span class="language-javascript"> <span class="comment">// const response = await fetch(&#x27;/api/gold-grading&#x27;);</span></span></span><br><span class="line"><span class="language-javascript"> <span class="comment">// return await response.json();</span></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">return</span> &#123; <span class="attr">data</span>: &#123; <span class="attr">totalPatients</span>: <span class="number">1234</span>, <span class="attr">chartPoints</span>: [<span class="comment">/* ... */</span>] &#125; &#125;;</span></span><br><span class="line"><span class="language-javascript"> &#125;</span></span><br><span class="line"><span class="language-javascript"> &#125;;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">QueryClientProvider</span> <span class="attr">client</span>=<span class="string">&#123;queryClient&#125;</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&quot;height: 200vh;&quot;</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">p</span>&gt;</span>Scroll down to see the chart...<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line"> <span class="tag">&lt;<span class="name">LazyQuery</span> &#123;<span class="attr">queryOptions</span>&#125;&gt;</span></span><br><span class="line"> &#123;#snippet children(&#123; query &#125;)&#125;</span><br><span class="line"> &#123;#if query.isLoading&#125;</span><br><span class="line"> <span class="tag">&lt;<span class="name">p</span>&gt;</span>Loading chart data...<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"> &#123;:else if query.error&#125;</span><br><span class="line"> <span class="tag">&lt;<span class="name">p</span>&gt;</span>Error: &#123;query.error.message&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"> &#123;:else if query.data&#125;</span><br><span class="line"> <span class="tag">&lt;<span class="name">CopdGoldGradingChart</span></span></span><br><span class="line"><span class="tag"> <span class="attr">isLoading</span>=<span class="string">&#123;query.isFetching&#125;</span></span></span><br><span class="line"><span class="tag"> <span class="attr">error</span>=<span class="string">&#123;query.error&#125;</span></span></span><br><span class="line"><span class="tag"> <span class="attr">refetch</span>=<span class="string">&#123;()</span> =&gt;</span> query.refetch()&#125;</span><br><span class="line"> data=&#123;query.data.data&#125;</span><br><span class="line"> totalPatients=&#123;query.data.data.totalPatients&#125;</span><br><span class="line"> /&gt;</span><br><span class="line"> &#123;/if&#125;</span><br><span class="line"> &#123;/snippet&#125;</span><br><span class="line"> <span class="tag">&lt;/<span class="name">LazyQuery</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">QueryClientProvider</span>&gt;</span></span><br></pre></td></tr></table></figure>
-
<ul>
<li>页面初始加载时,<code>LazyQuery</code> 组件被渲染,但由于其 <code>div</code> 在 viewpoint 之外,<code>isIntersecting</code> 为 <code>false</code>。</li>
<li><code>createQuery</code> 被调用,但因为 <code>enabled</code> 条件为 <code>false</code>,查询处于禁用状态,不会发起任何网络请求。</li>
@@ -239,7 +245,8 @@
<li>这个变化被 <code>createQuery</code> 的 <code>enabled</code> 访问器捕获,查询被自动激活,<code>queryFn</code> 开始执行。</li>
<li>TanStack Query 负责管理后续的状态变化(<code>isFetching</code>, <code>data</code>, <code>error</code>),并驱动 <code>children</code> 片段内的 UI 自动更新。</li>
</ul>
-<h4 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h4><ul>
+<h4 id="参考"><a class="header-anchor" href="#参考">¶</a>参考</h4>
+<ul>
<li><a target="_blank" rel="noopener" href="https://runed.dev/docs/utilities/use-intersection-observer">https://runed.dev/docs/utilities/use-intersection-observer</a></li>
<li><a target="_blank" rel="noopener" href="https://tanstack.com/table/latest/docs/introduction">https://tanstack.com/table/latest/docs/introduction</a></li>
</ul>