图形用户界面( Graphical User Interface ) 简称 GUI。
说简单点,就是用来在 JS 项目中调试配置不同参数的一套 UI 组件。
日常开发中,场景可能用到的配置参数通常是:数字、字符串、颜色、布尔值。
在 JS 中,一个知名的 GUI 类库为:dat.gui。
dat.gui 官网地址: https://github.com/dataarts/dat.gui
因为 dat.gui 是针对原生 JS 的,若在 React 的 JSX 语法中使用略微繁琐。
react-dat-gui 这个库应运而生,react-dat-gui 是基于 dat.gui 封装而成。
官方地址:https://github.com/claus/react-dat-gui
我们先简单讲一下 dat.gui.js ,后重点讲解一下 react-dat-gui。
yarn add dat.gui
//npm i dat.gui --save
dat.gui 默认并不包含 .d.ts 文件,所以如果想在 TypeScript 中获得语法提示,则还需额外安装对应的包
yarn add @types/dat.gui --dev
//npm i @types/data.gui --save-dev
import { GUI } from 'dat.gui'
const gui = new GUI()
或者
import dat from 'dat.gui'
const gui = new dat.GUI()
具体如何使用可参考官网,或API文档:https://github.com/dataarts/dat.gui/blob/master/API.md
yarn add react-dat-gui
//npm i react-dat-gui --save
react-dat-gui 已包含 .d.ts 文件,因此可以直接在 TypeScript 项目中获得语法提示
react-dat-gui 还自带有样式文件,我们在使用时只需引入即可生效
react-dat-gui 一共 2 部分:
- 最外层的 <DatGUI > 标签
- 里面具体的每一项参数,例如 <DatBoolean >、<DatString > 等
首先需要引入 DatGUI 和 全部的 css 样式
import DatGUI from 'react-dat-gui'
import 'react-dat-gui/dist/index.css'
然后可以使用 <DatGUI /> 来创建 GUI 的最外层框架、每一项参数都是 <DatGUI /> 的子项。
<DatGUI>
...
</DatGUI>
<DatGUI > 有 2 个必填属性:
- data={xxx} ,要绑定的数据对象
- onUpdate={(newDate) => { ... }} ,当用户修改某个参数后,触发更新处理函数
选项类型 | 选项的作用 |
---|---|
DatBoolean | 调整 布尔值 |
DatString | 调整 字符串 |
DatNumber | 调整 数字 |
DatColor | 调整 颜色 |
DatSelect | 调整 下拉框 |
DatButton | 创建一个按钮 |
DatFolder | 创建一个可折叠的菜单面板 |
DatPresets | 创建一个预设 |
从上面可以看出,参数选项一共有 2 种类型:
- 用于调整参数的类型:DatBoolean、DatString、DatNumber、DatColor、DatSelect
- 用于创建某些特殊对象的类型:DatButton、DatFoler、DatPresets
-
label (可选属性):选项对应的标签名,值为 字符串
-
className (可选属性):选项对应的样式名,值为 字符串
注意自己增加的样式会覆盖掉组件默认的样式,可以给自定义样式添加 !important,以提高其优先级。
-
style (可选属性):选项对应的 style 样式
针对这 5 个 选项类型:DatBoolean、DatString、DatNumber、DatColor、DatSelect
- path (必填属性):绑定数据中的字段名,值为 字符串
DatButton、DatFoler、DatPresets 这 3 个选项类型不需要 path 属性
DatBoolean、DatString、DatColor 只需要 path、label 属性即可。
DatNumber:
- min:必填项,最小值为多少
- max:必填项,最大值为多少
- step:必填项,数字增幅单位.
示例:
<DatNumber path='num' label='number' min={0} max={10} step={1} />
以上配置中,将数字的范围控制在 0 - 10 之间,且每次增加或减少的幅度为 1
DatSelect:
- options:非必填项,下拉值列表对应的数组
示例:
<DatSelect path='select' options={['red', 'green', 'blue']} />
特别注意:以上示例中数组元素为字符串,事实上你可以为数字,例如
<DatSelect path='select' options={[18, 34, 1986]} />
但是,最终 DatGUI 会将选项的值转化为 string 类型。
尽管 options 设置时,数组中元素为数字,但是最终会被转化为字符串
DatButton:
- onClick:必填项,点击按钮后的处理函数
示例:
const handleClick = () => {
console.log('you clicked me')
}
<DatButton onClick={handleClick} label='button' />
DatFolder:
DatFolder 用来创建一个可折叠的菜单面板。
你也可以理解成是一个可折叠的目录(文件夹)
- title:必填项,菜单面板的标题名
- closed:选填项,布尔值,默认值为 true,表示默认是否折叠菜单面板。
菜单子项内容:
child:必填项,是一个数组,包含菜单面板中的子项
是必填项,是数组,但是也可以是空数组
严格来说 child 并不是属性,而是 DatFolder 的内容。
示例:
<DatFolder title='other' closed={true} >
<DatString path='title' label='title' />
<DatNumber path='num' label='number' min={0} max={10} step={1} />
</DatFolder>
上面示例中,<DatString > 、<DatNumber > 就相当于 DatFolder 的 child。
DatPresets:
perset 单词的翻译为:预制、预设
DatPresets 是用来预设 N 组 参数配置。
-
onUpdate:必填项,当用户操作,选择了新的一组预设时,对应的处理函数
-
options:必填项,值为数组,数组中的每个元素的每个值键对组成一组预设数据。
"数组中的每个元素的每个值键对组成一组预设数据" 这句话略微绕口
实际中 DatPresets 会将 options 数组中的每个对象最外层的值键对进行拆分
键名为预设方案的名称,键值为预设方案的值
-
DatPresets 会把默认绑定的 data 作为第 1 项,且名字默认为 “Default”
以上针对 options 的解释比较绕口,看一下示例就容易明白。
示例:
const [dat, setDat] = useState<DatType>({ boo: false, title: 'hello', num: 2, color: '#FFFFFF', select: 'red' })
const presetOptions = [
{
second: {
boo: false, title: 'hello...', num: 2, color: '#FFFFFF', select: 'red'
},
third: {
boo: false, title: 'hi...', num: 5, color: '#666666', select: 'yellow'
}
},
{
fourth: {
boo: true, title: 'hi...', num: `5`, color: '#000000', select: 'blue'
}
}
]
...
<DatGUI data={dat} onUpdate={handleUpdate} className='dat-gui'>
<DatPresets onUpdate={handlePresetsUpdate} label='presets' options={presetOptions} />
</DatGUI>
仔细观察 presetOptions 的数据结构:
const presetOptions = [
{
预设方案2,
预设方案3
},
{
预设方案4
}
]
虽然 预设方案2 和 预设方案 3 在同一个元素对象中,但是 DatPrestes 会对数组中的元素进行解构。
最终所呈现的预设方案下拉框中,会显示:
- Default
- second
- third
- fourth
我在学习 react-dat-gui 时,发现对应的 index.d.ts 中 DatPresetsProps 的定义是错误的。
这是官方定义的:
export interface DatPresetsProps extends DatUnchangableFieldProps { onUpdate: (data: any) => any; options: { presetName?: string; data?: any; // Initial data preset: any; // Your preset }; }
这是我修改之后的:
export interface DatPresetsProps extends DatUnchangableFieldProps { onUpdate: (data: any) => any; options: { [k: string]: any }[]; }
我已向官方仓库提交了 PR,希望能被合并。
默认引入的 index.css 包含了 react-dat-gui 所需的全部样式,包括各种类型参数对应的样式。
这是官方提供的一套 css 样式,你也可以自定义样式来覆盖官方默认的样式。
官方默认的样式已经很简洁优雅了,一般情况下我们没有必要修改。
假设你就是想修改,那么这里贴出一部分官方默认的 css 样式供你参考:
.react-dat-gui {
position: fixed;
right: 16px;
top: 0;
width: 280px;
font-size: 12px;
font-family: Lucida Grande,sans-serif;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: transparent;
}
自定义样式:
.dat-gui {
top: 16px !important;
width: 360px !important;
font-size: 18px !important;
}
自定义样式的属性添加了 !important ,所以肯定会覆盖原有的样式
添加自定义样式:
<DatGUI data={dat} onUpdate={handleUpdate} className='dat-gui'>
...
</DatGUI>
补充说明:
每一项 GUI 参数 布局如下:
- 左侧为 参数 label
- 右侧为 具体参数设置项
react-dat-gui 官方为我们提供了一个 可选属性:labelWidth,属性值为以像素 px 为单位的数字,默认值为 40。
在 <DatGUI > 标签中添加该属性,会对所有子项起作用。
我们可以通过添加修改该属性值,来调整 左侧参数 label 的宽度。
例如下面代码中,设置 label 的宽度为 200px:
<DatGUI labelWidth={200} > ... </DaGUI>
所有的子项的 label 宽度均会被设置为 200px
注意:当修改左侧 label 的宽度后,右侧 具体参数设置项 会自动占据父级剩余的全部宽度。
示例使用的是:React + TypeScript + React-dat-gui
import { useState } from 'react'
import DatGUI, { DatBoolean, DatButton, DatColor, DatFolder, DatNumber, DatPresets, DatSelect, DatString } from 'react-dat-gui'
import 'react-dat-gui/dist/index.css'
type DatType = {
boo: boolean,
title: string,
num: number,
color: string,
select: string
}
const HelloGUI = () => {
const [dat, setDat] = useState<DatType>({ boo: false, title: 'hello', num: 2, color: '#FFFFFF', select: 'red' })
const presetOptions = [
{
second: {
boo: false, title: 'hello...', num: 2, color: '#FFFFFF', select: 'red'
},
third: {
boo: false, title: 'hi...', num: 5, color: '#666666', select: 'yellow'
}
},
{
fourth: {
boo: true, title: 'hi...', num: `5`, color: '#000000', select: 'blue'
}
}
]
const handleUpdate = (newData: any) => {
setDat(newData)
console.log(newData)
}
const handleClick = () => {
console.log('you clicked me')
}
const handlePresetsUpdate = (newData: any) => {
setDat(newData)
console.log(newData)
}
return (
<DatGUI data={dat} onUpdate={handleUpdate} className='dat-gui'>
<DatPresets onUpdate={handlePresetsUpdate} label='presets'
options={presetOptions} />
<DatBoolean path='boo' label='Boo?' />
<DatString path='title' label='title' />
<DatNumber path='num' label='number' min={0} max={10} step={1} />
<DatColor path='color' label='color' />
<DatSelect path='select' options={['red', 'green', 'blue']} />
<DatButton onClick={handleClick} label='button' />
<DatFolder title='other' closed={true} >
<DatString path='title' label='title' />
<DatNumber path='num' label='number' min={0} max={10} step={1} />
</DatFolder>
</DatGUI>
)
}
export default HelloGUI