Web 前端小白入门(二):心路历程,非技术指南

176

此篇文章记录一个小白入门 web 前端开发的历程,记录了我如何学习前端开发技术,并开发自己工作中常用的工具集,并不是技术入门指导。

第二个版本 Markdown 编辑器

前文提到,现有 Markdown 导出 PDF 时设置字体比较麻烦,所以才自己动手做了一个 Markdown 编辑器。在完成了第一版开发熟悉了 React 基础使用后就开始针对需求开发第二个版本。

第二个版本(0.2.0)Markdown 编辑器支持导出 PDF 和通过 CSS 设置编辑器字体与导出的 PDF 字体。在使用 Markdown 编辑文档时常常要向文档中插入图片,Markdown 支持填写图片地址来渲染图片,那么可以直接在编辑器界面上传图片并插入到编辑框是很常规的一个需求,所以这个版本也做了图片上传并插入到编辑框光标所在位置。还有一个功能也是我们在使用其他 Markdown 编辑器时常见到的,就是界面隐藏(隐藏编辑窗口、隐藏渲染窗口),所以此版本中也加入了页面隐藏功能。

此版本的 Markdown 编辑器分成了四个组件:

  • MD 父组件用来组织布局,state 用来存储数据并传递给子组件
  • MDEditor 组件包含编辑框,用来输入文档内容
  • MDRender 组件包含渲染框,用来展示渲染后的 HTML 内容
  • MDHeader 组件包含各种操作的按钮

所用技术栈如下:

  • AntD
  • axios
  • markdown-it
  • React
  • webpack

字体修改

字体修改功能的实现很简单,增加了一个输入框,用来填写 CSS 字体,监听 enter 点击事件,修改 MD 组件 state 中的 fontFamily。编辑框与渲染框的 CSS style 中 font-family 为 state 中的 fontFamily

monaco 字体.png

shizuha_courier.png

隐藏界面

隐藏界面与字体修改的思路一样,在 MD 组件的 state 中增加 viewMode,MD 组件根据不同的 viewMode 修改 render 中需要渲染的组件。

shizuha_0.2.0_hide_render.png

shizuha_0.2.0_hide_editor.png

图片上传

在实现图片上传功能时遇到一个需要解决的问题,就是如何获取到编辑框的光标位置。使用了 React 后对便不再手动操作 DOM 元素了,这个问题要如何解决呢?搜索了 React 文档后发现,可以给组件设置一个属性 ref 用来保存当前组件的 DOM 元素。但是如上所述,此版本分成了几个组件,图片上传按钮在 MDHeader 组件中,而输入框在 MDEditor 中,那么如果使用 ref 属性就要把 DOM 元素存在父组件 MD 中,然后再传递给 MDHeader 中。感觉这样做反倒麻烦了,何不直接使用 CSS id 直接获取一下 DOM 呢,毕竟这个 Mardkown 不存在复用问题,各子组件见耦合又怎样。所以我最后采取了简单粗暴的方式 document.getElementById('shizuha-md-editor')

获取到 DOM 后就是如何判断光标位置,经过一番 Google 后找到了解决方案(前端小白一点知识储备都没有,对 HTML 与前端的 JavaScript 环境一无所知),代码如下:

// 此段代码在 MDHeader 组件中
uploadCustomRequest({ file, onSuccess, onError }) {
    const self = this;

    const data = new FormData();
    data.append('file', file);
    data.append('dir', '/shizuha');

    axios.post('http://xxx.com/upload', data)
        .then(function (res) {
            if (1 !== res.data.success) {
                onError(new Error('upload picture failed'));
                return message.error('上传图片失败');
            }

            onSuccess(undefined, file);
            self.setState({
                fileList: []
            });
            return self.props.insertImgToMarkdownContent(res.data.path, file.name);
        })
        .catch(function (err) {
            onError(err);
            return message.error(err.message);
        });
}

// 此段代码在 MD 父组件中
insertImgToMarkdownContent(url, name) {
    const mdEditorElement = document.getElementById('md-editor');
    const insertImg = ` ![${name}](${url}) `
    const start = mdEditorElement.selectionStart;
    const end = mdEditorElement.selectionEnd;
    const current = start + insertImg.length;
    const markdownContent = mdEditorElement.value.slice(0, start) +
        insertImg +
        mdEditorElement.value.slice(end);
    this.handleMarkdownContentChange({
        target: {
            value: markdownContent
        }
    });
    message.success('图片地址已插入编辑框');
}

图片上传后,把数据传给了父组件进行处理,修改编辑框内容同时触发渲染框重新渲染,因为这个时候还没有引入 Redux 呢,学习总得循序渐进嘛。

图片上传.png

导出 PDF

导出 PDF 功能依赖于 Chrome 自带的打印功能,在开发这个功能时遇到一些需要思考的问题。了解到 Chrome 打印是打印当前 HTML body 内的所有内容,但是我们的 body 中除了渲染框还有其他内容,这些显然是不应该在导出的 PDF 中展示的。这个问题的解决思路就是在即将导出 PDF 前,把 body 内容换成渲染框的内容,在导出结束后再复原。

所以在 MD 组件的 state 中加入了一个字段 isPrinting,MD 组件的 render 中判断 isPrinting 是否为 true,如果为 true 就只展示 MDRender 组件,否则展示所有组件。相配合的 MDHeader 中的导出 PDF 按钮对应的操作函数就是修改 isPrintingtrue 然后调用 window.print(),等打印结束后再修改 isPrintingfalse 即可,代码如下:

htmlToPDF() {
    this.setState({
        isPrinting: true
    }, () => {
        window.print();
        this.setState({
            isPrinting: false
        });
    });
}

shizuha_pdf.png

可以看到在打印的这个时刻,后面的 HTML 内容仅为渲染框的内容。

总结

此版本已经完成我需要的所有功能,但是代码中数据的管理依赖于组件 props 传递,逻辑复杂而且不易维护,所以接下来的学习就是如果使用数据存储库 Redux 来实现组件间共享数据的管理。

文章太长,分开多篇记录,未完待续。