前端进阶(九)-微应用框架、Svelte
微前端 微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
它主要解决了两个问题:
1、随着项目迭代应用越来越庞大,难以维护。
2、跨团队或跨部门协作开发项目导致效率低下的问题。
qiankun 安装
1 2 yarn add qiankun npm i qiankun -S
注册微应用并启动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { registerMicroApps, start } from 'qiankun' ;registerMicroApps([ { name: 'react app' , 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 打包。
react app接入qiankun 在 src 目录新增 public-path.js:
1 2 3 if (window .__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window .__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
设置 history 模式路由的 base:
Micro App Micro app是京东开发的微前端app框架,2021年7月开源
qiankun 还是有一些缺点的:
项目的侵入性依然很强。
qiankun 在沙箱方面依然有不少坑。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的特点:
使用简单。 将功能封装到 WebComponent 中
零依赖。 无依赖、更高的扩展性
兼容所有框架 技术栈无关
安装micro app
1 yarn add @micro-zoe/micro-app
在入口处引入
1 2 3 4 import microApp from '@micro-zoe/micro-app' microApp.start()
分配一个路由给子应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { BrowserRouter, Switch, Route } from 'react-router-dom' import MyPage from './my-page' export default function AppRoute ( ) { return ( <BrowserRouter> <Switch> <Route path='/my-page' > <MyPage /> </Route> </ Switch> </BrowserRouter> ) }
在应用中嵌入子应用
1 2 3 4 5 6 7 8 9 10 11 12 export function MyPage ( ) { return ( <div> <h1>子应用</h1> / / name(必传):应用名称 / / url(必传):应用地址,会被自动补全为http:/ /localhost:3000/i ndex.html <micro-app name='app1' url='http://localhost:3000/' baseroute='/my-page' ></micro-app> </ div> ) }
子应用设置基础路由
1 2 3 4 5 6 7 8 9 10 11 import { BrowserRouter, Switch, Route } from 'react-router-dom' export default function AppRoute ( ) { return ( <BrowserRouter basename={window .__MICRO_APP_BASE_ROUTE__ || '/' }> ... </BrowserRouter> ) }
在webpack-dev-server的headers中设置跨域支持
1 2 3 4 5 devServer: { headers: { 'Access-Control-Allow-Origin' : '*' , } },
数据通信 数据通信也是 micro-app 的一大亮点,在使用层面上比 qiankun 的 EventBus 要好用一点
父传子
在基座应用的容器组件里添加一个 microApp.setData 就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const ReactMicroApp = () => { const [microAppState, setMicroAppState] = useState(microApp.getData('react-app' )); const sendReactAppData = () => { 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来获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 了,而且入参必须也为对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 进来,且不能把注释给干掉!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import microApp from '@micro-zoe/micro-app' import {useState} from "react" ;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> ) }
渲染模式
默认模式 :每次都按顺序执行一次 JS,具有幂等性
umd 模式 :只在初次渲染时执行所有 JS,对于需要频繁切换微应用的项目可以提高其性能
共享静态资源 使用 globalAssets 共享资源:
1 2 3 4 5 6 7 8 9 10 import microApp from '@micro-zoe/micro-app' microApp.start({ globalAssets: { js: ['js地址1' , 'js地址2' , ...], css: ['css地址1' , 'css地址2' , ...], } })
或者使用global属性
1 2 <link rel ="stylesheet" href ="xx.css" global > <script src ="xx.js" global > </script >
还有对资源的过滤
1 2 3 <link rel ="stylesheet" href ="xx.css" exclude > <script src ="xx.js" exclude > </script > <style exclude > </style >
生命周期 micro app提供了不同在微应用的生命周期可以执行不同的命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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('渲染出错' )} /> ) }
应用之间跳转
window.history
通过数据通信控制跳转
传递路由实例方法
隔离
JS 方面使用 Proxy 拦截了用户全局操作的行为,防止对 window 的访问和修改,避免全局变量污染。
CSS 方面有两种隔离:
元素隔离方面,micro-app 模拟实现了类似 ShadowDom 的功能,元素不会逃离 <micro-app> 元素边界,子应用只能对自身的元素进行增、删、改、查的操作。
其他功能 keepAlive
保持微应用的状态 Keep-Alive
1 <micro-app name ='xx' url ='xx' keep-alive > </micro-app >
prefetch
micro app vs qiankun qiankun侵入式较强
servelt Svelte 是一个给我同样感觉的JavaScript框架。与React,Vue,Angular和其他框架相比,使用Svelte构建的应用程序是已编译 ,不必为网站访问者提供框架和库代码,因此,所有体验的结果都更加流畅,占用的带宽更少,并且一切感觉更快,更轻便。
与Hugo一样,Hugo会在部署时消失并生成纯HTML,Svelte也会消失,而您所获得的只是纯JavaScript。
Svelte 用于构建快速的 Web 应用。
Svelte 类似 React 和 Vue,都致力让你轻而易举地构建灵活的可交互的用户界面。
与它们截然不同的是:Svelte 在构建时 将你的代码转为更优的 JavaScript,而不是在 运行时 才解释执行你的代码。
这预示着你无需付出框架本身的性能成本,且首次加载也无额外性能损耗。
你可以使用 Svelte 编写整个应用,也可以用来逐步重构现有代码,整半皆可;还可以只输出单个独立组件(无需强制附带框架自身)并将其用于任意框架中,可谓百面玲珑。
响应式代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <script > let count = 0 ; $: doubled = count * 2; $: 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语句
1 2 3 4 5 6 7 8 9 10 11 12 13 <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语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <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 >
异步数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <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} <p > The number is {number}</p > {:catch error} <p style ="color: red" > {error.message}</p > {/await}
事件 只执行一次
1 2 3 4 5 6 7 <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)。
为了展示组件事件,需要先写一个组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 ``` ### 生命周期 `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 会禁止你使用 `$` 作为你声明的变量的前缀。 ```html <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。
1 2 3 4 5 6 7 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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中的状态
1 2 3 4 5 6 7 8 9 <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 这套观察者-订阅的理念且值得借鉴。
1 2 3 4 5 6 7 8 9 <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 文件中供反复应用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <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>元素当内容为空的缺省 情况应该默认显示什么内容:
我们包含了一个默认插槽*,用于直接代替组件的子节点。不过有些时候,你需要对放置的内容有更多的控制能力,我们可以使用 *命名插槽 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 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插槽是否存在内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <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>,它表示当前组件,允许在某些情况递归自身,这对于展示文件夹树之类的视图很有用,其中文件夹可以包含 其他 文件夹。
1 2 3 4 5 {#each folders as f} <li > <svelte:self {...f } /> </li > {/each}
组件可以通过<svelte:component>进行 “变身”,这样可以省掉一系列的if块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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对象的事件,例如鼠标移动的事件:
1 2 3 4 5 6 7 <script > let x, y </script > <svelte:window on:mousemove ={ e => ({x, y} = e) } /><p > 鼠标位置:{x}, {y}</p >
我们还可以绑定一些window的属性,某些业务我们可能需要监测浏览器窗口的大小,原生的写法
是通过监听 window.onresize 来实现的。
在 Svelte 中,它更为简单,并且支持绑定
1 2 3 4 5 6 7 <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上触发。
1 2 3 4 5 6 7 8 9 10 <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> 等等元素:
1 2 3 <svelte:head > <link rel ="stylesheet" href ="./global.css" > </svelte:head >
在服务器端渲染 (SSR) 模式下,<svelte:head> 的内容与 HTML 的其余部分是分别返回的。
<svelte:fragment>实现类似插槽的功能。
1 2 3 4 5 6 7 8 9 10 <script > import Material from './Material.svelte' </script > <Material > <svelte:fragment slot ="bottom" > <li > 高筋面粉</li > <li > 低筋面粉</li > </svelte:fragment > </Material >
<svelte:options>允许你配置编译器的选项。比如希望为组件的变量生成属性访问器(默认不生成)。生成属性访问器的目的,是提供给使用端更容易去访问的,这通常是你编写了一个 Svelte 组件,想同时在 React 或者 Vue 等地方上用时才需要这个选项。
1 2 3 4 5 6 7 <script > export let x = 'hello' </script > <svelte:options accessors /> <p > {x}</p >
下列是允许在此处设置可选用的值:
immutable={true}:你确信不会使用可变数据,因此编译器可以执行简单的引用相等性检查来确定值是否已更改。
immutable={false}:默认值,Svelte 将按默认的方式处理可变对象的更改
accessors={true}:为组件的 props 添加 getter 和 setter
accessors={false}:默认值
namespace="...":将使用此组件的命名空间,最常见的就是 "svg"
tag="...":将组件编译为自定义元素时,指定的标签名称
Svelte Kit 正如 React 之于 Next ,Svelte 对应的 Web 框架便是 SvelteKit。
它是一个用 Svelte 构建应用的框架,包括服务器端渲染(SSR)、路由、针对 JS 和 CSS 的代码分割,以及针对不同 Serverless 平台生成不同代码的适配器等等。