Provide/inject API
provide/inject API 的灵感来自 Svelte 的 context API 和 Vue 的 provide/inject API。其目的是在不显式作为 props 传递的情况下,与组件树共享状态。
大多数情况下,一组相关的组件会使用 Provide/inject API 来彼此透明地共享/访问状态。
渲染一个带标记点的地图
让我们复现 Svelte context API 教程中的地图示例,并学习如何使用 Edge 构建它。我们还会使用 Alpine.js 配合 Mapbox JavaScript SDK 来渲染地图。
本示例的最终源码可在 Github 上获取。你需要为你的 Mapbox 账户准备一个 access token 才能渲染地图。
期望的 API
以下是期望的 API 示例。其中 map 组件负责使用 Mapbox JavaScript SDK 渲染地图,map.marker 组件在地图上显示一个标记点。
@map({ center: [-84, 35], zoom: 3 })
@!map.marker({ lat: 37.8225, lon: -122.0024, label: 'Edge Body Shaping' })
@!map.marker({ lat: 33.8981, lon: -118.4169, label: 'Edge Barbershop & Essentials' })
@!map.marker({ lat: 29.723, lon: -95.4189, label: 'Edge Waxing Studio' })
@!map.marker({ lat: 28.3378, lon: -81.3966, label: 'Edge 30 Nutritional Consultants' })
@!map.marker({ lat: 40.6483, lon: -74.0237, label: 'Edge Brands LLC' })
@end
Map 组件
由于地图是使用 map 组件渲染的,它需要在渲染之前访问所有的标记点。这正是 provider/inject API 发挥作用的地方。
map组件会与它的子组件共享/注入一个对象。map.marker(map 组件的子组件)会访问这个共享对象,并向 markers 数组推送一个新的标记点。- 最后,
map组件会将所有数据传递给一个 Alpine 组件,并使用 Mapbox SDK 渲染地图。
{{-- 用一个包含地图选项的局部变量 --}}
@let(map = {
center,
zoom,
markers: [],
})
{{-- 与子组件共享 map 对象 --}}
@inject({ map })
{{-- 执行子组件,但不渲染它们 --}}
@eval(await $slots.main())
{{-- 渲染一个 div 并将其绑定到一个 Alpine 组件 --}}
<div x-data="map({{ js.stringify(map) }})" id="map"></div>
Marker 组件
map.marker 组件的职责是向 map.markers 数组推送标记点。它可以通过 $context 变量访问被注入的状态。
{{-- 确保 marker 组件是 map 组件的子组件 --}}
@if(!$context.map)
@newError(
'The map.marker component should be nested within the map component',
$caller.filename,
$caller.line,
$caller.col
)
@end
{{-- 将 props 作为一个标记推送到 map --}}
@eval($context.map.markers.push({ lat, lon, label }))
这就是我们实现期望 API 所需的全部内容。
Alpine 组件
最后,让我们定义一个 Alpine 组件,它将使用 Mapbox SDK 在客户端渲染地图。
document.addEventListener('alpine:init', () => {
window.Alpine.data('map', function (data) {
return {
createMap() {
mapboxgl.accessToken = '<!-- YOUR ACCESS TOKEN -->'
return new mapboxgl.Map({
container: this.$root,
style: 'mapbox://styles/mapbox/streets-v9',
center: data.center,
zoom: data.zoom
})
},
addMarker(map, markerData) {
const popup = new mapboxgl.Popup({ offset: 25 }).setText(markerData.label);
new mapboxgl.Marker().setLngLat([markerData.lon, markerData.lat]).setPopup(popup).addTo(map);
},
init() {
const map = this.createMap()
if (data.markers && Array.isArray(data.markers)) {
data.markers.forEach((marker) => {
this.addMarker(map, marker)
})
}
}
}
})
})
最终结果
本示例的最终源码可在 Github 上获取。你需要为你的 Mapbox 账户准备一个 access token 才能渲染地图。
@inject 装饰器
你可以使用 @inject 标签与组件的子组件共享/注入状态。请确保在渲染或求值组件插槽之前调用 @inject 标签。
@let(sharedState = {})
@inject(sharedState)
$context 变量
组件子组件可以通过 $context 变量访问共享的状态。
@map()
{{ inspect($context) }}
@end