Svelte组件通信:父子组件间的数据传递
2022-02-072.3k 阅读
理解 Svelte 组件通信基础
在 Svelte 应用开发中,组件是构建用户界面的基本单元。组件之间常常需要交换数据和信息,这就涉及到组件通信。父子组件间的数据传递是其中最常见的一种通信方式。在 Svelte 里,理解组件通信机制的核心在于认识到每个组件都是相对独立的,但又能通过特定方式与其他组件交互。
Svelte 组件本质上是一个包含 HTML、CSS 和 JavaScript 的自包含单元。每个组件都有自己的状态(state)和行为(behavior)。当组件被实例化时,它可以接收来自父组件的数据,同时也可以向父组件发送数据,这种交互构成了 Svelte 应用动态性和灵活性的基础。
从父组件传递数据到子组件
- 属性(Props)的使用
- 在 Svelte 中,父组件向子组件传递数据主要通过属性(props)来实现。属性就像是组件的“输入参数”。例如,我们创建一个简单的父组件
App.svelte
和一个子组件Child.svelte
。 - 首先创建
Child.svelte
,代码如下:
- 在 Svelte 中,父组件向子组件传递数据主要通过属性(props)来实现。属性就像是组件的“输入参数”。例如,我们创建一个简单的父组件
<script>
export let message;
</script>
<p>{message}</p>
- 在上述代码中,通过
export let message;
声明了一个名为message
的属性,这个属性将从父组件接收数据。在模板部分,直接将message
渲染在<p>
标签内。 - 然后在
App.svelte
中使用Child.svelte
组件并传递数据,代码如下:
<script>
import Child from './Child.svelte';
let parentMessage = 'Hello from parent!';
</script>
<Child message={parentMessage} />
- 在
App.svelte
中,先导入了Child.svelte
组件,然后定义了一个变量parentMessage
。在使用<Child>
组件时,将parentMessage
作为message
属性传递给了Child
组件。这样,Child
组件就能接收到父组件传递的message
数据并进行展示。
- 传递复杂数据类型
- 除了传递简单的字符串类型数据,也可以传递对象、数组等复杂数据类型。例如,修改
Child.svelte
来接收一个对象:
- 除了传递简单的字符串类型数据,也可以传递对象、数组等复杂数据类型。例如,修改
<script>
export let user;
</script>
<p>{user.name} is {user.age} years old.</p>
- 在
App.svelte
中传递一个用户对象:
<script>
import Child from './Child.svelte';
let user = {
name: 'John',
age: 30
};
</script>
<Child user={user} />
- 这里,父组件
App.svelte
将user
对象作为user
属性传递给Child.svelte
。子组件可以通过解构user
对象来获取其中的属性并进行展示。
- 属性的默认值
- 在子组件中,可以为属性设置默认值。当父组件没有传递该属性时,子组件将使用默认值。例如,修改
Child.svelte
为:
- 在子组件中,可以为属性设置默认值。当父组件没有传递该属性时,子组件将使用默认值。例如,修改
<script>
export let message = 'Default message';
</script>
<p>{message}</p>
- 此时,如果在
App.svelte
中使用<Child>
组件时没有传递message
属性:
<script>
import Child from './Child.svelte';
</script>
<Child />
- 子组件
Child.svelte
将显示“Default message”。而如果传递了message
属性,如<Child message="New message" />
,则会显示“New message”。
响应式数据传递
- 父组件数据变化影响子组件
- Svelte 的响应式系统使得当父组件传递给子组件的属性值发生变化时,子组件会自动更新。例如,在
App.svelte
中添加一个按钮来改变传递给子组件的数据:
- Svelte 的响应式系统使得当父组件传递给子组件的属性值发生变化时,子组件会自动更新。例如,在
<script>
import Child from './Child.svelte';
let count = 0;
const increment = () => {
count++;
};
</script>
<Child value={count} />
<button on:click={increment}>Increment</button>
- 在
Child.svelte
中接收value
属性并显示:
<script>
export let value;
</script>
<p>The value is: {value}</p>
- 当在
App.svelte
中点击按钮时,count
的值会增加,由于count
作为value
属性传递给了Child
组件,Child
组件会自动更新并显示新的值。
- 单向数据流原则
- Svelte 遵循单向数据流原则,即数据从父组件流向子组件。子组件不能直接修改父组件传递过来的属性值。例如,在
Child.svelte
中尝试直接修改message
属性:
- Svelte 遵循单向数据流原则,即数据从父组件流向子组件。子组件不能直接修改父组件传递过来的属性值。例如,在
<script>
export let message;
const wrongModify = () => {
message = 'New value';
};
</script>
<button on:click={wrongModify}>Wrong Modify</button>
<p>{message}</p>
- 在
App.svelte
中:
<script>
import Child from './Child.svelte';
let parentMessage = 'Initial message';
</script>
<Child message={parentMessage} />
- 当点击
Child.svelte
中的“Wrong Modify”按钮时,虽然message
在子组件内部看似改变了,但这并不会影响到父组件中的parentMessage
。如果需要改变父组件的数据,需要通过其他机制,比如事件机制,这将在后面介绍。
子组件向父组件传递数据
- 使用自定义事件
- 子组件向父组件传递数据主要通过自定义事件实现。首先,在
Child.svelte
中创建并触发一个自定义事件:
- 子组件向父组件传递数据主要通过自定义事件实现。首先,在
<script>
import { createEventDispatcher } from'svelte';
const dispatch = createEventDispatcher();
const sendDataToParent = () => {
let dataToSend = 'Data from child';
dispatch('childEvent', dataToSend);
};
</script>
<button on:click={sendDataToParent}>Send Data to Parent</button>
- 在上述代码中,通过
createEventDispatcher
创建了一个事件分发器dispatch
。当点击按钮时,触发名为childEvent
的自定义事件,并传递数据'Data from child'
。 - 在
App.svelte
中监听这个自定义事件:
<script>
import Child from './Child.svelte';
const handleChildEvent = (event) => {
console.log('Received data from child:', event.detail);
};
</script>
<Child on:childEvent={handleChildEvent} />
- 在
App.svelte
中,使用on:childEvent
来监听Child
组件触发的childEvent
事件。当事件触发时,handleChildEvent
函数会被调用,event.detail
中包含了子组件传递过来的数据。
- 传递复杂数据结构
- 同样可以传递复杂数据结构,比如对象或数组。修改
Child.svelte
来传递一个对象:
- 同样可以传递复杂数据结构,比如对象或数组。修改
<script>
import { createEventDispatcher } from'svelte';
const dispatch = createEventDispatcher();
const sendDataToParent = () => {
let dataToSend = {
key: 'value',
array: [1, 2, 3]
};
dispatch('childEvent', dataToSend);
};
</script>
<button on:click={sendDataToParent}>Send Data to Parent</button>
- 在
App.svelte
中接收并处理这个对象:
<script>
import Child from './Child.svelte';
const handleChildEvent = (event) => {
console.log('Received object from child:', event.detail);
};
</script>
<Child on:childEvent={handleChildEvent} />
- 这样,父组件就能接收到子组件传递的复杂数据对象并进行相应处理。
- 双向绑定的实现原理(基于父子组件通信)
- 在 Svelte 中,双向绑定看起来是一种方便的语法糖,让数据在父子组件间双向流动。例如,有一个
InputComponent.svelte
作为子组件:
- 在 Svelte 中,双向绑定看起来是一种方便的语法糖,让数据在父子组件间双向流动。例如,有一个
<script>
export let value;
const handleChange = (event) => {
let newVal = event.target.value;
dispatch('input', newVal);
};
</script>
<input type="text" bind:value on:input={handleChange}>
- 在
App.svelte
中使用这个组件并进行双向绑定:
<script>
import InputComponent from './InputComponent.svelte';
let textValue = '';
const handleInput = (event) => {
textValue = event.detail;
};
</script>
<InputComponent bind:value={textValue} on:input={handleInput} />
<p>The value is: {textValue}</p>
- 这里
bind:value
语法实际上是一种简写。当输入框的值改变时,InputComponent.svelte
触发input
事件并传递新的值。App.svelte
监听这个事件并更新textValue
。同时,textValue
的初始值作为value
属性传递给InputComponent.svelte
,从而实现了双向绑定。
多层父子组件间的数据传递
- 层层传递属性
- 在复杂的组件结构中,可能存在多层父子组件关系。例如,有
Grandparent.svelte
、Parent.svelte
和Child.svelte
三个组件。Grandparent.svelte
作为最顶层组件,Parent.svelte
是Grandparent.svelte
的子组件,Child.svelte
是Parent.svelte
的子组件。 - 在
Child.svelte
中接收属性并显示:
- 在复杂的组件结构中,可能存在多层父子组件关系。例如,有
<script>
export let message;
</script>
<p>{message}</p>
- 在
Parent.svelte
中导入Child.svelte
并传递属性:
<script>
import Child from './Child.svelte';
export let message;
</script>
<Child message={message} />
- 在
Grandparent.svelte
中导入Parent.svelte
并传递属性:
<script>
import Parent from './Parent.svelte';
let grandMessage = 'Message from grandparent';
</script>
<Parent message={grandMessage} />
- 通过这种层层传递属性的方式,
Grandparent.svelte
的数据可以传递到Child.svelte
。
- 使用上下文(Context)
- 对于多层嵌套组件间传递数据,使用上下文可以简化传递过程。首先,在
Grandparent.svelte
中设置上下文:
- 对于多层嵌套组件间传递数据,使用上下文可以简化传递过程。首先,在
<script>
import { setContext } from'svelte';
let sharedData = 'Shared data in context';
setContext('sharedDataContext', sharedData);
</script>
{#if false}
<!-- This block is just to hold child components without rendering -->
<Parent />
{/if}
- 在
Child.svelte
中获取上下文数据:
<script>
import { getContext } from'svelte';
let sharedData = getContext('sharedDataContext');
</script>
<p>{sharedData}</p>
- 这里,
Grandparent.svelte
使用setContext
设置了一个名为sharedDataContext
的上下文,并将sharedData
放入其中。Child.svelte
使用getContext
获取这个上下文数据,无需通过中间的Parent.svelte
层层传递属性。但需要注意,上下文数据是共享的,可能会导致数据管理的复杂性,应谨慎使用。
处理组件通信中的性能问题
- 不必要的重新渲染
- 在父子组件通信中,可能会出现不必要的重新渲染问题。例如,如果父组件传递给子组件的属性值频繁变化,但子组件实际上并不依赖这些变化来更新其显示,就会导致不必要的重新渲染。
- 假设
Child.svelte
有一个isVisible
属性,用于控制组件的显示隐藏,而另一个unrelatedProp
属性子组件并不依赖:
<script>
export let isVisible;
export let unrelatedProp;
</script>
{#if isVisible}
<p>Child component is visible</p>
{/if}
- 在
App.svelte
中频繁改变unrelatedProp
:
<script>
import Child from './Child.svelte';
let isVisible = true;
let unrelatedProp = 0;
const incrementUnrelated = () => {
unrelatedProp++;
};
</script>
<Child isVisible={isVisible} unrelatedProp={unrelatedProp} />
<button on:click={incrementUnrelated}>Increment Unrelated</button>
- 每次点击按钮,
unrelatedProp
变化会导致Child
组件重新渲染,即使isVisible
没有变化,子组件显示的内容也不依赖unrelatedProp
。
- 优化方法
- 一种优化方法是使用
$: derived
来创建衍生状态,只有当真正依赖的状态变化时才触发更新。例如,修改Child.svelte
为:
- 一种优化方法是使用
<script>
import { derived } from'svelte/store';
export let isVisible;
export let unrelatedProp;
let visibleStore = derived({ isVisible }, ($store) => {
return $store.isVisible;
});
</script>
{#if $visibleStore}
<p>Child component is visible</p>
{/if}
- 这样,只有当
isVisible
变化时,Child
组件内部依赖的$visibleStore
才会变化,从而触发重新渲染,避免了因unrelatedProp
变化导致的不必要重新渲染。 - 另一种方法是使用
shouldUpdate
生命周期函数。在Child.svelte
中添加:
<script>
export let isVisible;
export let unrelatedProp;
const shouldUpdate = (newProps) => {
return newProps.isVisible!== isVisible;
};
</script>
{#if isVisible}
<p>Child component is visible</p>
{/if}
- 这里
shouldUpdate
函数返回true
或false
,只有当返回true
时,组件才会重新渲染。通过比较新老isVisible
属性值,只有当isVisible
变化时才会重新渲染,忽略了unrelatedProp
的变化。
与 React 和 Vue 父子组件通信的对比
- 与 React 的对比
- 在 React 中,父组件向子组件传递数据也是通过属性(props),这一点与 Svelte 相似。例如,在 React 中创建一个父组件
App.js
和子组件Child.js
: Child.js
代码如下:
- 在 React 中,父组件向子组件传递数据也是通过属性(props),这一点与 Svelte 相似。例如,在 React 中创建一个父组件
import React from'react';
const Child = ({ message }) => {
return <p>{message}</p>;
};
export default Child;
App.js
代码如下:
import React, { useState } from'react';
import Child from './Child';
const App = () => {
const [parentMessage, setParentMessage] = useState('Hello from parent');
return <Child message={parentMessage} />;
};
export default App;
- 然而,React 的单向数据流更为严格,子组件通过回调函数将数据传递给父组件时,需要父组件提前将回调函数作为属性传递给子组件。例如,在
Child.js
中添加一个按钮来更新父组件数据:
import React from'react';
const Child = ({ message, updateParent }) => {
const handleClick = () => {
updateParent('New message from child');
};
return (
<div>
<p>{message}</p>
<button onClick={handleClick}>Update Parent</button>
</div>
);
};
export default Child;
- 在
App.js
中传递回调函数:
import React, { useState } from'react';
import Child from './Child';
const App = () => {
const [parentMessage, setParentMessage] = useState('Hello from parent');
const handleChildUpdate = (newMessage) => {
setParentMessage(newMessage);
};
return <Child message={parentMessage} updateParent={handleChildUpdate} />;
};
export default App;
- 而在 Svelte 中,子组件可以通过自定义事件更直接地向父组件传递数据,不需要父组件提前传递回调函数。
- 与 Vue 的对比
- 在 Vue 中,父组件向子组件传递数据同样通过属性。例如,创建一个父组件
App.vue
和子组件Child.vue
。Child.vue
代码如下:
- 在 Vue 中,父组件向子组件传递数据同样通过属性。例如,创建一个父组件
<template>
<p>{{ message }}</p>
</template>
<script>
export default {
props: ['message']
};
</script>
App.vue
代码如下:
<template>
<Child :message="parentMessage" />
</template>
<script>
import Child from './Child.vue';
export default {
components: {
Child
},
data() {
return {
parentMessage: 'Hello from parent'
};
}
};
</script>
- 子组件向父组件传递数据时,Vue 使用
$emit
方法触发自定义事件。例如,在Child.vue
中添加一个按钮来传递数据:
<template>
<div>
<p>{{ message }}</p>
<button @click="sendDataToParent">Send Data to Parent</button>
</div>
</template>
<script>
export default {
props: ['message'],
methods: {
sendDataToParent() {
this.$emit('childEvent', 'Data from child');
}
}
};
</script>
- 在
App.vue
中监听事件:
<template>
<Child :message="parentMessage" @childEvent="handleChildEvent" />
</template>
<script>
import Child from './Child.vue';
export default {
components: {
Child
},
data() {
return {
parentMessage: 'Hello from parent'
};
},
methods: {
handleChildEvent(data) {
console.log('Received data from child:', data);
}
}
};
</script>
- 虽然 Vue 和 Svelte 在子组件向父组件传递数据上都使用自定义事件,但 Svelte 的语法相对更简洁,事件分发器的使用更为直观,且 Svelte 的响应式系统在数据变化跟踪和组件更新上有其独特的优势。
通过深入理解 Svelte 父子组件间的数据传递机制,开发者可以构建出更加灵活、高效且易于维护的前端应用。无论是简单的属性传递,还是复杂的多层组件通信以及性能优化,掌握这些知识都是 Svelte 开发的关键。