基于jsoneditor二次封装一个可实时预览的json编辑器组件(react版)

徐小夕
• 阅读 1001

前言

做为一名前端开发人员,掌握vue/react/angular等框架已经是必不可少的技能了,我们都知道,vue或react等MVVM框架提倡组件化开发,这样一方面可以提高组件复用性和可扩展性,另一方面也带来了项目开发的灵活性和可维护,方便多人开发协作.接下来文章将介绍如何使用react,开发一个自定义json编辑器组件.我们这里使用了jsoneditor这个第三方库,官方地址: jsoneditor 通过实现一个json在线编辑器,来学习如何一步步封装自己的组件(不限于react,vue,原理类似).

你将学到:

  • react组件封装的基本思路
  • SOLID (面向对象设计)原则介绍
  • jsoneditor用法
  • 使用PropTypes做组件类型检查

设计思路

在介绍组件设计思路之前,有必要介绍一下著名的SOLID原则.

SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁提出的面向对象编程和面向对象设计的五个基本原则。利用这些原则,程序员能更容易和高效的开发一个可维护和扩展的系统。 SOLID被典型的应用在测试驱动开发上,并且是敏捷开发以及自适应软件开发的基本原则的重要组成部分。

  • S 单一功能原则: 规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的服务都应该严密的和该功能保持一致。

  • O 开闭原则: 规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。遵循这种原则的代码在扩展时并不需要改变。

  • L 里氏替换原则: 派生类(子类)对象可以在程序中代替其基类(超类)对象,是对子类型的特别定义.

  • I 接口隔离原则: 指明应用或者对象应该不依赖于它不使用的方法。接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样应用或对象只需要知道它们感兴趣的方法。这种缩小的接口也被称为角色接口。接口隔离原则(ISP)的目的是系统去耦合,从而容易重构,更改和重新部署。接口隔离原则是在SOLID (面向对象设计)中五个面向对象设计(OOD)的原则之一,类似于在GRASP (面向对象设计)中的高内聚性。

  • D 依赖反转原则: 是指一种特定的解耦 形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。

掌握好这5个原则将有利于我们开发出更优秀的组件,请默默记住.接下来我们来看看json编辑器的设计思路.

基于jsoneditor二次封装一个可实时预览的json编辑器组件(react版) 如上所示, 和任何一个输入框一样, 参考antd组件设计方式并兼容antd的form表单, 我们提供了onChange方法.(具体细节下文会详细介绍)

首先利用jsoneditor渲染的基本样式以及API,我们能实现一个基本可用的json编辑器,然后通过对外暴露的json和onChange属性进行数据双向绑定, 通过onError来监控异常或者输入的错误, 通过themeBgColor来修改默认的主题色,通过这几个接口,我们便能完全掌握一个组件的运行情况.

正文

接下来我们就正式开始我们的正文.由于本文的组件是基于react实现的,但是用在vue,angular上,基本模式同样适用.关键就是掌握好不同框架的生命周期.

在学习实现json编辑器组件之前,我们有必要了解一下jsoneditor这个第三方组件的用法与api.

jsoneditor的使用

  1. 安装

我们先执行npm install安装我们的组件

npm install jsoneditor

其次手动引入样式文件

<link href="jsoneditor/dist/jsoneditor.min.css" rel="stylesheet" type="text/css">

这样,我们就能使用它的api了:

<div id="jsoneditor" style="width: 400px; height: 400px;"></div>

<script>
    // 创建编辑器
    var container = document.getElementById("jsoneditor");
    var editor = new JSONEditor(container);

    // 设置json数据
    function setJSON () {
        var json = {
            "Array": [1, 2, 3],
            "Boolean": true,
            "Null": null,
            "Number": 123,
            "Object": {"a": "b", "c": "d"},
            "String": "Hello World"
        };
        editor.set(json);
    }

    // 获取json数据
    function getJSON() {
        var json = editor.get();
        alert(JSON.stringify(json, null, 2));
    }
</script>

所以你可能看到如下界面: 基于jsoneditor二次封装一个可实时预览的json编辑器组件(react版) 为了能实现实时预览和编辑,光这样还远远不够,我们还需要进行额外的处理.我们需要用到jsoneditor其他的api和技巧.

结合react进行二次封装

基于以上谈论,我们很容易将编辑器封装成react组件, 我们只需要在componentDidMount生命周期里初始化实例即可.react代码可能是这样的:

import React, { PureComponent } from 'react'
import JSONEditor from 'jsoneditor'

import 'jsoneditor/dist/jsoneditor.css'

class JsonEditor extends PureComponent {
  initJsonEditor = () => {
    const options = {
      mode: 'code',
      history: true,
      onChange: this.onChange,
      onValidationError: this.onError
    };

    this.jsoneditor = new JSONEditor(this.container, options)
    this.jsoneditor.set(this.props.value)
  }

