营销业务是前端要有专业判断的 最后一个阵地
javascript.plainenglish.io/why-svelte-…
简单来说,就是 “在 MobX 的响应式机制里,一旦监听到可观察数据发生变动,主动通知/调用 Svelte 的更新函数,从而让 Svelte 继续它的响应式更新。”
核心做法通常包括以下几个步骤(以常见模式为例):
-
创建一个能够代表 MobX 数据状态的 Svelte Store
- 在 Svelte 生态中,一般会使用
writable或readable(来自svelte/store)来创建一个 Store。 - 而这个 Store 的值会和 MobX 的某个可观察对象/值挂钩。
- 在 Svelte 生态中,一般会使用
-
利用 MobX 的
autorun或reaction- 在 Svelte Store 初始化时,就用
autorun(或reaction)去“监听” MobX 里的可观察数据。 - 每当 MobX 数据更新,就调用 Svelte Store 的
set()或内部的变动通知方法,把最新值同步给 Svelte Store。
- 在 Svelte Store 初始化时,就用
-
Svelte Store 值变动后,触发 Svelte 的响应式更新
- 当 Svelte Store 的值变化后,Svelte 本身就会触发订阅该 Store 的组件或变量发生更新;
- 这会让组件模板或 Svelte 的
$:声明式代码重新执行、重新渲染 DOM。
-
可选的:在销毁/卸载组件时,销毁 MobX 的
reaction- 如果你是在一个组件里用到这个混合的 Store,那么往往需要在组件销毁或 Store 不再使用时,手动取消 MobX reaction/autorun 的订阅,以免产生内存泄漏。
底层可能的示例代码
给一个简化的示例,你可以看到这种“桥接”思路是如何写的。以下是一个伪示例,不一定跟 svelte-mobx 源码一模一样,但思路相近:
js
复制
// 1. 假设我们有一个 mobxStore,对象里有可观察值
import { observable } from 'mobx';
import { readable } from 'svelte/store';
import { autorun } from 'mobx';
const mobxStore = observable({
count: 0,
increment() {
this.count++;
}
});
// 2. 封装一个函数,把 mobxStore 转成 Svelte 的 Store
function mobxToSvelte(mobxObj, selectValueFn) {
// selectValueFn 用于从 mobxObj 中读出要注入到 Svelte 的值
// 比如: (store) => store.count
return readable(selectValueFn(mobxObj), (set) => {
// autorun: 当 mobxObj 里被观察的属性发生变更时,会执行回调
const dispose = autorun(() => {
const newValue = selectValueFn(mobxObj);
set(newValue);
});
// 返回清理函数
return () => dispose();
});
}
// 3. 在 Svelte 组件里用这个 store
import { mobxToSvelte } from './bridge';
export const countStore = mobxToSvelte(mobxStore, (s) => s.count);
// Svelte 中使用
/*
<script>
import { countStore } from './somewhere';
$: countValue = $countStore; // 通过自动订阅 store 获取最新值
</script>
<template>
<p>Count: {countValue}</p>
<button on:click={() => mobxStore.increment()}>
+1
</button>
</template>
*/
- 每当
mobxStore.count更新,MobX 的autorun都会把最新的mobxStore.count传给set(); set()会让countStore(Svelte Store)产生一个“值变了”的事件;- Svelte 响应式系统监听到这个 Store 值变动,就重新渲染与它相关的组件模板或代码。
为什么这样能“保留”两套响应式?
- Svelte 一端:只认自己的 Store 或
$:声明式语句。当 Store 的值变了,Svelte 会精确地更新 DOM。 - MobX 一端:只需要跟踪那些“可观察数据”(
observable或computed)有没有变。 - 桥接:利用 MobX 的
autorun/reaction,在数据变动时主动去调用 Svelte 的 Store 更新函数;反之,如果需要从 Svelte 发动作(比如点击按钮)去修改 MobX 数据,也可以直接调用 MobX store 方法(如store.increment())。 - 如此,就等于在 MobX 与 Svelte 之间加了一个很薄的“转换层”。
svelte-mobx 库的做法
svelte-mobx(或类似的 mobx-svelte-store)等库,实际上就是把上面手写的“桥接逻辑”包装成一个更简洁的 API,让你能够在 Svelte 中直接使用类似:
js
复制
import { observer } from 'svelte-mobx';
import { myMobxStore } from './myMobxStore';
// 直接在模板里用 $: auto-run reaction
// 或通过 observer 让组件感知 mobxStore 的变化
它们通常内部也是依赖 autorun / reaction 或 computed 来观察 MobX 对象的变化,然后再触发 Svelte 的更新。
要点:Svelte 自己本身没有提供像 Vue 那样的“自动对任意对象做 Proxy 化”功能,也不会自动跟踪所有外部对象的变化。
一旦你把状态交给了 MobX,就需要额外的桥接代码,让“MobX → Svelte”这一段通讯顺畅。
svelte-mobx做的事情,核心就是这套监听 + 更新的桥接机制。
这段代码做了什么?
ts
复制
import * as mobx from "mobx";
import { onDestroy } from "svelte";
type AutorunFn = (view: () => void) => void;
function connect(): { autorun: AutorunFn } {
let disposer: mobx.IReactionDisposer;
onDestroy(() => {
disposer && disposer();
});
return {
autorun: (view: () => void) => {
disposer && disposer();
disposer = mobx.autorun(view);
},
};
}
export { connect };
- 引入了 MobX 并从
svelte导入了onDestroy。 - 定义了
connect()函数,返回一个拥有autorun(view)方法的对象。 - 当你在组件里调用
connect()时,它会在内部记录一次mobx.autorun(...)返回的disposer(即取消订阅的函数)。 onDestroy(() => disposer && disposer()):当 Svelte 组件销毁时,就自动调用mobx.autorun的disposer,从而停止跟踪,避免内存泄漏。- 每次调用
autorun(view)都会先释放之前的 Reaction,再创建新的 Reaction。
可以理解为:它只是一段让你“在 Svelte 组件里安全地使用 autorun”的封装。至于你要怎么拿到 MobX 的可观察数据、怎么让它和组件中的变量交互,还需要自己写额外的逻辑。
在 Svelte 中的使用示例
假设你有一个 mobx store:
ts
复制
// store.ts
import { observable } from 'mobx';
export const myStore = observable({
count: 0,
increment() {
this.count++;
}
});
然后在某个 Svelte 组件中使用你贴出来的 connect() :
html
复制
<!-- MyComponent.svelte -->
<script lang="ts">
import { connect } from './connect'; // 你那段代码所在文件
import { myStore } from './store';
// 调用 connect()
const { autorun } = connect();
// 使用 autorun 监听 mobx store 的变化
autorun(() => {
console.log('myStore.count 变化了:', myStore.count);
});
// 组件中的函数,触发 mobx store 的更新
function handleClick() {
myStore.increment();
}
</script>
<div>
<p>当前 count:{myStore.count}</p>
<button on:click={handleClick}> +1 </button>
</div>
注意点
-
你会发现,上面并没有让 Svelte 的模板自动追踪
myStore.count的变化。- 因为 Svelte 自身并不知道
myStore是个 MobX 对象。 - Svelte 只在编译期追踪本地的声明变量和Svelte Store (
writable,readable等)。 - 你在模板里直接写
{myStore.count}不会自动更新 UI(Svelte 不会监听到MobX的变化)。你可以在autorun()里手动把count赋给一个本地的let变量,然后让Svelte用这个本地变量来刷新UI。
- 因为 Svelte 自身并不知道
-
如果你想真正地让UI自动刷新,你需要额外的桥接; 比如:
ts 复制 // 伪代码: 创建一个svelte store,autorun同步MobX的值 import { writable } from 'svelte/store'; import { myStore } from './store'; import { autorun } from 'mobx'; export const svelteCount = writable(myStore.count); autorun(() => { svelteCount.set(myStore.count); });这样在组件里用
$svelteCount才会自动更新 DOM。 -
你的
connect()只解决了“把 Reaction 生命周期和 Svelte 组件解绑”的问题,让你可以在 Svelte 里愉快地调用mobx.autorun()而不用担心卸载组件时忘记清理 Reaction。
只有这么少的底层代码,为什么?
- 因为它只专注做了最小的事情:在 Svelte 组件的销毁周期中,自动调用
mobx的disposer。 - 实际上,如果你看过像
mobx-svelte-store或svelte-mobx之类的库,它们功能更多:会把 MobX observable 数据封装成 Svelte Store,或通过一系列高级API让你在 Svelte 模板里直接写$: someVar = store.prop也能自动更新。 - 但上面这段
connect()只是一个简单的示例/工具函数,并不包含完整的“自动桥接 MobX → Svelte”流程,就这么点代码也很正常。大多数使用 MobX + Svelte 的项目,往往会自己写一个或者用现成的封装,让这两套响应式机制“互通”。