核心概念
本主题解释了场景的核心概念以及如何在创建自己的场景中使用它们。
场景
场景是一组称为 场景对象 的对象集合。这些对象代表场景的不同方面:数据、时间范围、变量、布局和可视化。场景对象共同构成一个 对象树
场景允许您对对象进行分组和嵌套。如数据、时间范围或变量等可以添加到树中的任何对象,使它们对该对象及其所有后代对象可用。正因为如此,场景允许您创建具有多个时间范围、可以共享和转换的查询或嵌套变量的仪表板。
@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
包装实例。这确保对象的原始父对象不会被改变,同时允许你在另一个场景对象的状态中存储对实例的引用。