核心概念
本主题解释了场景的核心概念以及如何在创建自己的场景时使用它们。
场景
场景是称为场景对象的对象集合。这些对象代表场景的不同方面:数据、时间范围、变量、布局和可视化。场景对象共同构成一个对象树
场景允许您对对象进行分组和嵌套。数据、时间范围或变量等内容可以添加到树中的任何对象,使其对该对象及其所有后代对象可用。因此,场景允许您创建具有多个时间范围的仪表板、可以共享和转换的查询或嵌套变量。
@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()
订阅对象的 state 将使组件对 state 更改具有反应性。对场景对象 state 的每次更改都是不可变的,并且会导致组件重新渲染。
修改状态
要更改场景对象的状态,请使用每个场景对象都具有的 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
来包装实例。这确保了对象的原始父级不会更改,同时允许您将对该实例的引用存储在另一个场景对象的 state 中。