【Svelte】动态加载组件并传递参数的正确姿势,及两种方式比较
下面通过比较两种动态加载组件的方式,解释经典的 Svelte 响应式编程方法,让你可以掌握现代前端框架的精髓。
In Svelte 5, the most Svelte-idiomatic and recommended way to dynamically append components is to use Svelte's reactivity system, typically with an array and an {#each} block, rather than direct DOM manipulation like document.querySelector and imperative appendChild for Svelte components.
Here are the two main ways to achieve this, with the first being the strongly preferred Svelte approach:
1. The Svelte-Idiomatic Way (Recommended)
This approach uses Svelte's reactive {#each} block to render components based on an array of data. When you want to add a new CompA, you simply add the data (j) to an array, and Svelte automatically renders a new CompA instance.
src/App.svelte (or your main component)
<script>import CompA from './compa.svelte';// This array will hold the data for each CompA instance// Svelte will reactively render a CompA for each item in this arraylet messages = [];async function addMessage() {// get data from urlconst r = await fetch("https://jsonplaceholder.typicode.com/todos/1", { // Using a public API for examplemethod: "GET", // Changed to GET for the example API// body: "xxx" // Not needed for GET});const j = await r.json();// Add the new data to the messages array// Svelte requires reassignment for reactivity, so we spread the existing array// and add the new item.messages = [...messages, j];console.log('Messages:', messages);}
</script><h1>Svelte 5 Dynamic Components</h1><div id="parent-div" style="border: 1px solid blue; padding: 10px; min-height: 50px;"><h2>Messages from CompA:</h2>{#each messages as msg (msg.id || JSON.stringify(msg))}<CompA data={msg} />{/each}
</div><button onclick={addMessage}>Add CompA with Message</button><style>div {margin-top: 20px;}button {padding: 10px 20px;background-color: #007bff;color: white;border: none;border-radius: 5px;cursor: pointer;}button:hover {background-color: #0056b3;}
</style>
src/compa.svelte
<script>// Declare the prop 'data' that CompA expects to receivelet { data } = $props();// You can also use reactive declarations if needed// let processedData = $state(data.title.toUpperCase());
</script><div style="border: 1px solid green; margin: 5px; padding: 5px;"><p><strong>ID:</strong> {data.id}</p><p><strong>Title:</strong> {data.title}</p><p><strong>Completed:</strong> {data.completed ? 'Yes' : 'No'}</p>
</div>
Explanation:
let messages = [];: We declare a reactive arraymessagesin the parent component.messages = [...messages, j];: WhenaddMessageis called, we fetch the datajand then add it to themessagesarray. Crucially, we create a new array using the spread syntax (...) and reassign it. Svelte detects this reassignment and triggers a re-render.{#each messages as msg (msg.id || JSON.stringify(msg))}: The{#each}block iterates over themessagesarray. For each item (msg), it renders a<CompA>component.(msg.id || JSON.stringify(msg))is thekeyfor the{#each}block. It's important for Svelte to efficiently update lists. If yourjobject has a uniqueid, use that. Otherwise,JSON.stringify(msg)can be a fallback, though a proper unique ID is always better.
<CompA data={msg} />: For each item, we pass themsgobject as a prop nameddatatoCompA.let { data } = $props();: InCompA.svelte, we declare a prop nameddatausing Svelte 5's$props()macro to receive the data.
2. Advanced / Imperative Way (Less Recommended for this use case)
If you have a very specific reason to imperatively mount a Svelte component into an arbitrary DOM node that is not directly part of the parent component's template (e.g., mounting into a third-party library's container or a document.body overlay), you can use the component constructor directly. However, for adding components to a div within your own component's template, the {#each} approach is much cleaner and safer.
This method requires manual lifecycle management (destroying the component).
src/App.svelte
<script>import CompA from './compa.svelte';import { onDestroy } from 'svelte'; // For cleanuplet componentInstances = []; // To keep track of created component instances for destructionasync function addMessage() {// get data from urlconst r = await fetch("https://jsonplaceholder.typicode.com/todos/2", {method: "GET",});const j = await r.json();// Get the parent elementconst footer = document.querySelector('#parent-div');if (!footer) {console.error('Parent div #parent-div not found!');return;}// Create a new instance of CompA and mount it to the footerconst compAInstance = new CompA({target: footer, // The DOM element to mount the component intoprops: {data: j // Pass j as a prop named 'data'}});// Store the instance so we can destroy it latercomponentInstances = [...componentInstances, compAInstance];console.log('Imperatively added CompA with data:', j);}// Lifecycle hook to destroy all dynamically created components when this parent component is destroyedonDestroy(() => {componentInstances.forEach(instance => {console.log('Destroying imperative instance:', instance);instance.$destroy();});});
</script><h1>Svelte 5 Dynamic Components (Imperative)</h1><div id="parent-div" style="border: 1px solid red; padding: 10px; min-height: 50px;"><h2>Imperatively Added CompA Instances:</h2>
</div><button onclick={addMessage}>Add CompA Imperatively</button><style>div {margin-top: 20px;}button {padding: 10px 20px;background-color: #dc3545;color: white;border: none;border-radius: 5px;cursor: pointer;}button:hover {background-color: #c82333;}
</style>
src/compa.svelte (Same as above)
<script>let { data } = $props();
</script><div style="border: 1px solid orange; margin: 5px; padding: 5px;"><p><strong>ID:</strong> {data.id}</p><p><strong>Title:</strong> {data.title}</p><p><strong>Completed:</strong> {data.completed ? 'Yes' : 'No'}</p>
</div>
Explanation:
import { onDestroy } from 'svelte';: We needonDestroyto clean up the imperatively created components.let componentInstances = [];: We maintain an array of component instances so we can call$destroy()on them later.const footer = document.querySelector('#parent-div');: We get the target DOM element.const compAInstance = new CompA({ target: footer, props: { data: j } });: This is the core. You instantiate the Svelte component directly using its constructor.target: The DOM element where the component will be mounted.props: An object containing the initial props to pass to the component.
onDestroy(() => { ... });: It's crucial to call$destroy()on each created component instance when the parent component unmounts to prevent memory leaks.
Which method to choose?
For almost all scenarios where you want to add components dynamically within your Svelte application, use the first method with {#each}. It's more declarative, leverages Svelte's reactivity system, automatically handles lifecycle management (creation, updates, destruction), and is generally easier to maintain.
The imperative new CompA(...) method should be reserved for niche cases where you need to integrate Svelte components into non-Svelte environments or have very specific DOM manipulation requirements that cannot be met declaratively.
