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

Posted

技术标签:

【中文标题】Svelte.js - 如何使用新道具重新渲染子组件?【英文标题】:Svelte.js - How to rerender child components with new props? 【发布时间】:2020-01-20 12:07:54 【问题描述】:

我正在使用 Svelte.js 创建一个多步骤表单,但在渲染每个表单页面时遇到了问题。

这是一个简单的演示,向您展示我的意思:

// App.svelte

<script>
    import FormPage from "./FormPage.svelte";
    let formNode;

    let pageSelected = 0;

    let formPages = [
        
            name: "email",
            label: "Email"
        ,
        
            name: "password",
            label: "Password"
        
    ];

    const handleIncPage = () => 
        if(pageSelected + 1 < formPages.length)
        pageSelected = pageSelected + 1;        
    

    const handleDecPage = () => 
        if(pageSelected -1 > -1)
        pageSelected = pageSelected - 1;        
    
</script>

<form bind:this=formNode>
    <FormPage pageData=formPages[pageSelected] />
</form>

<button on:click=handleDecPage>Back</button>
<button on:click=handleIncPage>Next</button>

<p>
    Page Selected: pageSelected
</p>

这是FormPage 组件:

// FormPage.svelte

<script>
    export let pageData;

    const name, label = pageData;
</script>

<div id=`form-page-$name`>
    <label>Label: label</label>
    <input type="text" name=name id=`input-$name` />
</div>

<pre>JSON.stringify(pageData, null, 2)</pre>

当我运行应用程序和 inc/dec pageSelected 时,pageData 属性成功更改 - 如 pre 元素所示。但是,labelinput 元素与它们在第一页上的完全相同。包裹div 元素的idinput 元素的id 也没有改变。

当我输入input 并更改页面时,文本保持不变。

我的目标是:当pageSelected 更改时重新渲染FormPage 组件,并让inputlabel 根据这些新道具更改它们的值。也应该是,当我更改页面时,已经输入 input 的文本应该更新并为空(因为没有为输入赋予初始值)。

在 React.js 中完成多步骤表单后,我将使用唯一的 key 属性来确保每次 pageSelected 状态更改时我的 FormPage 都会重新呈现。但我不确定如何在 Svelte.js 中做类似的事情。

有什么建议吗?

更新:

阅读 *** 上的 question 后,我了解了如何更改 inputlabel 元素以及 id 属性。这是我更新的FormPage 组件:

// FormPage.svelte

<script>
    export let pageData;

    $: name = pageData.name;
    $: label = pageData.label;

</script>

<div id=`form-page-$name`>
    <label>Label: label</label>
    <input type="text" name=name id=`input-$name`  />
</div>

<pre>JSON.stringify(pageData, null, 2)</pre>

但是,input 里面的文字仍然没有改变。

有没有办法同时更新输入的值?对于我的用例来说,每次道具更改时创建一个全新的元素是理想的,但似乎 Svelte 只更新了在同一个底层 DOM 节点上发生更改的少数属性。

可以告诉 Svelte 在这种情况下重新创建元素吗?

更新 2

所以我想出了一种方法来改变输入的值。这是我更新的FormPage 组件:

// FormPage.svelte

<script>
    export let pageData;

        import onMount, afterUpdate, onDestroy from "svelte";

        let inputNode;

        $: name = pageData.name;
        $: label = pageData.label;
        $: value = pageData.initialValue || "";

        onMount(() => 
            console.log("Mounted");
        );

        afterUpdate(() => 
            console.log("Updated");
            inputNode.value = value
        );

        onDestroy(() => 
            console.log("Destroyed");
        );

</script>

<div id=`form-page-$name`>
    <label>Label: label</label>
    <input type="text" name=name id=`input-$name` bind:this=inputNode  />
</div>

<pre>JSON.stringify(pageData, null, 2)</pre>

这解决了更新输入值的直接问题。

我添加了onMountafterUpdateonDestroy 函数来查看组件如何随时间变化。

首先调用onDestroy 函数,然后调用onMount。这些都只触发一次。但是,afterUpdate 会在每次道具更改时触发。这证实了我对组件没有被重新创建的怀疑。

【问题讨论】:

【参考方案1】:

有一种更简单的方法可以做到这一点。 pageData 在您的FormPage 中更新,问题是当您使用const name, label = pageData; 时,您基本上是将pageData 中的namelabel 参数的值分配给两个常量namelabel 分别。这两个变量不再绑定到父组件。为了解决这个问题,请直接在FormPage 中使用pageData,如下所示。

FormPage.svelte

<script>
    export let pageData;
</script>

<div id=`form-page-$pageData.name`>
    <label>Label: pageData.label</label>
    <input type="text" name=pageData.name bind:value=pageData.value id=`input-$pageData.name` />
</div>

注意:如果您不希望编译器在没有传递pageData 的情况下给您一个undefined 错误,那么您可以将pageData 初始化为一个空对象,就像export let pageData = ; 一样。

至于input 问题,最简单的做法是将value 添加到formPages,然后将bindvalue 添加到pageData.value,如上图FormPage 所示。

新的formPages 数据

let formPages = [
    name: "email",
    label: "Email",
    value: ""
, 
    name: "password",
    label: "Password",
    value: ""
];

这是REPL 上的一个工作示例。

【讨论】:

这是否解决了您的问题?【参考方案2】:

#key../key 解决了这个问题。

https://svelte.dev/docs#key

在我的情况下,Sizing 组件用于更新大小(字体大小或行高等),例如您的 FormPage.svelte

<script>
import Sizing from '..../Sizing.svelte'

const pages = 
  'font-size': Sizing,
  'line-height': Sizing, 
  ...

const props = 
  'font-size':  min: 8, max: 20, step: 1,
  'line-height': min: 1, max: 3, step: 0.1

let currentProp // 'font-size', 'line-height' etc ..
</script>

<div>
  #key currentProp
     <svelte:component this="pages[currentProp]" ...props/>
  /key
</div>

每当currentProp 更改时,Sizing 组件也会更新。

...props 映射到导出的变量

// Sizing.svelte
<script>
  export let min
  export let max
  export let step
</script>

【讨论】:

谢谢你,它完美地解决了我的问题!我只需要这样做: #key changedProp /key

以上是关于Svelte.js - 如何使用新道具重新渲染子组件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在重新渲染期间从道具更新派生状态

VUE2js:如何在其道具更改后重新渲染组件?

道具更新时重新渲染组件(使用 Redux)

在 React 中按下按钮时重新渲染道具数据

将新的商店状态放入道具后,反应不会重新渲染

当父组件在 Next.js 应用程序中更新其道具时重新渲染 React 子组件