Plus 组件

Json编辑器

用于展示和编辑 JSON 数据

使用场景

JsonViewer 组件可用于 JSON 数据的展示与编辑。Semi 重点参考了 VS Code的 text-buffer 数据结构设计思路,复用了部分 utils与数据类型定义(Token解析,语言服务等),结合我们的功能/样式定制需求,实现了 JsonViewer 组件, 视觉上会与 Semi Design Next 体系内的其他组件更协调,对于特定数据类型的定制化渲染定制会更方便。
相比于直接使用 MonacoEditor,Semi JsonViewer 在工程化构建上做了额外处理,使用更为简单,无需关注 Webpack插件、worker loader等复杂的配置。
同时由于我们仅关注 Json 数据格式,更轻量化,在开箱即用的同时,拥有更小的体积(📦 -96%) ,更极致的加载速度(🚀 -53.5%) ,更少的内存占用(⬇️ 71.6%)。
对于五百万行及以下的数据,均可以做到1s内完成数据加载与解析。详细的对比数据可查阅 Performance 章节

  • 如果你仅需要对 Json 做预览/编辑,无需对更复杂的其他编程语言作修改,我们建议你选用 JsonViewer
  • 如果你还需要处理其他格式的数据/代码文件,完整的代码编辑器能力(语法高亮、代码补全、错误提示、复杂编辑等)是刚需,构建产物体积不是关注重点,我们建议你选用 Monaco Editor

代码演示

如何引入

import { JsonViewer } from '@fibrous/semi-ui-react';

基本用法

JsonViewer 的基本用法。传入 height 和 width 参数,设置组件的高度和宽度和初始值。通过 value 传入 Json 字符串
编辑内容变化时触发 onChange,参数为编辑器当前文本。value 变为与编辑器当前内容不同的值时才会重建编辑器,在 onChange 中把文本写回 value(setState)不会丢失光标;value 不变时编辑器保留用户的输入。若需要获取组件的值,可以通过 ref 获取,具体可参考 Methods。

代码
import { JsonViewer } from '@fibrous/semi-ui-react';
const data = `{
    "name": "Semi",
    "version": "0.0.0"
}`;
export default function JsonViewerDemo() {
    return (
        <div style={{ marginBottom: 16 }}>
            <JsonViewer height={100} width={700} value={data} />
        </div>
    );
}

设置行高

配置 options 的 lineHeight 参数,设置固定行高(单位:px, 默认 20)。

代码
import { JsonViewer } from '@fibrous/semi-ui-react';
const data = `{
    "name": "Semi",
    "version": "0.0.0"
}`;
export default function JsonViewerDemo() {
    return (
        <div>
            <div style={{ marginBottom: 12, overflow: 'hidden' }}>
                <JsonViewer height={100} width={700} value={data} options={{ lineHeight: 20 }} />
            </div>
            <div style={{ marginBottom: 12, overflow: 'hidden' }}>
                <JsonViewer height={120} width={700} value={data} options={{ lineHeight: 24 }} />
            </div>
            <div style={{ marginBottom: 12, overflow: 'hidden' }}>
                <JsonViewer height={120} width={700} value={data} options={{ lineHeight: 26 }} />
            </div>
        </div>
    );
}

自动换行

配置 options 的 autoWrap 参数,设置为 true 时,组件会根据内容长度自动换行。

代码
import { JsonViewer } from '@fibrous/semi-ui-react';
const data = `{
    "name": "Semi",
    "version": "0.0.0",
    "description": "Semi Design is a design system that defines a set of mid_back design and front_end basic components."
}`;
export default function JsonViewerDemo() {
    return (
        <div style={{ marginBottom: 16 }}>
            <JsonViewer height={120} width={700} value={data} options={{ autoWrap: true }} />
        </div>
    );
}

格式化配置

配置 options 的 formatOptions 参数,设置组件的格式化配置。

  • tabSize: number,设置缩进大小为4,表示每级缩进 4 个空格
  • insertSpaces: boolean,true 表示使用空格进行缩进,false 表示使用制表符(Tab)
  • eol: string,设置换行符,可以是\n,\r\n,
代码
import { useRef } from 'react';
import { JsonViewer, Button, type JsonViewerHandle } from '@fibrous/semi-ui-react';
const data = `{
  "name": "Semi",
  "version": "0.0.0"
}`;
export default function JsonViewerDemo() {
    const jsonviewerRef = useRef<JsonViewerHandle>(null);
    return (
        <div>
            <Button onClick={() => console.log(jsonviewerRef.current?.format())}>格式化</Button>
            <div style={{ marginBottom: 16, marginTop: 16 }}>
                <JsonViewer ref={jsonviewerRef} height={100} width={700} value={data} options={{ formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' } }} />
            </div>
        </div>
    );
}

自定义渲染规则

通过配置 options.customRenderRule 参数,你可以自定义 JSON 内容的渲染方式(注意:仅在只读模式下生效)。

行为说明

customRenderRule 在计算 path 时更精确:同一条键值对的 key token 与 value token 会拥有相同的 path(即都对应到该属性所在的路径,例如 root.<key>)。

因此,像 path === 'root.<key>' 这类仅依赖 path 的规则,可能同时命中 key 和 value,导致与后续 value 匹配规则产生“覆盖/优先级”差异。

若你希望只匹配 key 或只匹配 value,请使用函数匹配的第三个参数 tokenType:

const targetPath = 'root.<key>';
// 仅匹配 key
match: (_value, path, tokenType) => tokenType === 'key' && path === targetPath;
// 仅匹配 value
match: (value, path, tokenType) => tokenType === 'value' && path === targetPath;

customRenderRule 是一个规则数组,每条规则包含两个属性:

  • match: 匹配条件,可以是以下三种类型之一:
    • 字符串:精确匹配
    • 正则表达式:按正则匹配
    • 函数:自定义匹配逻辑,函数签名为 (value: string | number | boolean | null, path: string, tokenType: 'key' | 'value') => boolean
      • value: 待匹配的值(JSON 键或值)。当 match 为函数时,value 会尽量传入解析后的原始类型(number / boolean / null / string),因此可以使用 === 进行严格匹配;当 match 为字符串或正则时,仍基于文本内容匹配(字符串类型会去除两侧引号)
      • path: 当前匹配到的路径,格式为 root.key1.key2.key3[0].key4
      • tokenType: 当前 token 的类型,'key' 表示 JSON 键名,'value' 表示 JSON 值。可用于区分同名键和值的匹配
  • render: 自定义渲染函数,函数签名为 (content: string) => React.ReactNode
    • content: 匹配到的内容。如果是字符串类型的值,将包含双引号(如 "name","Semi")
    • 返回 null、undefined 或 false 时不做自定义渲染,保留 token 原文,因此可以写成 (content) => cond && <X />
代码
import { JsonViewer, Rating, Popover, Tag, Image, type JsonViewerCustomRenderRule as CustomRenderRule } from '@fibrous/semi-ui-react';
const data = `{
  "name": "Semi",
  "version": "2.7.4",
  "rating": 5,
  "tags": ["design", "react", "ui"],
  "image": "/assets/root-web-sites/abstract.jpg"
}`;
export default function JsonViewerDemo() {
    const customRenderRule: CustomRenderRule[] = [
        {
            match: 'Semi',
            render: (content) => {
                return (
                    <Popover showArrow content="我是用户自定义的渲染" trigger="hover">
                        <span>{content}</span>
                    </Popover>
                );
            },
        },
        {
            match: (value) => value == 5,
            render: (content) => {
                return <Rating defaultValue={Number(content)} size={10} disabled />;
            },
        },
        {
            match: (_value, path) => path === 'root.tags[0]' || path === 'root.tags[1]' || path === 'root.tags[2]',
            render: (content) => {
                return (
                    <Tag size="small" shape="circle">
                        {content}
                    </Tag>
                );
            },
        },
        {
            match: new RegExp('^http'),
            render: (content) => {
                // content is the raw string token with its quotes; without them it is a valid URL
                return (
                    <Popover showArrow content={<Image width={100} height={100} src={content.replace(/^"|"$/g, '')} />} trigger="hover">
                        <span>{content}</span>
                    </Popover>
                );
            },
        },
    ];
    return (
        <div>
            <div style={{ marginBottom: 16, marginTop: 16 }}>
                <JsonViewer
                    height={200}
                    width={600}
                    value={data}
                    showSearch={false}
                    options={{
                        formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' },
                        customRenderRule,
                        readOnly: true,
                        autoWrap: true,
                    }}
                />
            </div>
        </div>
    );
}

自定义搜索按钮

通过 renderSearchButton 属性,你可以自定义搜索按钮的渲染方式,实现固定位置、自定义样式等需求。

代码
import { IconSearch } from '@fibrous/semi-icons-react';
import { JsonViewer, Button } from '@fibrous/semi-ui-react';

const data = `{
    "name": "Semi",
    "version": "0.0.0"
}`;

export default function JsonViewerDemo() {
    return (
        <div style={{ marginBottom: 16 }}>
            <JsonViewer
                height={200}
                width={700}
                value={data}
                renderSearchButton={(defaultButton, controls) => (
                    <div style={{ position: 'absolute', top: 10, right: 10, zIndex: 10 }}>
                        {!controls.showSearchBar ? (
                            <Button icon={<IconSearch />} onClick={controls.onToggleSearchBar}>
                                搜索
                            </Button>
                        ) : (
                            defaultButton
                        )}
                    </div>
                )}
            />
        </div>
    );
}

API 参考

JsonViewer

Props

属性说明类型默认值
className根元素的类名。string-
height高度,数字按 px 处理,字符串原样使用。number | string400
limitSearchButtonBounds是否限制搜索按钮的拖动范围在组件容器内。boolean-
onChange编辑内容变化时调用,参数为编辑器当前文本。Type(value: string) => void-
options编辑器配置,字段见 JsonViewerOptions。ariaLabel 未传时取语言包 JsonViewer.content,readOnly 与 autoWrap 默认分别为 false 与 true;三者按键合并,只传部分字段时其余仍取默认值。options 按深比较变化时重建编辑器;就地修改 formatOptions、customRenderRule 等嵌套对象不会被察觉,需替换为新对象。JsonViewerOptions-
ref组件句柄,类型为 JsonViewerHandle,上面的方法见下方 Methods。注意它不是根 DOM 元素。TypeReact.Ref<JsonViewerHandle>-
renderSearchButton自定义渲染搜索按钮。第一个参数是默认按钮的 ReactNode,可直接放进返回的内容中;第二个参数 searchControls 为搜索状态与方法,其中 showSearchBar 是渲染时的值,状态变化后组件会重新渲染。Type( defaultSearchButton: React.ReactNode, searchControls: { showSearchBar: boolean; onToggleSearchBar: () => void; onSearch: (text: string, caseSensitive?: boolean, wholeWord?: boolean, regex?: boolean) => void; onPrevSearch: () => void; onNextSearch: () => void; onReplace: (text: string) => void; onReplaceAll: (text: string) => void; } ) => React.ReactNode-
showSearch是否显示搜索按钮。booleantrue
style根元素的内联样式,同名属性会覆盖组件设置的 width、height 与 position。React.CSSProperties-
value展示与编辑的 JSON 文本,未传时为空文本。只有 value 与编辑器当前内容不同时才会重建编辑器,把 onChange 的值写回 value 不会丢失光标、焦点和撤销历史。string""
width宽度,数字按 px 处理,字符串原样使用。number | string400

Methods

方法说明类型
format按 formatOptions 格式化当前内容。Type() => void
getSearchResults获取当前搜索结果。Type() => ReturnType<JsonViewerFoundation['getSearchResults']>
getStyle返回当前的 width 与 height。Type() => { width: number | string; height: number | string }
getValue获取编辑器当前文本。Type() => string
nextSearch跳转到下一个搜索结果,可选步长。Type(step?: number) => void
prevSearch跳转到上一个搜索结果,可选步长。Type(step?: number) => void
replace替换当前搜索匹配项;替换文本为空串时删除匹配,没有匹配时不做任何事,只读模式下同样不做任何事。Type(replaceText: string) => void
replaceAll替换所有搜索匹配项;替换文本为空串时删除全部匹配,只读模式下不做任何事。Type(replaceText: string) => void
search搜索文本,可选参数控制大小写敏感、全词匹配和正则表达式。Type(searchText: string, caseSensitive?: boolean, wholeWord?: boolean, regex?: boolean) => void

className、style 与其他原生属性(如 id、aria-label)落在根元素上。根元素没有 role、而传了 aria-label 或 aria-labelledby 时,根元素加 role="group";显式传入的 role 优先。

JsonViewerOptions

属性说明类型默认值
lineHeight行高number20
autoWrap是否自动换行booleantrue
readOnly是否只读booleanfalse
customRenderRule自定义渲染规则CustomRenderRule[]-
formatOptions格式化配置FormattingOptions-
completionOptions自动补全配置CompletionOptions-
ariaLabel内容区的可访问名称(只读时的滚动区域、可编辑时的文本框)string语言包 JsonViewer.content

CustomRenderRule

属性说明类型默认值
match匹配规则string | RegExp | (value: string | number | boolean | null, path: string, tokenType: 'key' | 'value') => boolean-
render渲染函数(content: string) => React.ReactNode-

FormattingOptions

属性说明类型默认值
tabSize缩进大小number4
insertSpaces是否使用空格进行缩进booleantrue
eol换行符string'\n'

CompletionOptions

属性说明类型默认值
staticCompletions静态补全项。只在按文档结构已推断出候选项时追加在其后,推断不出任何候选项时不提供;与已有候选项同 label 的不重复添加。只读取每项的 label、insertText(缺省时用 label)、documentationCompletionItem[]-

SearchControls

当使用 renderSearchButton 时,第二个参数 searchControls 包含以下属性:

属性说明类型
showSearchBar当前是否显示搜索栏boolean
onToggleSearchBar切换搜索栏显示/隐藏() => void
onSearch执行搜索(text: string, caseSensitive?: boolean, wholeWord?: boolean, regex?: boolean) => void
onPrevSearch跳转到上一个搜索结果() => void
onNextSearch跳转到下一个搜索结果() => void
onReplace替换当前搜索结果(text: string) => void
onReplaceAll替换所有搜索结果(text: string) => void

Methods

可以通过 ref 调用组件实例上绑定的方法,实现某些特殊交互。

名称描述
getValue()获取当前值
format()格式化当前内容
search(searchText: string, caseSensitive?: boolean, wholeWord?: boolean, regex?: boolean)搜索文本,可选参数控制大小写敏感、全词匹配和正则表达式
getSearchResults()获取当前搜索结果
prevSearch(step?: number)导航到上一个搜索结果,可选步长参数
nextSearch(step?: number)导航到下一个搜索结果,可选步长参数
replace(replaceText: string)替换当前搜索匹配项
replaceAll(replaceText: string)替换所有搜索匹配项
getStyle()获取当前的 width 与 height

Performance

Bundle Size

组件体积体积(Gzip)
JsonViewer203.14kb51.23kb
MonacoEditor5102.0 KB1322.7 KB

渲染不同量级数据耗时

注:

  • 测试数据生成方式详情可查阅 url
  • 当数据量级超出50w行时,ReactMonacoEditor 默认关闭高亮等行为,数据对比不遵循单一变量原则
组件1k行5k行1w行10w行50w行100w行300w行
JsonViewer30.42ms30.66ms36.87ms52.73ms111.02ms178.81ms506.25ms
ReactMonacoEditor72.01ms73.76ms76.64ms97.89ms133.31ms202.79ms495.53ms
性能提升57.70%58.41%51.87%46.11%---