Svelte 迷途求索(二) —— 组件传参和状态管理
一、简单体验
Svelte 的开发体验和 Vue 3 很类似
在 Svelte 组件中也是将 JS 写在 <script> 标签中,将 CSS 写在 <style> 中
稍微不同的点在于,DOM 模板不需要写在 <template> 中,而是可以写在任何地方
<script>
import Nested from './Nested.svelte';
const title = 'Hello World';
</script>
<style>
p { color: purple; }
</style>
<h1>{title}</h1>
<p>Let's Enjoy Sevelte</p>
<Nested/>
Svelte 中的变量自带响应式特性,数据变更时会即时渲染到页面上
同时 Svelte 还提供了 $: 用来实现一段响应式逻辑,就像 Vue 中的 Computed 和 Watch
<script>
let count = 0;
const handleClick = () => count += 1;
$: doubled = count * 2;
$: if (count > 9) {
console.log('count is dangerously high!');
count = 9;
};
</script>
<button on:click={handleClick}>Click</button>
<p>{count} doubled is {doubled}</p>
总之 Svelte 是一个很好上手的框架,更多的特性或语法可以通过官方文档了解,《Svelte 迷途求索》将着重介绍 Svelte 实战中可能遇到的问题
二、创建 Props:父对子传参
在开发的时候,如果子组件中的某个数据需要通过父组件传入,就需要在子组件中声明 props
// 子组件
<script lang="ts">
export let text: string = undefined;
</script>
<p>hello {text}</p>
没错,在 Svelte 中创建 props 就是这么简单粗暴,只需要使用 let 创建一个变量,然后通过 export 导出即可
可以给这个 prop 添加一个默认值,不管是上面的 undefined,还是一个具体的值,那么这个 prop 就会被标记为可选属性
如果没有设置默认值,这个 prop 就是一个必填属性。使用该组件时如果没有提供该属性,则会打印警告
三、通过 prop 传入函数:子对父传参
上面已经介绍过,组件的 prop 是通过 let 创建的
而使用 const, class, function 创建的 prop 都是只读属性,即使通过 export 导出,也不会接收外部参数
然而通过 prop 传入一个函数的情况还是挺常见的,比如子组件向父组件传参
好在虽然不能使用 function,但还是可以使用箭头函数作为 prop
下面这个例子中,子组件通过回调函数 onChange 实现了向父组件传参
// 子组件
<script lang="ts">
// onChange 需要父组件提供
export let onChange = (v: number) => {};
let count: number = 0;
const addCount = () => {
count += 1;
typeof onChange === 'function' && onChange(count);
};
</script>
<button on:click={addCount}>Click</button>
<p>count: {count}</p>
// 父组件
<script lang="ts">
import Child from './child.svelte';
const onChange = (v: number) => {
console.log('onChange', v);
};
</script>
<Child {onChange} />
<!--
* 等价于:
* <Child onChange={onChange} />
-->
四、状态管理:复杂情况的组件传参
通过 props 可以很轻松的实现父组件对子组件的传参
如果是兄弟组件之间传参,可以运用状态提升的思想来实现
但如果是更复杂的情况呢?比如祖孙组件,甚至是几个没有直接关系的组件。这时候就可以使用 Svelte 自带的状态管理
// stores/index.js
import { writable } from 'svelte/store';
export const titleStore = writable('Hello World');
export const userInfo = writable({
name: 'Wise.Wrong',
blog: 'https://www.cnblogs.com/wisewrong/',
});
svelte/store 模块提供了三个函数 writable、readable、derived,分别用来创建可写状态、只读状态、派生状态
其中最常用的是可写状态 writable, 上面的 stores/index.js 中就使用 writable 创建了两个 store 对象
但 store 对象是不能直接用于页面渲染的,比如下面的用法就会出错
这是因为 writable 创建的是包含 set、update、subscribe 方法的 store 对象
其中 subscribe 会在 store 更新的时候触发,类似 Vue 中的 watch
基于这个特性,我们可以这样读取 store 的值:
它确实生效了,但这样也太不优雅了,其实只需要在 store 前面加一个 $ 就行了
如果需要在组件中修改 store 的值,可以用到 set 和 update 方法
import { titleStore } from './stores';
const onChange = (v: string) => {
// set 接收的参数会作为 store 的新值
titleStore.set(v);
};
const onUpdate = (str: string) => {
// update 可以接收一个函数,其返回值会作为 store 的新值
titleStore.update((v: string) => v + str);
}
但如果是在表单中,有时候连 update 或 set 都不需要,因为 store 可以配合 bind: 使用
<script lang="ts">
// parent.svelte
import { userInfo } from '../../stores';
import Child from './child.svelte';
</script>
<div class="title">This is <strong>{$userInfo?.name}</strong></div>
<Child />
<style>
.title {
margin-bottom: 12px;
}
</style>
<script lang="ts">
// child.svelte
import { userInfo } from '../../stores';
</script>
<section>
<div>
<span>姓名: </span>
<input type="text" bind:value={$userInfo.name}>
</div>
<div>
<span>博客: </span>
<input type="text" bind:value={$userInfo.blog}>
</div>
</section>