资讯动态

Svelte `svelte/reactivity/window` 模块详解:把 window 属性变成响应式状态

发布时间:2026/9/7 4:19:05 来源:尧图企业网站定制
Sveltesvelte/reactivity/window模块详解把 window 属性变成响应式状态【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte本文基于 Svelte 官方参考文档 21-svelte-reactivity-window.md 展开系统讲解svelte/reactivity/window模块提供的 10 个响应式 window 值。读完本文你将掌握如何在模板、$derived和$effect中直接引用响应式的window.innerWidth、window.scrollY等值而无需手动绑定事件并能从源码层面理解其订阅机制、各值背后的更新策略差异事件驱动、requestAnimationFrame轮询、matchMedia监听以及服务端渲染时的行为边界。模块定位免去手动事件监听的响应式 window 视图在 Svelte 5SvelteKit 项目中的现代写法即 runes 模式下把浏览器window对象的属性接入响应式系统传统上有两条路使用svelte:window特殊元素的bind:绑定参考 02-svelte-window.md在$effect里自己addEventListener/removeEventListener。svelte/reactivity/window模块提供了第三种、也是更省心的方式它导出一组预构建的响应式 window 值每个值都有一个响应式的current属性。只要在响应式上下文中模板、$derived、$effect读取xxx.currentSvelte 就会自动追踪该 window 属性并在其变化时刷新相关依赖事件监听、清理、SSR 兜底全部由模块内部完成。官方文档给出的最小示例如下script import { innerWidth, innerHeight } from svelte/reactivity/window; /script p{innerWidth.current}x{innerHeight.current}/p在 packages/svelte/package.json 中可以看到该子路径导出的入口声明./reactivity/window指向./src/reactivity/window/index.js类型定义统一收敛在./types/index.d.ts所有导出均可按svelte/reactivity/window直接 import。完整导出清单10 个响应式值及其更新机制模块源码位于 packages/svelte/src/reactivity/window/index.js。结合源码与类型声明 packages/svelte/types/index.d.ts完整导出清单如下均自 Svelte 5.11.0 起提供类型签名统一为ReactiveValuenumber | undefinedonline除外导出对应 window 属性更新机制源自源码服务端行为scrollXwindow.scrollXscroll事件undefinedscrollYwindow.scrollYscroll事件undefinedinnerWidthwindow.innerWidthresize事件undefinedinnerHeightwindow.innerHeightresize事件undefinedouterWidthwindow.outerWidthresize事件undefinedouterHeightwindow.outerHeightresize事件undefinedscreenLeftwindow.screenLeftrequestAnimationFrame轮询undefinedscreenTopwindow.screenToprequestAnimationFrame轮询undefinedonlinenavigator.onLineonlineoffline事件undefineddevicePixelRatiowindow.devicePixelRatiomatchMedia的change事件undefined几点需要注意的边界行为服务端一律为undefined每个导出都写成BROWSER ? () window.xxx : () undefined的形式BROWSER来自esm-env因此同一份代码在 SSR/Node 环境读取current不会抛错只是得到undefined。online跟踪的是navigator.onLine而非window.online——这是源码中唯一一处读取navigator命名空间的值。devicePixelRatio的浏览器差异源码注释明确指出Chrome 下它会随当前缩放级别变化而 Firefox 和 Safari 下不会。screenLeft/screenTop的更新时机源码注释写明它们是在requestAnimationFrame回调中检测变化的并非即时同步。SSR 行为在测试中有直接印证packages/svelte/tests/server-side-rendering/samples/reactivity-window/main.svelte 一次性 import 了全部 10 个值并在模板中逐个渲染{xxx.current}对应的_expected.html断言了它们在服务端渲染产物中呈现为undefined。典型用法模板、derived 与 effect 中的直接引用在模板中current的读取会被 Svelte 编译器识别为对响应式状态的读取属性变化时自动更新 DOMscript import { innerWidth, innerHeight, online } from svelte/reactivity/window; import { $derived } from svelte; /script !-- 响应式上下文一模板 -- p{innerWidth.current}x{innerHeight.current}/p !-- 响应式上下文二derived -- script module context 不可用这里示意在普通 script 内 let isLandscape $derived(innerWidth.current innerHeight.current); -- p{online.current ? 在线 : 离线}/p在$derived与$effect中同样可以直接读取current不需要任何额外接线script import { innerWidth, scrollY } from svelte/reactivity/window; import { $derived, $effect } from svelte; let isNarrow $derived(innerWidth.current 768); $effect(() { // 仅在 scrollY.current 变化时重跑 console.log(scrollY:, scrollY.current); }); /script {#if isNarrow} p窄屏模式/p {/if}与svelte:window的差异在于svelte:window bind:innerWidth{w} /需要双向绑定的可变变量且该元素只能出现在组件顶层而svelte/reactivity/window的值是只读视图可以在任何组件、任何位置包括被script逻辑驱动的深层模板直接引用也不需要为每个值引入一个绑定变量。当你的需求只是“读取并响应 window 值”时本模块是更轻量的选择需要回写例如bind:scrollY滚动页面时仍应使用svelte:window。源码剖析一ReactiveValue 与惰性引用计数订阅每个导出本质上都是一个ReactiveValue实例。以innerWidth为例window/index.jsexport const innerWidth new ReactiveValue( BROWSER ? () window.innerWidth : () undefined, // fn取值函数 (update) on(window, resize, update) // onsubscribe订阅启动函数 );ReactiveValue类定义在 packages/svelte/src/reactivity/reactive-value.js非常精简export class ReactiveValue { #fn; #subscribe; constructor(fn, onsubscribe) { this.#fn fn; this.#subscribe createSubscriber(onsubscribe); } get current() { this.#subscribe(); // 在 effect 中被读取时挂上订阅 return this.#fn(); // 无论是否在 effect 中都返回真实当前值 } }这里的两个细节值得注意get current()是惰性的——只有在响应式追踪上下文effect_tracking()为真中读取时内部的render_effect才会被创建事件监听才会真正挂上return this.#fn()保证了即使在非响应式上下文中读取例如普通函数里取一次innerWidth.current拿到的也是窗口真实值而不是脏缓存。而真正的订阅生命周期管理委托给createSubscriberpackages/svelte/src/reactivity/create-subscriber.js它解决了一个核心问题多个 effect 同时读取同一个值时底层事件监听只挂一次全部 effect 销毁后才真正卸载。其内部机制维护subscribers计数与一个内部version source(0)信号每个读取current的 effect 都会get(version)建立依赖并注册一个render_effect当计数从 0 变 1 时调用start(update)拿到stop函数计数归 0 时调用stop()并increment(version)源码中的注释解释了为何退订计数要用queue_micro_task延后一拍——否则 effect 重跑与旧 teardown 的时序交错会导致计数错误归零产生“不必要的重新订阅 旧订阅泄漏”归零时的increment(version)则是为了把下游$derived标记为脏确保之后重新订阅时不会因版本号比较而被跳过。onsubscribe中使用的on函数来自 packages/svelte/src/events/index.js其实现位于 packages/svelte/src/internal/client/dom/elements/events.js它调用addEventListener并返回一个解绑函数因此与createSubscriber的stop语义天然契合。另外在开发模式下devicePixelRatio的内部 source 还会被tag(this.#dpr, window.devicePixelRatio)打上追踪标签来自internal/client/dev/tracing.js方便 Svelte DevTools 识别该值。源码剖析二不同值采用不同更新策略的原因统一走on(window, resize, update)显然不够——并不是每个 window 属性都有对应事件。从源码可以看出三种策略1. 事件驱动scroll / resize / onlineexport const scrollX new ReactiveValue( BROWSER ? () window.scrollX : () undefined, (update) on(window, scroll, update) ); export const online new ReactiveValue( BROWSER ? () navigator.onLine : () undefined, (update) { const unsub_online on(window, online, update); const unsub_offline on(window, offline, update); return () { unsub_online(); unsub_offline(); }; } );online需要同时挂两个事件所以onsubscribe里手动组合了两个解绑函数再返回一个统一的清理函数——这正是createSubscriber约定的 teardown 形态。2. rAF 轮询screenLeft / screenTopexport const screenLeft new ReactiveValue( BROWSER ? () window.screenLeft : () undefined, (update) { let value window.screenLeft; let frame requestAnimationFrame(function check() { frame requestAnimationFrame(check); if (value ! (value window.screenLeft)) { update(); } }); return () { cancelAnimationFrame(frame); }; } );浏览器并不为窗口在屏幕上的移动位置提供专用事件因此源码选择了一个自续的requestAnimationFrame循环逐帧比对值变化才触发update()清理时cancelAnimationFrame终止轮询。这也解释了文档注释中“它在requestAnimationFrame回调中被更新”的措辞——屏幕多显示器拖拽场景下screenLeft/screenTop的响应是逐帧采样出来的而非事件即时推送。3. matchMedia 递归重订阅devicePixelRatiodevicePixelRatio是唯一不用ReactiveValue的导出而是一个自定义类window/index.jsexport const devicePixelRatio /* __PURE__ */ new (class DevicePixelRatio { #dpr source(BROWSER ? window.devicePixelRatio : undefined); #update() { const off on( window.matchMedia((resolution: ${window.devicePixelRatio}dppx)), change, () { set(this.#dpr, window.devicePixelRatio); off(); this.#update(); // 重新按新分辨率建立 matchMedia 查询 } ); } constructor() { if (BROWSER) this.#update(); if (DEV) tag(this.#dpr, window.devicePixelRatio); } get current() { get(this.#dpr); return BROWSER ? window.devicePixelRatio : undefined; } })();原因在于matchMedia查询是静态的——监听(resolution: 2dppx)只能捕获“离开 2dppx”这一次变化一旦变化发生必须销毁旧查询、按新值重建才能继续监听下一档分辨率例如浏览器缩放从 100% 到 125% 到 150% 的连续跳变。因此这里实现了“变更 → 写入信号 → 解绑 → 重建查询”的递归自更新循环并用source/set信号 get追踪来实现响应式读取。与svelte:window绑定的对照两者可绑定的属性集合基本一致svelte:window支持innerWidth、innerHeight、outerWidth、outerHeight、scrollX、scrollY、online、devicePixelRatio八个绑定其中除scrollX/scrollY外均只读且svelte:window只能位于组件顶层。选型建议只读消费 window 值→ 用svelte/reactivity/window任意位置可用、无绑定变量开销、SSR 安全需要双向回写bind:scrollX/bind:scrollY驱动滚动→ 仍用svelte:window需要监听 window 事件onkeydown等→svelte:window或自行用svelte/events的on()。值得注意的是svelte/reactivity/window是纯运行时导出不依赖svelte:window元素在模板中出现两者的底层事件接入可以推断为殊途同归都是对window上的addEventListener做封装但前者通过createSubscriber的引用计数把监听器的增删与响应式 effect 的生命周期精确对齐。模块入口、类型与版本约束子路径导出packages/svelte/package.json 中./reactivity/window映射到./src/reactivity/window/index.js无 browser/server 条件区分——SSR 安全完全靠源码内的BROWSER判断实现类型声明packages/svelte/types/index.d.ts 中declare module svelte/reactivity/window段约第 2620 行起逐一声明了 10 个导出类型统一为ReactiveValuenumber | undefined与文档“On the server it isundefined”的表述一致版本前提所有导出标注since 5.11.0即需要 Svelte 5.11 及以上版本仓库当前 packages/svelte/package.json 版本为 5.57.0运行环境要求node 18相邻模块同属响应式工具族的svelte/reactivitySvelteDate、SvelteSet、SvelteMap、SvelteURL、MediaQuery、createSubscriber等入口见 packages/svelte/src/reactivity/index-client.js其文档见 21-svelte-reactivity.md。svelte/reactivity/window正是构建在其导出的createSubscriber之上的典型应用如果你需要把其他 Web API如IntersectionObserver、WebSocket包装成响应式值可以直接复用同一套createSubscriberon()的模式。小结svelte/reactivity/window用 10 个导出把最常用的 window 属性变成了“读一下current就能自动追踪”的响应式状态scrollX/scrollY走scroll事件inner*/outer*走resize事件online走online/offline事件screenLeft/screenTop用 rAF 轮询兜底无事件的属性devicePixelRatio则通过matchMedia递归重订阅跟踪缩放变化所有导出在服务端读取时安全地返回undefined而订阅的挂载与卸载由createSubscriber的引用计数机制与 effect 生命周期严格对齐。对于只需消费 window 值的场景它比svelte:window绑定和手写事件监听都更简洁且源码实现可直接作为封装自定义响应式 Web API 的参考范式。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价