不管是vue
,react
,angular
还是svelte
,开发时都会把一个页面拆分成粒度更小的组件,方便复用和维护。
所以组件间通信就变得异常重要,下面我们比较下vue
和svelte
的组件的通信方式。
通信方式 | vue | svelte |
---|---|---|
props | 支持 | 支持 |
eventbus(发布-订阅) | 支持 | 支持 |
父向子孙传递 | provide/inject | setContext/getContext |
中心化状态管理 | vuex | store |
关联关系组件通信 | 支持 | 不支持 |
下面我们就一个个介绍下svelte
组件间的通信方式
props
在我们new
一个svelte组件的时候,会把props
传入
const app = new App({
target: document.body,
props: {
// assuming App.svelte contains something like
// `export let answer`:
answer: 42
}
});
当我们在组件标签上定义属性值时,也会被编译成props
传入
下面我们在看一下,svelte
怎么接收props
传入的值
虽然语法上用export标记
接收props
,但是编译之后的结果就是一个普通的变量
eventBus(发布-订阅)
先看一个组件标签上定义的自定义事件,svelte在编译子组件时,会调用$on方法把标签上定义的自定义事件添加到子组件的事件管理中心eventBus
组件上为什么会有这个$on方法的呢,svelte编译组件的时候会继承SvelteComponent
类
class Inner extends SvelteComponent {
constructor(options) {
super(options);
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
在SvelteComponent
类中声明了$on方法
class SvelteComponent {
...
$on(type, callback) {
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1)
callbacks.splice(index, 1);
};
}
...
}
在我们调用$on
方法的时候,也就是会在该组件的callbacks
上订阅传入的自定义事件。
然后我们怎么触发这个自定义事件呢?
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
dispatch('message', {
text: 'Hello!'
});
首先调用createEventDispatcher
生成一个可以触发当前组件的自定义事件的dispatch
方法。
function createEventDispatcher() {
//获取当前组件
const component = get_current_component();
//返回的dispatch方法
return (type, detail) => {
//获取存储自定义事件的callbacks
const callbacks = component.$$.callbacks[type];
if (callbacks) {
// TODO are there situations where events could be dispatched
// in a server (non-DOM) environment?
const event = custom_event(type, detail);
//遍历调用自定义事件
callbacks.slice().forEach(fn => {
//绑定component示例,传入构造的自定义事件event,自定义事件的参数挂载在event.detail上
fn.call(component, event);
});
}
};
}
和vue eventbus
不同的是我们createEventDispatcher
返回的dispatch
触发的是当前组件的订阅的自定义事件,所以无法做全局事件发布-订阅总线。
setContext/getContext
先看下setContext
方法
function setContext(key, context) {
get_current_component().$$.context.set(key, context);
}
向当前组件context(Map)
中set
要传递给子孙组件的值
再看下子孙组件怎么获取值
context: new Map(parent_component ? parent_component.$$.context : [])
因为父子组件关系在组件模板定义时就确定,并且在创建子组件时获取父组件context赋值给当前组件context,从而实现父组件向子孙组件的传值。
中心化状态管理store
svelte
的store
通过writable
方法储存一个值
import { writable } from 'svelte/store';
export const count = writable(0);
先看一下writable
方法
function writable(value, start = noop) {
//取消订阅后的stop函数,subscribers数组为空触发
let stop;
//subscribe订阅的事件数组,数据更新时触发
const subscribers = [];
//设置新值
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) { // store is ready
const run_queue = !subscriber_queue.length;
for (let i = 0; i < subscribers.length; i += 1) {
const s = subscribers[i];
s[1]();
//subscriber_queue队列中push subscribe订阅的事件和新值
subscriber_queue.push(s, value);
}
//run_queue控制set更新数据时触发订阅的事件中如果继续set更新数据重复触发阻止死循环
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
//把update函数返回结果作为set新值
function update(fn) {
set(fn(value));
}
//订阅数据变化的事件,并返回取消订阅方法
function subscribe(run, invalidate = noop) {
const subscriber = [run, invalidate];
subscribers.push(subscriber);
if (subscribers.length === 1) {
//初次订阅的事件在执行前调用传入的start函数,并返回取消订阅后的stop函数
stop = start(set) || noop;
}
//执行订阅事件
run(value);
//返回取消订阅函数
return () => {
const index = subscribers.indexOf(subscriber);
if (index !== -1) {
subscribers.splice(index, 1);
}
if (subscribers.length === 0) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
writable
方法返回一个管理数据的对象,含有set
,update
,subscribe
方法
- set:更新数据
- update:调用update函数返回结果作为set新值
- subscribe:订阅事件,数据更新时触发 这些方法可以让我们很好的管理数据和监听数据变化的状态,但是我们怎么实现这个响应式数据呢
使用的方式很简单$count
,主要还是在于svelte
强大的编译能力
let $count,
$$unsubscribe_count = noop,
$$subscribe_count = () => ($$unsubscribe_count(), $$unsubscribe_count = subscribe(count, $$value => $$invalidate(1, $count = $$value)), count);
$$self.$$.on_destroy.push(() => $$unsubscribe_count());
$$subscribe_count();
对该数据订阅一个更新dom节点
的方法,这样数据每次更新时,会触发subscribe
,然后调用invalidate
函数触发节点更新。(invalidate函数触发节点更新的介绍可以参考svelte组件渲染过程)
同时把返回的unsubscribe
函数push
进on_destroy
数组,组件销毁时取消订阅。
关联关系组件通信
在vue中我们可以通过vm.$parent
,vm.$children
查找到任意我们需要通信的组件进行交互,但是svelte
组件间的创建并没有建立父子级关系,而且更新节点的invalidate方法是对外封闭的,所以无法像vue那样自由的找到任意的组件的交互。
总结
svelte组件间通信方式
- props:通过向子组件传参的形式通信
- eventbus:事件管理中心,事件中心添加自定义的事件(订阅),在需要的时机手动触发(发布)
- store:中心化状态管理机,svelte通过的编译的方法让store管理的数据修改时可以动态的触发节点的更新
- setContext/getContext:组件创建阶段,父组件一层层往子组件添加context
谢谢大家的观看,有问题忘指正。