前端进阶(九)-微应用框架、Svelte
安装
yarn add qiankun
npm i qiankun -S
注册微应用并启动:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/yourActiveRule',
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
start();
微应用分为有 webpack
构建和无 webpack
构建项目,有 webpack
的微应用(主要是指 Vue、React、Angular)需要做的事情有:
public-path.js
文件,用于修改运行时的 publicPath
。history
模式的路由,需要设置路由 base
,值和它的 activeRule
是一样的。public-path.js
,修改并导出三个生命周期函数。webpack
打包,允许开发环境跨域和 umd
打包。qiankun微应用的加载过程
首先读取 entry
的 HTML 文件,解析其中的内容。
抽取 HTML 文件内的 JS 文件请求,使用重构后的 fetch 请求进行 JS 资源拉取,并且对返回的 JS 代码进行沙箱隔离,上下文设置。
抽取 HTML 文件内的 CSS 文件请求,使用重构后的 fetch 请求进行 CSS 资源拉取,并且对返回的 CSS 代码进行样式沙箱隔离(shadow DOM
)。
在 src
目录新增 public-path.js
:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
设置 history
模式路由的 base
:
Micro app是京东开发的微前端app框架,2021年7月开源
qiankun 还是有一些缺点的:
在micro-app
之前,业内已经有一些开源的微前端框架,比较流行的有2个:single-spa
和qiankun
。
single-spa
是通过监听 url change 事件,在路由变化时匹配到渲染的子应用并进行渲染,这个思路也是目前实现微前端的主流方式。同时single-spa
要求子应用修改渲染逻辑并暴露出三个方法:bootstrap
、mount
、unmount
,分别对应初始化、渲染和卸载,这也导致子应用需要对入口文件进行修改。因为qiankun
是基于single-spa
进行封装,所以这些特点也被qiankun
继承下来,并且需要对webpack配置进行一些修改。
micro-app
并没有沿袭single-spa
的思路,而是借鉴了WebComponent的思想,通过CustomElement结合自定义的ShadowDom,将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染。并且由于自定义ShadowDom的隔离特性,micro-app
不需要像single-spa
和qiankun
一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置,是目前市面上接入微前端成本最低的方案。
Micro App的特点:
安装micro app
yarn add @micro-zoe/micro-app
在入口处引入
// index.js
import microApp from '@micro-zoe/micro-app'
microApp.start()
分配一个路由给子应用
// router.js
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import MyPage from './my-page'
export default function AppRoute () {
return (
<BrowserRouter>
<Switch>
// 👇 非严格匹配,/my-page/* 都指向 MyPage 页面
<Route path='/my-page'>
<MyPage />
</Route>
</Switch>
</BrowserRouter>
)
}
在应用中嵌入子应用
// my-page.js
export function MyPage () {
return (
<div>
<h1>子应用</h1>
// name(必传):应用名称
// url(必传):应用地址,会被自动补全为http://localhost:3000/index.html
// baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page`
<micro-app name='app1' url='http://localhost:3000/' baseroute='/my-page'></micro-app>
</div>
)
}
子应用设置基础路由
// router.js
import { BrowserRouter, Switch, Route } from 'react-router-dom'
export default function AppRoute () {
return (
// 👇 设置基础路由,子应用可以通过window.__MICRO_APP_BASE_ROUTE__获取基座下发的baseroute,如果没有设置baseroute属性,则此值默认为空字符串
<BrowserRouter basename={window.__MICRO_APP_BASE_ROUTE__ || '/'}>
...
</BrowserRouter>
)
}
在webpack-dev-server的headers中设置跨域支持
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
}
},
数据通信也是 micro-app
的一大亮点,在使用层面上比 qiankun
的 EventBus 要好用一点
父传子
在基座应用的容器组件里添加一个 microApp.setData
就好了
// ReactMicroApp
const ReactMicroApp = () => {
const [microAppState, setMicroAppState] = useState(microApp.getData('react-app'));
// 发送数据给 react-app
const sendReactAppData = () => {
// setData的值必须为对象
microApp.setData('react-app', {
name: `react-app 随机数: ${Math.random()}`
})
}
return (
<div>
<h1>react-app</h1>
<button onClick={sendReactAppData}>基座 => react-app</button>
<p>子应用消息 {microAppState ? microAppState.msg : '无'}</p>
<micro-app
name='react-app'
url='http://localhost:3001/'
baseroute='/react-app'
>
</micro-app>
</div>
)
}
微应用在接收数据的时候也是一样通过事件监听addDataListener来获取
// react-app App.js
function App() {
const [mainAppData, setMainAppData] = useState( window.microApp.getData());
// 监听基座数据
useEffect(() => {
if (window.microApp) {
const dataListener = (data) => {
console.log('主应用传的数据', data);
setMainAppData(data);
}
window.microApp.addDataListener(dataListener)
return () => {
window.microApp.clearDataListener()
}
}
})
return (
<div>
<p>主应用的数据:{mainAppData ? mainAppData.name : '无'}</p>
<BrowserRouter basename={window.__MICRO_APP_BASE_ROUTE__ || '/'}>
<div>
<header className={styles.header}>
<Link to="/">react-app Home</Link>
<Link to="/about">react-app About</Link>
</header>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/about" element={<About/>}/>
</Routes>
</div>
</BrowserRouter>
</div>
);
}
子传父时微应用发送数据也是一行 window.microApp.dispatch
就 OK 了,而且入参必须也为对象
// react-app App.js
function App() {
const [mainAppData, setMainAppData] = useState( window.microApp.getData());
useEffect(() => {
if (window.microApp) {
const dataListener = (data) => {
console.log('主应用传的数据', data);
setMainAppData(data);
}
window.microApp.addDataListener(dataListener)
return () => {
window.microApp.clearDataListener()
}
}
})
// 发送数据给基座
const sendToMain = () => {
window.microApp.dispatch({msg: `我是 react-app 随机数 ${Math.random()}`})
}
return (
<div>
<p>主应用的数据:{mainAppData ? mainAppData.name : '无'}</p>
<button onClick={sendToMain}>给基座应用发送</button>
<BrowserRouter basename={window.__MICRO_APP_BASE_ROUTE__ || '/'}>
<div>
<header className={styles.header}>
<Link to="/">react-app Home</Link>
<Link to="/about">react-app About</Link>
</header>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/about" element={<About/>}/>
</Routes>
</div>
</BrowserRouter>
</div>
);
}
基座监听的方式有点hacky
虽然没有用到 jsxCustomEvent
,但是也一定要 import 进来,且不能把注释给干掉!
// ReactMicroApp.js
import microApp from '@micro-zoe/micro-app'
import {useState} from "react";
/** @jsxRuntime classic */
/** @jsx jsxCustomEvent */
import jsxCustomEvent from '@micro-zoe/micro-app/polyfill/jsx-custom-event'
const ReactMicroApp = () => {
const [microAppState, setMicroAppState] = useState(microApp.getData('react-app'));
const sendReactAppData = () => {
microApp.setData('react-app', {
name: `react-app 随机数: ${Math.random()}`
})
}
const onDataChange = (e) => {
console.log('react-app onDataChange', e);
console.log('react-app 数据', e.detail.data);
setMicroAppState(e.detail.data);
}
return (
<div>
<h1>react-app</h1>
<button onClick={sendReactAppData}>基座 => react-app</button>
<p>子应用消息 {microAppState ? microAppState.msg : '无'}</p>
<micro-app
name='react-app'
url='http://localhost:3001/'
baseroute='/react-app'
onDataChange={onDataChange}
>
</micro-app>
</div>
)
}
渲染模式
使用 globalAssets
共享资源:
// index.js
import microApp from '@micro-zoe/micro-app'
microApp.start({
globalAssets: {
js: ['js地址1', 'js地址2', ...], // js地址
css: ['css地址1', 'css地址2', ...], // css地址
}
})
或者使用global属性
<link rel="stylesheet" href="xx.css" global>
<script src="xx.js" global></script>
还有对资源的过滤
<link rel="stylesheet" href="xx.css" exclude>
<script src="xx.js" exclude></script>
<style exclude></style>
micro app提供了不同在微应用的生命周期可以执行不同的命令
/** @jsxRuntime classic */
/** @jsx jsxCustomEvent */
import jsxCustomEvent from '@micro-zoe/micro-app/polyfill/jsx-custom-event'
const App = () => {
return (
<micro-app
name='xx'
url='xx'
onCreated={() => console.log('micro-app元素被创建')}
onBeforemount={() => console.log('即将被渲染,只在初始化时执行一次')}
onMounted={() => console.log('已经渲染完成,只在初始化时执行一次')}
onAfterhidden={() => console.log('已卸载')}
onBeforeshow={() => console.log('即将重新渲染,初始化时不执行')}
onAftershow={() => console.log('已经重新渲染,初始化时不执行')}
onError={() => console.log('渲染出错')}
/>
)
}
应用之间跳转
隔离
JS 方面使用 Proxy
拦截了用户全局操作的行为,防止对 window
的访问和修改,避免全局变量污染。
CSS 方面有两种隔离:
元素隔离方面,micro-app
模拟实现了类似 ShadowDom
的功能,元素不会逃离 <micro-app>
元素边界,子应用只能对自身的元素进行增、删、改、查的操作。
keepAlive
保持微应用的状态 Keep-Alive
<micro-app name='xx' url='xx' keep-alive></micro-app>
prefetch
qiankun侵入式较强
Svelte是一个给我同样感觉的JavaScript框架。与React,Vue,Angular和其他框架相比,使用Svelte构建的应用程序是已编译,不必为网站访问者提供框架和库代码,因此,所有体验的结果都更加流畅,占用的带宽更少,并且一切感觉更快,更轻便。
与Hugo一样,Hugo会在部署时消失并生成纯HTML,Svelte也会消失,而您所获得的只是纯JavaScript。
Svelte 用于构建快速的 Web 应用。
Svelte 类似 React 和 Vue,都致力让你轻而易举地构建灵活的可交互的用户界面。
与它们截然不同的是:Svelte 在构建时 将你的代码转为更优的 JavaScript,而不是在 运行时 才解释执行你的代码。
这预示着你无需付出框架本身的性能成本,且首次加载也无额外性能损耗。
你可以使用 Svelte 编写整个应用,也可以用来逐步重构现有代码,整半皆可;还可以只输出单个独立组件(无需强制附带框架自身)并将其用于任意框架中,可谓百面玲珑。
响应式代码
<script>
let count = 0;
$: doubled = count * 2;
// 只要更改count 的值,我们就可以在控制台输出它的值
$: console.log(`the count is ${count}`);
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
<p>{count} doubled is {doubled}</p>
在 Svelte 中,我们使用 export
关键字来实现接受父组件传递过来的属性值。
组件的另一个核心点是 UI,用于描述 UI 的 HTML 是一种“静态”的语言,HTML 是无法表达 逻辑 的,比如条件和循环,但 Svelte 可以。Svelte 为其增加了一些逻辑支持,譬如判断、遍历等,以增强你对 UI 的表达能力。
这些额外附加的逻辑能力似曾相识,它十分接近一些以往非常流行的 Handlebars 或者 Mustache 模板语言,例如{ {#if ...} }
,稍有区别的是,Svelte 使用单个大括号括起:{#if ...}
。
Svelte 的编译器将会编译这些逻辑,通过将 HTML 编译成使用原生 JS 创建的形式,也即这些 HTML 最终将使用 createElement 等原生的 DOM API 来创建。
解释 HTML 模板时,Svelte 就已经明确哪些状态需要更新。
if语句
<script>
let user = { loggedIn: false };
function toggle() {
user.loggedIn = !user.loggedIn;
}
</script>
{#if user.loggedIn}
<button on:click={toggle}>Log out</button>
{:else}
<button on:click={toggle}>Log in</button>
{/if}
each语句
<script>
let cats = [
{ id: 'J---aiyznGQ', name: 'Keyboard Cat' },
{ id: 'z_AbfPXTKms', name: 'Maru' },
{ id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' }
];
</script>
<h1>The Famous Cats of YouTube</h1>
<ul>
{#each cats as cat}
<li>
<a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
{cat.name}
</a>
</li>
{/each}
</ul>
异步数据
<script>
async function getRandomNumber() {
const res = await fetch(`https://svelte.dev/tutorial/random-number`);
const text = await res.text();
if (res.ok) {
return text;
} else {
throw new Error(text);
}
}
let promise = getRandomNumber();
function handleClick() {
promise = getRandomNumber();
}
</script>
<button on:click={handleClick}>生成随机数字</button>
{#await promise}
<p>{promise}</p>
{:then number}
<!-- 返回的结果使用 :then 块连续标记,
后面提供接收Promise resolve 的变量名称
-->
<p>The number is {number}</p>
{:catch error}
<!-- 如果发生错误,在 :catch 子块中接收错误对象 -->
<p style="color: red">{error.message}</p>
{/await}
只执行一次
<script>
function handleClick() {
alert('no more alerts')
}
</script>
<button on:click|once={handleClick}>Click me</button>
事件修饰符
preventDefault
:在运行事件处理程序之前先调用 event.preventDefault()
,这对客户端在表单处理时就很有用。stopPropagation
:调用 event.stopPropagation()
停止事件冒泡,防止事件传播到下一个元素。passive
:改进触摸/滚轮事件的滚动性能(Svelte 默认会自动在安全的地方添加它)。nonpassive
:显式设置 passive: false
。capture
:在 捕获 阶段就触发处理程序,而非 冒泡 阶段(参阅 MDN 文档)once
:在运行一次事件处理后,立即将其移除。self
:仅当 event.target 是元素本身时才触发事件处理程序。事件分发
组件也可以发送(dispath)事件。为此,需要创建一个事件分发器(dispatcher)。
为了展示组件事件,需要先写一个组件
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Inner on:message={handleMessage}/>
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>Click to say hello</button>
this
onMount
再常见不过,它在组件首次渲染到 DOM 后立即触发
要在销毁组件时运行代码,请使用onDestroy
。
beforeUpdate
函数会在 DOM 更新之前执行,而相应的 afterUpdate
则在数据同步到 DOM 之后执行。
tick
函数与其他生命周期函数有所不同,不一定是在首次初始化时调用,你可以随时调用它。它返回一个 Promise,并 resolve 任何挂起的状态(如果没有就立即 resolve)。
在 Svelte 中,当你更新组件状态时,它不会立即更新 DOM。相反,它会等到下一个 微任务 时更新,期间查看是否需要应用其他的更改,包括其他组件的更改。这样做可以减少一些无用功,让浏览器更有效地批量处理这些事情。
Svelte 是没有对应的状态库的,因为它内置了状态管理,它被称为 store
。
当期望脱离组件的层级(父-子)关系且能够在任意位置都能访问某个状态(变量)时,状态管理仍然是非常有用的一个特性。
并非所有的状态都属于在组件层次的结构内。某些时候,有些状态需要被多个毫不相干的组件或普通的 JavaScript 模块访问。
在 Svelte 中,我们通过store来实现。
Svelte 将状态划分为两种,一种可读可写
,一种只读
,都用可读可写
(可以读取,也可以修改)虽然省事,不过允许或者说强制状态是只读的,可以防止状态被意外修改。
所谓 store(也即状态),只不过是具有 subscribe
方法的对象,它允许当 store 的值改变时自动通知对此感兴趣的相关组件或程序。在 App.svelte
中,count
便是一个 store,我们在 count.subscribe
的回调中设置 count_value
的值。
Svelte 假定所有以 $
开头的任何标识符都表示引用某个 store 值,而 $
实际上是一个保留字符,Svelte 会禁止你使用 $
作为你声明的变量的前缀。
<script>
import { count } from './stores.js';
import Incrementer from './Incrementer.svelte';
import Decrementer from './Decrementer.svelte';
import Reset from './Reset.svelte';
</script>
<h1>count 当前的值是:{$count}</h1>
并非所有 store 允许所有人可写的。例如,你可能有一个 store 表示鼠标位置或者用户地理位置,允许 ‘外部’ 来修改这个值是没有意义的。对于这种情况,我们可以用只读store。
只读状态
并非所有 store 允许所有人可写的。例如,你可能有一个 store 表示鼠标位置或者用户地理位置,允许 ‘外部’ 来修改这个值是没有意义的。对于这种情况,我们可以用只读store。
import { readable } from 'svelte/store';
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => set(new Date()), 1000);
return function stop() { clearInterval(interval); };
});
readable
的第一个参数代表初始值,如果还没有具体的初始值,可以先设为 null
或 undefined
。
第二个参数 start
是一个函数,该函数接受一个 set
回调用于设置值,并且返回一个 stop
函数。
当 store 被第一个订阅者读取时,就会调用 start
函数(然后在 start
函数中使用 set
提供一个最终的值;
最后一个订阅者退订时,调用 stop
函数。
状态继承
derived
来创建一个新的 store,它将继承自某一个或多个其他的 store。
import { readable, derived } from 'svelte/store';
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => {set(new Date()), 1000);
return function stop() { clearInterval(interval); };
});
const start = new Date();
export const elapsed = derived(
time,
$time => Math.round(($time - start) / 1000)
);
可从多个 store 派生为一个 store,并显式地 set
一个值而不是返回它(这对于异步派生的值很有用处)。
自定义store store.js
import { writable } from 'svelte/store';
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const count = createCount();
引入store中的状态
<script>
import { count } from './stores.js';
</script>
<h1>count 的当前值是:{$count}</h1>
<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>
用RXJS替代store
要获得 store
的值,是通过 store.subscribe
方法去订阅,store 产生的值会自动推送给订阅者,这点与 RxJS 如出一辙,实际上,你可以将 Svelte 的 store
看作是 RxJS 的 Observable
。
这样做不是没有原因的,其一是为了更容易与类似 rxjs 这样的库相结合,共同发挥作用;其二是认同 RxJS 这套观察者-订阅的理念且值得借鉴。
<script>
import { fromEvent } from 'rxjs'
import { map } from 'rxjs/operators'
let position = fromEvent(document, 'mousemove')
.pipe(map(e => `鼠标位置: ${e.clientX},${e.clientY}`))
</script>
{$position}
Svelte 支持两种运动效果,一种是补间动画效果 tweened
,另一种是弹簧效果 spring
。这两种效果在页面中都十分常见,因此 Svelte 内置支持它们。
过渡效果更是页面动画的重头戏,由于十分常用,Svelte 为过渡动画效果专门创建了指令级别的支持,分别是 transition
、in
、out
,它们都支持设置附加参数,例如 duration
用于设置动画运动的总时长。transition
是将进入过渡和退出过渡两者齐集一身
Svelte 无法为各种各样的动画提供预设支持,因此如果某些效果特别引人入胜,你可以通过自定义 CSS 过渡效果来包装它们,有些还需要配合 JS 创建的效果,Svelte 也是支持的。
局部过渡其实是将动画效果限制只应用在元素添加或删除时才触发,而在首次创建时元素不会被执行动画效果。
延迟过渡经常用于例如穿梭框之类的组件中,它可以实现提高用户体验的交互。这种效果通常会有一个发送端
(send)和接收端
(receive),例如 crossfade
效果。
action
同样是一种为组件增强能力的特性
action
与bind:this不同的是,它有更强的封装性和复用性,将逻辑代码拆分到 JS 文件中供反复应用。
<script>
export function longpress(node, duration) {
let timer;
const handleMousedown = () => {
timer = setTimeout(() => {
node.dispatchEvent(
new CustomEvent('longpress')
);
}, duration * 1000);
};
const handleMouseup = () => clearTimeout(timer);
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('mouseup', handleMouseup);
return {
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('mouseup', handleMouseup);
}
};
}
</script>
<button
use:longpress={duration}
...
>按住我别放</button>
HTML 元素允许相互组合,由此构建复杂的页面。
如果不是普通的 HTML 元素,而是 Svelte 组件,同样支持让组件作为容器,为其添加子组件,以此组合出更大更强的组件,这种容器被称为 插槽
(Slots)。
组件可以指定<slot>
元素当内容为空的缺省情况应该默认显示什么内容:
我们包含了一个默认插槽,用于直接代替组件的子节点。不过有些时候,你需要对放置的内容有更多的控制能力,我们可以使用 命名插槽 :
// box.svelte
<div class="box">
<h3>
<slot name="name">Unknown name</slot>
</h3>
<div>
<slot name="address">Unknown address</slot>
</div>
</div>
// app.svelte
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
<span slot="name">P. Sherman</span>
<span slot="address">42 Wallaby Way Sydney</span>
</Box>
<Box />
可以编写一个if
块,块的范围包括了comments
评论插槽以及插槽所在的整个<div>
,在if
块中需要检测$$slots
中的comments
插槽是否存在内容:
<script>
export let title = ''
</script>
<article>
<h2>{title}</h2>
<hr />
<div><slot name="content" /></div>
{#if $$slots.comments}
<div style="margin-top: 30px;">
评论:
<slot name="comments" />
</div>
{/if}
</article>
组件 Context(上下文) API 为组件提供一种彼此 '对话' 机制,而无需将数据和函数作为属性来传递,或者发送大量的事件。
Context 相关的 API 函数有两个,分别是setContext
和getContext
。
如果组件调用了setContext(key, context)
,那么其任意的子组件 都可以通过const context = getContext(key)
来获取这个 context。
Svelte 对比 React 稍微要简单直观一些,抛却了 Provider 和 Consumer 的概念,事实上简单些就很好,就想组件间共享点数据罢了,没必要有板有眼创造一些高级隐喻,让其漂染浓厚的设计
气息。
模块上下文的主要用途,是提供一种方法,让某个组件的所有实例都共享同一个上下文(这其实用 store 也完全能实现同等功能,只不过模块上下文更显得关联性高一些,以及封装性强一些)。
Svelte 提供了各种内置的特殊元素。
<svelte:self>
,它表示当前组件,允许在某些情况递归自身,这对于展示文件夹树之类的视图很有用,其中文件夹可以包含 其他 文件夹。
{#each folders as f}
<li>
<svelte:self {...f} />
</li>
{/each}
组件可以通过<svelte:component>
进行 “变身”,这样可以省掉一系列的if
块
<script>
import Red from './Red.svelte';
import Green from './Green.svelte';
import Blue from './Blue.svelte';
let selected = '0';
$: component = [Red, Green, Blue][selected]
</script>
<select bind:value={selected}>
<option value="0">红</option>
<option value="1">绿</option>
<option value="2">蓝</option>
</select>
<svelte:component this={component} />
正如任何 DOM 元素都可以将监听事件一样,你可以通过<svelte:window>
来监听window
对象的事件,例如鼠标移动的事件:
<script>
let x, y
</script>
<svelte:window on:mousemove={ e => ({x, y} = e) } />
<p>鼠标位置:{x}, {y}</p>
我们还可以绑定一些window
的属性,某些业务我们可能需要监测浏览器窗口的大小,原生的写法
是通过监听 window.onresize
来实现的。
在 Svelte 中,它更为简单,并且支持绑定
<script>
let width, height
</script>
<svelte:window bind:innerWidth={width} bind:innerHeight={height} />
<p>Window Size: width={width}, height={height}</p>
支持绑定的属性列表如下:
innerWidth
- 获得浏览器窗口的内容区域的宽度,包含垂直滚动条(如果有的话)innerHeight
- 获得浏览器窗口的内容区域的高度,包含水平滚动条(如果有的话)outerWidth
- 获得浏览器窗口的宽度outerHeight
- 获得浏览器窗口的高度scrollX
- document 在水平方向已滚动的像素值scrollY
- document 在垂直方向已滚动的像素值online
- window.navigator.onLine
的别名,代表当浏览器能够访问网络。<svelte:body>
元素允许你监听document.body
上的事件。这对于mouseenter
和mouseleave
事件来说十分有用,它们无法在window
上触发。
<script>
let msg = ''
</script>
<svelte:body
on:mouseenter={() => msg = 'Enter'}
on:mouseleave={() => msg = 'Leave'}
/>
<div>{msg}</div>
<svelte:head>
元素允许你将其他元素插入到文档中的<head>
中:
<head>
中可以插入例如 <script>
、<link>
、<title>
和 <meta>
等等元素:
<svelte:head>
<link rel="stylesheet" href="./global.css">
</svelte:head>
在服务器端渲染 (SSR) 模式下,<svelte:head>
的内容与 HTML 的其余部分是分别返回的。
<svelte:fragment>
实现类似插槽的功能。
<script>
import Material from './Material.svelte'
</script>
<Material>
<svelte:fragment slot="bottom">
<li>高筋面粉</li>
<li>低筋面粉</li>
</svelte:fragment>
</Material>
<svelte:options>
允许你配置编译器的选项。比如希望为组件的变量生成属性访问器(默认不生成)。生成属性访问器的目的,是提供给使用端更容易去访问的,这通常是你编写了一个 Svelte 组件,想同时在 React 或者 Vue 等地方上用时才需要这个选项。
<script>
export let x = 'hello'
</script>
<svelte:options accessors />
<p>{x}</p>
下列是允许在此处设置可选用的值:
immutable={true}
:你确信不会使用可变数据,因此编译器可以执行简单的引用相等性检查来确定值是否已更改。immutable={false}
:默认值,Svelte 将按默认的方式处理可变对象的更改accessors={true}
:为组件的 props 添加 getter 和 setteraccessors={false}
:默认值namespace="..."
:将使用此组件的命名空间,最常见的就是 "svg"
tag="..."
:将组件编译为自定义元素时,指定的标签名称正如 React 之于 Next,Svelte 对应的 Web 框架便是 SvelteKit。
它是一个用 Svelte 构建应用的框架,包括服务器端渲染(SSR)、路由、针对 JS 和 CSS 的代码分割,以及针对不同 Serverless 平台生成不同代码的适配器等等。