如何 trigger/force 更新 Svelte 组件

How to trigger/force update a Svelte component

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

  1. 我想在单击按钮时强制刷新 UI。我正在使用接受 HTTP post 数据和 returns data 对象(http post 结果)作为其插槽的自定义组件 AsyncFetcher

  2. 我想要禁用功能。因此,当单击 "Disable" 按钮时,将调用 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={() => id=id} 来欺骗它刷新但无济于事。如果 id 是一个对象而不是字符串 id={...id} 会起作用,不幸的是,这里不是这种情况。

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

使用组件来管理抓取是非常不寻常的。通常,您会在 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>

同时 gives a completely serviceable answer, here's a solution for forcing Svelte to update a component to reflect an external change of its data (also posted here).

main.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 函数中根据需要进行细化,这样只有您想要同步的片段才会被同步。

就我而言,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()

这里有一个有点老套的解决方案,用于强制重新呈现不依赖于外部数据的组件:

<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

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

<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

要获取数据,请使用 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}

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

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