  componentDidMount () {
    this.initJsonEditor()
  }

  componentWillUnmount () {
    if (this.jsoneditor) {
      this.jsoneditor.destroy()
    }
  }

  render() {
    return <div className="jsoneditor-react-container" ref={elem => this.container = elem} />
  }
}
export default JsonEditor

至于options里的选项, 我们可以参考jsoneditor的API文档,里面写的很详细, 通过以上代码,我们便可以实现一个基本的react版的json编辑器组件.接下来我们来按照设计思路一步步实现可实时预览的json编辑器组件.

  1. 实现预览和编辑视图

其实这一点很好实现,我们只需要实例化2个编辑器实例,一个用于预览,一个用于编辑就好了.

import React, { PureComponent } from 'react'
import JSONEditor from 'jsoneditor'
import 'jsoneditor/dist/jsoneditor.css'

class JsonEditor extends PureComponent {
  onChange = () => {
    let value = this.jsoneditor.get()
    this.viewJsoneditor.set(value)
  }

  initJsonEditor = () => {
    const options = {
      mode: 'code',
      history: true
    };

    this.jsoneditor = new JSONEditor(this.container, options)
    this.jsoneditor.set(this.props.value)
  }

  initViewJsonEditor = () => {
    const options = {
      mode: 'view'
    };

    this.viewJsoneditor = new JSONEditor(this.viewContainer, options)
    this.viewJsoneditor.set(this.props.value)
  }

  componentDidMount () {
    this.initJsonEditor()
    this.initViewJsonEditor()
  }

  componentDidUpdate() {
    if(this.jsoneditor) {
      this.jsoneditor.update(this.props.value)
      this.viewJsoneditor.update(this.props.value)
    }
  }

  render() {
    return (
      <div className="jsonEditWrap">
        <div className="jsoneditor-react-container" ref={elem => this.container = elem} />
        <div className="jsoneditor-react-container" ref={elem => this.viewContainer = elem} />
      </div>
    );
  }
}

export default JsonEditor

这样,我们便能实现一个初步的可实时预览的编辑器.可能效果长这样:

基于jsoneditor二次封装一个可实时预览的json编辑器组件(react版) 接近于成熟版,但是还有很多细节要处理.

  1. 对外暴露属性和方法以支持不同场景的需要
    import React, { PureComponent } from 'react'
    import JSONEditor from 'jsoneditor'
    

import 'jsoneditor/dist/jsoneditor.css'

