核心概念
本主题解释了 Scenes 的核心概念以及如何在创建自己的场景时使用它们。
场景
场景是对象的集合,称为场景对象。这些对象代表场景的不同方面:数据、时间范围、变量、布局和可视化。场景对象共同形成*对象树*
场景允许您对对象进行分组和嵌套。数据、时间范围或变量等内容可以添加到树中的任何对象,从而使其可供该对象及其所有子对象使用。因此,场景允许您创建具有多个时间范围、可以共享和转换的查询或嵌套变量的仪表盘。
@grafana/scenes 附带多个对象,例如SceneQueryRunner
、SceneFlexLayout
、VizPanel
等,用于解决常见问题。但是,您也可以创建自己的场景对象来扩展功能。
场景对象
场景由称为场景对象的原子对象构建而成。场景对象定义包含
- 状态 - 扩展
SceneObjectState
的接口。
import { SceneObjectState } from '@grafana/scenes';
// 1. Create interface that describes state of the scene object
interface CounterState extends SceneObjectState {
count: number;
}
- 模型 - 扩展
SceneObjectBase
类的类。模型包含场景对象逻辑。
import { SceneObjectBase } from '@grafana/scenes';
export class Counter extends SceneObjectBase<CounterState> {
public static Component = CounterRenderer;
public constructor() {
super({
count: 0,
});
}
public onIncrement = () => {
this.setState({
count: this.state.count + 1,
});
};
}
- React 组件 - 用于渲染场景对象。
import React from 'react';
import { SceneComponentProps } from '@grafana/scenes';
function CounterRenderer({ model }: SceneComponentProps<Counter>) {
const { count } = model.useState();
return (
<div>
<div>Counter: {count}</div>
<button onClick={model.onIncrement}>Increase</button>
</div>
);
}
状态
场景对象可以具有关联状态。对象状态的形态通过一个必须扩展 SceneObjectState
接口的接口来表示
interface CounterState extends SceneObjectState {
count: number;
}
订阅状态更改
组件可以通过使用渲染时接收到的 model
属性来读取场景对象的状态。要订阅状态更改,请调用 model.useState
方法
function CounterRenderer({ model }: SceneComponentProps<Counter>) {
const { count } = model.useState();
// ...
}
使用 model.useState()
订阅对象的状态将使组件对状态更改具有响应性。对场景对象状态的每一次更改都是不可变的,并将导致组件重新渲染。
修改状态
要更改场景对象的状态,请使用每个场景对象都具有的 setState
方法。这可以直接从组件中完成
function CounterRenderer({ model }: SceneComponentProps<Counter>) {
const { count } = model.useState();
const onIncrement = () => model.setState({ count: count + 1 });
// ...
}
这也可以从场景对象类中完成
export class Counter extends SceneObjectBase<CounterState> {
// ...
public onIncrement = () => {
this.setState({
count: this.state.count + 1,
});
};
}
function CounterRenderer({ model }: SceneComponentProps<Counter>) {
const { count } = model.useState();
return (
<div>
<div>Counter: {count}</div>
<button onClick={model.onIncrement}>Increase</button>
</div>
);
}
我们建议您在场景对象而不是组件中实现修改状态的方法,以将模型复杂性与组件分离。
数据和时间范围
使用 $data
属性将来自 Grafana 数据源的数据添加到场景。查询使用 SceneQueryRunner
场景对象配置
import { SceneQueryRunner } from '@grafana/scenes';
const queryRunner = new SceneQueryRunner({
datasource: {
type: 'prometheus',
uid: '<PROVIDE_GRAFANA_DS_UID>',
},
queries: [
{
refId: 'A',
expr: 'rate(prometheus_http_requests_total{}[5m])',
},
],
});
您的 Grafana 实例必须配置指定的数据源。
要使 SceneQueryRunner
工作,您必须为场景添加时间范围。每个场景对象都有一个 $timeRange
属性,可以将 SceneTimeRange
场景对象添加到该属性。要在上一个示例中创建的查询运行器指定时间范围,请在传递给构造函数的对象中添加 $timeRange
属性
import { SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
const queryRunner = new SceneQueryRunner({
datasource: {
type: 'prometheus',
uid: '<PROVIDE_GRAFANA_DS_UID>',
},
queries: [
{
refId: 'A',
expr: 'rate(prometheus_http_requests_total{}[5m])',
},
],
$timeRange: new SceneTimeRange({ from: 'now-5m', to: 'now' }),
});
将创建的 queryRunner
添加到您的场景。场景中的每个对象现在都可以访问提供的数据
const scene = new EmbeddedScene({
$data: queryRunner,
body: ...
})
每个场景对象都有一个可以配置的 $data
和 $timeRange
属性。由于场景是一个对象树,因此通过 SceneQueryRunner
和 SceneTimeRange
分别配置的数据和时间范围可供添加它们的那些对象以及所有子对象使用。
在下面的示例中,每个 VizPanel
使用不同的数据。“面板 A”使用在 EmbeddedScene
上定义的数据,而“面板 B”配置了自己的数据和时间范围
// Scene data, used by Panel A
const queryRunner1 = new SceneQueryRunner({
datasource: {
type: 'prometheus',
uid: '<PROVIDE_GRAFANA_DS_UID>',
},
queries: [
{
refId: 'A',
expr: 'rate(prometheus_http_requests_total{}[5m])',
},
],
});
// Panel B data
const queryRunner2 = new SceneQueryRunner({
datasource: {
type: 'prometheus',
uid: '<PROVIDE_GRAFANA_DS_UID>',
},
queries: [
{
refId: 'A',
expr: 'avg by (job, instance, mode) (rate(node_cpu_seconds_total[5m]))',
},
],
});
const scene = new EmbeddedScene({
$data: queryRunner1,
// Global time range. queryRunner1 will use this time range.
$timeRange: new SceneTimeRange({ from: 'now-5m', to: 'now' }),
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
width: '50%',
height: 300,
body: PanelBuilders.timeseries().setTitle('Panel using global time range').build(),
}),
new SceneFlexItem({
width: '50%',
height: 300,
body: PanelBuilders.timeseries()
.setTitle('Panel using local time range')
// Time range defined on VizPanel object. queryRunner2 will use this time range.
.setTimeRange(new SceneTimeRange({ from: 'now-6h', to: 'now' }))
.setData(queryRunner2)
.build(),
}),
],
}),
});
对 SceneObject 的引用和父引用
非常重要的一点是,不要在多个不同场景或同一场景内的不同位置重用同一个场景对象实例。当场景对象成为另一个场景对象状态的一部分时,其父对象会自动设置。因此,如果您想在多个场景对象的状态中使用同一个场景对象实例,您有两种选择。
- 克隆源场景对象。这将创建一个独立的实例,与源对象没有连接。
- 使用
SceneObjectRef
包装实例。这可以确保对象的原始父对象不被更改,同时允许您在另一个场景对象的状态中存储对该实例的引用。