如何触发/强制更新 Svelte 组件

Posted

技术标签:

【中文标题】如何触发/强制更新 Svelte 组件【英文标题】:How to trigger/force update a Svelte component 【发布时间】:2019-11-15 09:17:57 【问题描述】:

我正在努力解决 svelte 3 反应性问题...

    我想在单击按钮时强制刷新 UI。我正在使用一个自定义组件 AsyncFetcher,它接受 HTTP 发布数据,并为其插槽返回 data 对象(http 发布结果)。

    我想要一个禁用功能。因此,当单击“禁用”按钮时,会调用 http api,然后刷新数据视图。

<script>
    export let id

    function onDisable() 
        fetch('disable-api-url', id: id)
        // Then ??
        // What to do after the fetch call, to refresh the view
    
</script>

<AsyncFetcher postParam=id let:data>
    data.name

    <button on:click=??>Refresh</button>
    <button on:click=onDisable>Disable Item</button>
</AsyncFetcher>

我尝试做on:click=() =&gt; id=id 来欺骗它刷新无济于事。如果 id 是一个对象而不是字符串 id=...id 会起作用,但不幸的是,这里不是这样。

实现这一目标的正确方法是什么?

【问题讨论】:

【参考方案1】:

要获取数据,请使用await block:

<script>
    async function fetchData() 
        const res = await fetch('/api')
        const data = await res.json

        if (res.ok) 
            return data
         else 
            throw new Error(data)
        
    
</script>

<style>
    .error 
        color: red;
    
</style>

#await fetchData
    <p>Fetching...</p>
:then data
    <div>JSON.stringify(data)</div>
:catch error
    <div class="error">error.message</div>
/await

要刷新数据,您需要通过更新相关状态来触发重新渲染,因为这将重新运行等待块。您可以通过将 fetch 函数存储在一个状态中并在单击刷新按钮时重新分配它来触发重新渲染:

<script>
    async function fetchData() 
        const res = await fetch('/api')
        const data = await res.json

        if (res.ok) 
            return data
         else 
            throw new Error(data)
        
    

    let promise = fetchData()
</script>

<style>
    .error 
        color: red;
    
</style>

<button on:click="() => promise = fetchdata()">Refresh</button>

#await promise
    <p>Fetching...</p>
:then data
    <div>JSON.stringify(data)</div>
:catch error
    <div class="error">error.message</div>
/await

【讨论】:

【参考方案2】:

我这样做了(使组件消失并用计时器重新出现):

<script>
    import ForcedToRerender from './ForcedToRerender.svelte'
    let visible = true
    let rerender = () =>
    
        visible=false
        setTimeout(()=>visible = true, 100)
    
</script>
#if visible
    <ForcedToRerender />
/if
<button on:click=rerender>Rerender</button>

ForcedToRerender.svelte:

<script>
  import  onMount  from 'svelte'
    let num = 0
    let rnd = () => num = Math.random()
    onMount(rnd)
</script>
<div on:click=rnd>
    num
</div>

这行得通,你可以看到here。

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。【参考方案3】:

这是强制重新渲染不依赖于外部数据的组件的一个有点老套的解决方案:

<script>
    // Await immediately resolved promise to react to value change.
    const forceUpdate = async (_) => ;
    let doRerender = 0;
</script>
#await forceUpdate(doRerender) then _
    <ForcedToRerender on:click=() => doRerender++ />
/await

我试图找到一个更“原生”的解决方案,但这就是我最终得到的。 回复:https://svelte.dev/repl/2dc5c7ca82bc450f8f7dd25d2be577b1?version=3.43.0

【讨论】:

【参考方案4】:

在我的情况下,svelte 没有刷新输出, 因为我通过在 100% cpu 上运行基准测试来阻止 javascript 事件循环

在这种情况下,诀窍是使用 await sleep(10) 手动解锁事件循环

<script>
  function sleep(millisec = 0) 
    return new Promise((resolve, reject) => 
      setTimeout(_ => resolve(), millisec);
    );
  ;
  let result = '';
  async function runBenchmark() 
    for (let step = 0; step < 10; step++) 

      // this needs 100% cpu, so no time for svelte render
      cpuburn(); result += `$step: 1.234 sec\n`;

      // unblock the JS event loop, so svelte can render
      await sleep(10);
    
  
</script>

<pre>result</pre>

here 是一个 repl(但目前它会在 repl 运行时触发一个错误)

用同步函数调用解决这个问题可能是不可能的 (类似于$$svelte.forceTickSync()

【讨论】:

【参考方案5】:

虽然Rich Harris 给出了一个完全有用的答案,但这里有一个解决方案,用于强制 Svelte 更新组件以反映其数据的外部更改 (also posted here)。

ma​​in.js;来自在线示例的香草,没有特殊变化:

import App from './App.svelte';

var app = new App(
    target: document.body
);

export default app;

index.html;注意window.neek = ...

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Svelte app</title>
    <script>
        window.neek =  nick: true, camp:  bell: "Neek" , counter: 0 ;
    </script>
    <script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>

App.svelte;注意$: notneek = window.neekwindow.neek.update = ...

<script>
    let name = 'world';
    $: notneek = window.neek;

    function handleClick() 
        notneek.counter += 1;
    

    window.neek.update = function () 
        notneek = notneek;
    
</script>

<h1>Hello  notneek.camp.bell !</h1>

<button on:click=handleClick>
    Clicked notneek.counter notneek.counter === 1 ? 'time' : 'times'
</button>

由于update 函数在App.svelte 的范围内,它能够在通过window.neek.update() 调用时强制重新渲染。此设置使用window.neek.counter 作为按钮使用的内部数据(通过notneek.counter),并允许在组件外部更新深层属性(例如neek.camp.bell = "ish")并在调用neek.update() 时反映。

在控制台中,输入window.neek.camp.bell = "Bill" 并注意Hello Neek! 尚未更新。现在,在控制台中输入window.neek.update(),UI 将更新为Hello Bill!

最重要的是,您可以在 update 函数中进行任意细化,以便只同步您想要同步的部分。

【讨论】:

【参考方案6】:

使用组件来管理获取是非常规的。通常,您会在 onMount 或事件处理程序中获取数据:

<script>
  import  onMount  from 'svelte';

  let initialData;
  let otherData;

  onMount(async () => 
    const res = await fetch('some-url');
    initialData = await res.json();
  );

  async function update() 
    const res = await fetch('some-other-url');
    otherData = await res.json();
  
</script>

#if initialData
  <p>the data is initialData.something</p>
/if

<button on:click=update>update</button>

【讨论】:

以上是关于如何触发/强制更新 Svelte 组件的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Svelte 应用程序用作另一个 Svelte 应用程序中的组件?

如何从primefaces中的javascript触发组件刷新?

如何在 Typescript 文件中导入 Svelte 组件?

Svelte.js - 如何使用新道具重新渲染子组件?

Typescript + Svelte - 如何添加类型以传播组件道具

如何手动将 svelte 组件编译为 sapper/svelte 生成的最终 javascript 和 css?