class JsonEditor extends PureComponent { // 监听输入值的变化 onChange = () => { let value = this.jsoneditor.get() this.props.onChange && this.props.onChange(value) this.viewJsoneditor.set(value) } // 对外暴露获取编辑器的json数据 getJson = () => { this.props.getJson && this.props.getJson(this.jsoneditor.get()) } // 对外提交错误信息 onError = (errArr) => { this.props.onError && this.props.onError(errArr) }

initJsonEditor = () => { const options = { mode: 'code', history: true, onChange: this.onChange, onValidationError: this.onError };

this.jsoneditor = new JSONEditor(this.container, options)
this.jsoneditor.set(this.props.value)

}

initViewJsonEditor = () => { const options = { mode: 'view' };

this.viewJsoneditor = new JSONEditor(this.viewContainer, options)
this.viewJsoneditor.set(this.props.value)

}

componentDidMount () { this.initJsonEditor() this.initViewJsonEditor() // 设置主题色 this.container.querySelector('.jsoneditor-menu').style.backgroundColor = this.props.themeBgColor this.container.querySelector('.jsoneditor').style.border = thin solid ${this.props.themeBgColor} this.viewContainer.querySelector('.jsoneditor-menu').style.backgroundColor = this.props.themeBgColor this.viewContainer.querySelector('.jsoneditor').style.border = thin solid ${this.props.themeBgColor} }

componentDidUpdate() { if(this.jsoneditor) { this.jsoneditor.update(this.props.json) this.viewJsoneditor.update(this.props.json) } }

render() { return (

<div className="jsoneditor-react-container" ref={elem => this.container = elem} /> <div className="jsoneditor-react-container" ref={elem => this.viewContainer = elem} />
); } }

export default JsonEditor

通过以上的过程,我们已经完成一大半工作了,剩下的细节和优化工作,比如组件卸载时如何卸载实例, 对组件进行类型检测等,我们继续完成以上问题.

3. **使用PropTypes进行类型检测以及在组件卸载时清除实例**
类型检测时react内部支持的,安装react的时候会自动帮我们安装PropTypes,具体用法可参考官网地址[propTypes文档](https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html),其次我们会在react的componentWillUnmount生命周期中清除编辑器的实例以释放内存.完整代码如下:
``` jsx
import React, { PureComponent } from 'react'
import JSONEditor from 'jsoneditor'
import PropTypes from 'prop-types'
import 'jsoneditor/dist/jsoneditor.css'

/**
 * JsonEditor
 * @param {object} json 用于绑定的json数据
 * @param {func} onChange 变化时的回调
 * @param {func} getJson 为外部提供回去json的方法
 * @param {func} onError 为外部提供json格式错误的回调
 * @param {string} themeBgColor 为外部暴露修改主题色
 */
class JsonEditor extends PureComponent {
  onChange = () => {
    let value = this.jsoneditor.get()
    this.props.onChange && this.props.onChange(value)
    this.viewJsoneditor.set(value)
  }

  getJson = () => {
    this.props.getJson && this.props.getJson(this.jsoneditor.get())
  }

  onError = (errArr) => {
    this.props.onError && this.props.onError(errArr)
  }

  initJsonEditor = () => {
    const options = {
      mode: 'code',
      history: true,
      onChange: this.onChange,
      onValidationError: this.onError
    };

    this.jsoneditor = new JSONEditor(this.container, options)
    this.jsoneditor.set(this.props.value)
  }

  initViewJsonEditor = () => {
    const options = {
      mode: 'view'
    };

    this.viewJsoneditor = new JSONEditor(this.viewContainer, options)
    this.viewJsoneditor.set(this.props.value)
  }

  componentDidMount () {
    this.initJsonEditor()
    this.initViewJsonEditor()
    // 设置主题色
    this.container.querySelector('.jsoneditor-menu').style.backgroundColor = this.props.themeBgColor
    this.container.querySelector('.jsoneditor').style.border = `thin solid ${this.props.themeBgColor}`
    this.viewContainer.querySelector('.jsoneditor-menu').style.backgroundColor = this.props.themeBgColor
    this.viewContainer.querySelector('.jsoneditor').style.border = `thin solid ${this.props.themeBgColor}`
  }

  componentWillUnmount () {
    if (this.jsoneditor) {
      this.jsoneditor.destroy()
      this.viewJsoneditor.destroy()
    }
  }

  componentDidUpdate() {
    if(this.jsoneditor) {
      this.jsoneditor.update(this.props.json)
      this.viewJsoneditor.update(this.props.json)
    }
  }

  render() {
    return (
      <div className="jsonEditWrap">
        <div className="jsoneditor-react-container" ref={elem => this.container = elem} />
        <div className="jsoneditor-react-container" ref={elem => this.viewContainer = elem} />
      </div>
    );
  }
}

JsonEditor.propTypes = {
  json: PropTypes.object,
  onChange: PropTypes.func,
  getJson: PropTypes.func,
  onError: PropTypes.func,
  themeBgColor: PropTypes.string
}

export default JsonEditor

由于组件严格遵守开闭原则,所以我们可以提供更加定制的功能在我们的json编辑器中,已实现不同项目的需求.对于组件开发的健壮性探讨,除了使用propTypes外还可以基于typescript开发,这样适合团队开发组件库或者复杂项目组件的追溯和查错.最终效果如下:

基于jsoneditor二次封装一个可实时预览的json编辑器组件(react版)

笔者已经将实现过的组件发布到npm上了,大家如果感兴趣可以直接用npm安装后使用,方式如下:

npm i @alex_xu/xui

// 导入xui
import { 
  Button,
  Skeleton,
  Empty,
  Progress,
  Tag,
  Switch,
  Drawer,
  Badge,
  Alert
} from '@alex_xu/xui'

该组件库支持按需导入,我们只需要在项目里配置babel-plugin-import即可,具体配置如下:

// .babelrc
"plugins": [
  ["import", { "libraryName": "@alex_xu/xui", "style": true }]
]

npm库截图如下:

基于jsoneditor二次封装一个可实时预览的json编辑器组件(react版)

最后

如果想了解更多H5游戏, webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在公众号《趣谈前端》加入我们一起学习讨论,共同探索前端的边界。

基于jsoneditor二次封装一个可实时预览的json编辑器组件(react版)

更多推荐

点赞
收藏
评论区
推荐文章
浅梦一笑 浅梦一笑
4个月前
初学 Python 需要安装哪些软件?超级实用,小白必看!
编程这个东西是真的奇妙。对于懂得的人来说,会觉得这个工具是多么的好用、有趣,而对于小白来说,就如同大山一样。其实这个都可以理解,大家都是这样过来的。那么接下来就说一下python相关的东西吧,并说一下我对编程的理解。本人也是小白一名,如有不对的地方,还请各位大神指出01名词解释:如果在编程方面接触的比较少,那么对于软件这一块,有几个名词一定要了解,比如开发环
Jacquelyn38 Jacquelyn38
1年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Wesley13 Wesley13
1年前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序selectfromtable_nameorderiddesc;2.按照指定(多个)字段排序selectfromtable_nameorderiddesc,statusdesc;3.按照指定字段和规则排序selec
Wesley13 Wesley13
1年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
helloworld_34035044 helloworld_34035044
6个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue