发布于 2025-12-07 0 阅读
0

使用 React.js 和 TypeScript 创建 Markdown 编辑器并通过 Github Actions 进行部署

使用 React.js 和 TypeScript 创建 Markdown 编辑器并通过 Github Actions 进行部署

我记得在参加 freeCodeCamp 的时候,我准备在其中一个项目中做一个 Markdown 编辑器。所以这次我决定用 Markdown 编辑器,并结合 React.js 和 TypeScript。

您将学到什么

  • 使用 TypeScript 设置 React.js 项目
  • 通过将 Markdown 编译为 HTML 来创建 Markdown 编辑器
  • 使用 React hooks 为应用程序创建主题
  • 通过 Github Actions 进行持续部署

我是个懒人,我想你们大多数人也一样。所以,如果你想直接看,这里是代码和演示链接。

项目源代码:

GitHub 徽标 ashwamegh / react-typescript-markdown-editor

Markdown 编辑器与 React.js 和 TypeScript 结合使用,并结合使用 Github 操作工作流进行持续部署

项目演示:ashwamegh/react-typescript-markdown-editor

让我们开始设置我们的项目

1. 使用 React.js 和 TypeScript 设置我们的项目

我们都知道 TypeScript 的功能,它能帮你避免犯一些愚蠢的错误。如果与 React 结合使用,它们将成为驱动任何应用程序的绝佳组合。

我将使用它create-react-app,因为它提供了开箱即用的 TypeScript 支持。转到要创建项目的根目录并运行以下命令:

npx create-react-app markdown-editor --template typescript

Enter fullscreen mode Exit fullscreen mode

这个--template typescript标志将为您完成所有艰苦的工作,使用 TypeScript 设置 React.js 项目。

稍后,您需要删除一些引导代码才能开始创建应用程序。

作为参考,您可以检查此初始提交以查看已删除的内容:
https://github.com/ashwamegh/react-typescript-markdown-editor/commit/7cc379ec0d01f3f1a07396ff2ac6c170785df57b

完成初始步骤后,我们将继续创建 Markdown 编辑器。

2.创建Markdown编辑器

在深入研究代码之前,让我们先看看我们将要开发的项目的文件夹结构。

├── README.md
├── package.json
├── public
|  ├── favicon.ico
|  ├── index.html
|  ├── logo192.png
|  ├── logo512.png
|  ├── manifest.json
|  └── robots.txt
├── src
|  ├── App.test.tsx
|  ├── App.tsx
|  ├── components
|  |  ├── Editor.tsx
|  |  ├── Footer.tsx
|  |  ├── Header.tsx
|  |  ├── Main.tsx
|  |  ├── Preview.tsx
|  |  └── shared
|  |     └── index.tsx
|  ├── index.css
|  ├── index.tsx
|  ├── react-app-env.d.ts
|  ├── serviceWorker.ts
|  ├── setupTests.ts
|  └── userDarkMode.js
├── tsconfig.json
└── yarn.lock
Enter fullscreen mode Exit fullscreen mode

我将用它来emotion创建组件的样式以及react-icons项目中使用的图标。因此,你需要安装emotion并react-icons运行以下命令:

npm i -S @emotion/core @emotion/styled react-icons
Enter fullscreen mode Exit fullscreen mode

或者如果你yarn像我一样,你可以运行

yarn add @emotion/core @emotion/styled react-icons
Enter fullscreen mode Exit fullscreen mode

继续前进,首先我们将创建一个shared组件文件夹来创建我们将要重复使用的组件。


/* src/components/shared/index.tsx */

import React from 'react'
import styled from '@emotion/styled'

export const ColumnFlex = styled.div`
  display: flex;
  flex-direction: column;
`
export const RowFlex = styled.div`
  display: flex;
  flex-direction: row;
`

Enter fullscreen mode Exit fullscreen mode

在这个文件中,我们声明了两个样式组件 forflex-column和flex-rowstyled divs,稍后我们会用到它们。
想了解更多关于styled-componentswithemotion库的信息,请访问此链接。

3 使用 React hooks 创建自定义主题钩子

我们将使用 React Hooks 创建自定义 Hook 来实现基本的主题功能,使用它我们可以将主题从浅色切换到深色。

/* useDarMode.js */

import { useEffect, useState } from 'react'

export default () => {
  const [theme, setTheme] = useState('light')

  const toggleTheme = () => {
    if (theme === 'dark') {
      setTheme('light')
    } else {
      setTheme('dark')
    }
  }

  useEffect(() => {
    const localTheme = localStorage.getItem('theme')
    if (localTheme) {
      setTheme(localTheme)
    }
  }, [])

  return {
    theme,
    toggleTheme,
  }
}
Enter fullscreen mode Exit fullscreen mode

在我们的钩子文件中,我们将主题的初始状态设置为light使用useState钩子。并使用钩子useEffect检查浏览器本地存储中是否存在任何主题项,如果存在,则从中选择主题并将其设置为我们的应用程序。

由于我们已经定义了共享组件和自定义主题反应钩子,让我们深入了解我们的应用程序组件。

因此,我将我们的应用程序结构分为 5 个组件,分别是:Header、Main(包含应用程序的主要部分以及编辑器和预览组件)和 Footer 组件。

  1. 标题//包含普通标题代码和切换主题的开关
  2. 主//编辑器和预览组件的容器 i. 编辑器//包含编辑器的代码 ii. 预览//包含用于将 Markdown 代码预览为 HTML 的代码
  3. 页脚//包含正常的页脚代码
/* src/components/Header.tsx */

import React from 'react'
import { FiSun } from 'react-icons/fi'
import { FaMoon } from 'react-icons/fa'

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'

// Prop check in typescript
interface Props {
  toggleTheme: () => void,
  theme: string
}

const Header: React.FC<Props> = ({ theme, toggleTheme }) => {

  return (
    <header
      css={theme === 'dark' ?
      css`
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        background-color: #f89541;
        padding: 24px 32px;
        font-size: 16px;
      `:css`
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        background-color: #f8f541;
        padding: 24px 32px;
        box-shadow: 0px -2px 8px #000;
        font-size: 16px;
    `}>
      <div className="header-title">
        Markdown Editor
      </div>
      <div css={
        css`
          cursor: pointer;
        `}
        onClick={toggleTheme}
      >
       {
         theme === 'dark'?
         <FaMoon />:
         <FiSun />
       }
      </div>
    </header>
  )
}

export default Header;
Enter fullscreen mode Exit fullscreen mode

在这个组件中,我们使用 TypeScript 进行 prop 检查,你可能想知道为什么我们React.FC在这里提到它。原因很简单,通过将组件类型设置为 FC(FunctionComponent),React TypeScript 类型允许我们正确处理 children 和 defaultProps。

为了设计我们的组件样式,我们使用库css中的字符串样式的 prop ,您可以通过关注此处的emotion文档了解更多信息

创建 Header 组件后,我们将创建 Footer 组件,然后转到 Main 组件。

让我们看看 Footer 组件的代码

import React from 'react'

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'

const Footer: React.FC = () => {

  return (
    <footer>
      <div 
        className="footer-description"
        css={
            css`
                padding: 16px 0px;
                overflow: hidden;
                position: absolute;
                width: 100%;
                text-align: center;
                bottom: 0px;
                color: #f89541;
                background: #000;
            `
        }>
       <span>{`</>`}</span><span> with <a href="https://reactjs.org" target="_blank">React.js</a> &amp; <a href="https://www.typescriptlang.org/" target="_blank">TypeScript</a></span>
      </div>
    </footer>
  )
}

export default Footer;

Enter fullscreen mode Exit fullscreen mode

页脚组件包含简单的代码来呈现常见的信用内容。

/* src/components/Main.tsx */

import React, { useState } from 'react'

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'
import { RowFlex } from './shared'
import Editor from './Editor';
import Preview from './Preview';

interface Props {
  theme: string
}

const Main: React.FC<Props> =  ({ theme }) => {
  const [markdownContent, setMarkdownContent] = useState<string>(`
# H1
## H2
### H3
#### H4
##### H5

__bold__
**bold**
_italic_
`);
  return (
    <RowFlex
      css={css`
        padding: 32px;
        padding-top: 0px;
        height: calc(100vh - 170px);
        `}>
      <Editor theme={theme} markdownContent={markdownContent} setMarkdownContent={setMarkdownContent}/>
      <Preview theme={theme} markdownContent={markdownContent}/>
    </RowFlex>
  )
}

export default Main;

Enter fullscreen mode Exit fullscreen mode

因为,前面组件中的一些代码看起来可能很熟悉,现在您可以自己理解了。除此之外,我们使用了useState钩子来创建一个状态来保存 Markdown 内容,并使用一个处理程序来设置它,并setMarkdownContent在代码中调用。

我们需要将这些传递给我们的Editor和Preview组件,以便它们能够为用户提供编辑和预览 Markdown 内容的方式。我们还使用一些基本的 Markdown 文本为内容设置了初始状态。

让我们看看编辑器组件的代码:

/* src/components/Editor.tsx */

import React, { ChangeEvent } from 'react'
import PropTypes from 'prop-types';

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'
import { ColumnFlex } from './shared'

interface Props {
  markdownContent: string;
  setMarkdownContent: (value: string) => void,
  theme: string
}

const Editor: React.FC<Props> = ({ markdownContent, setMarkdownContent, theme }) => {
    return (
        <ColumnFlex
        id="editor"
        css={css`
            flex: 1;
            padding: 16px;
          `}>
        <h2>
        Editor
        </h2>
        <textarea
          onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setMarkdownContent(e.target.value)}
          css={theme === 'dark'?
          css`
            height: 100%;
            border-radius: 4px;
            border: none;
            box-shadow: 0 -2px 10px rgba(0, 0, 0, 1);
            background: #000;
            color: #fff;
            font-size: 100%;
            line-height: inherit;
            padding: 8px 16px;
            resize: none;
            overflow: auto;
            &:focus {
              outline: none;
            }
          `
          : css`
            height: 100%;
            border-radius: 4px;
            border: none;
            box-shadow: 2px 2px 10px #999;
            font-size: 100%;
            line-height: inherit;
            padding: 8px 16px;
            resize: none;
            overflow: auto;
            &:focus {
              outline: none;
            }
          `}
          rows={9}
          value={markdownContent}
          />
      </ColumnFlex>
    )
}

Editor.propTypes = {
  markdownContent: PropTypes.string.isRequired,
  setMarkdownContent: PropTypes.func.isRequired,
}

export default Editor;
Enter fullscreen mode Exit fullscreen mode

这是一个简单的组件,用于<textarea/>为用户提供一种输入方式,这些输入必须进一步编译才能在预览组件中呈现为 HTML 内容。

现在,除了预览组件之外,我们已经创建了几乎所有用于保存代码的组件。
我们需要一些东西来将用户的 Markdown 内容编译为简单的 HTML,但我们不想编写所有的编译器代码,因为我们有很多选项可供选择。

在这个应用中,我们将使用marked库将 Markdown 内容编译为 HTML。因此,你需要通过运行以下命令来安装它:

npm i -S marked
Enter fullscreen mode Exit fullscreen mode

或用纱线

yarn add marked
Enter fullscreen mode Exit fullscreen mode

如果你想了解更多关于这个图书馆的信息,你可以在这里看到

让我们看看预览组件的代码

/* src/components/Preview.tsx */

import React from 'react'
import PropTypes from 'prop-types'
import marked from 'marked'

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'
import { ColumnFlex } from './shared'

interface Props {
    markdownContent: string,
    theme: string
}

const Preview: React.FC<Props> = ({ markdownContent, theme }) => {
    const mardownFormattedContent = ( marked(markdownContent));

    return (
        <ColumnFlex
            id="preview"
            css={css`
            flex: 1;
            padding: 16px;
            `}
        >
            <h2>Preview</h2>
            <div
                css={theme === 'dark'
                ? css`
                height: 100%;
                border-radius: 4px;
                border: none;
                box-shadow: 0 -2px 10px rgba(0, 0, 0, 1);
                font-size: 100%;
                line-height: inherit;
                overflow: auto;
                background: #000;
                padding: 8px 16px;
                color: #fff;
                `
                : css`
                height: 100%;
                border-radius: 4px;
                border: none;
                box-shadow: 2px 2px 10px #999;
                font-size: 100%;
                line-height: inherit;
                overflow: auto;
                background: #fff;
                padding: 8px 16px;
                color: #000;
            `}
            dangerouslySetInnerHTML={{__html: mardownFormattedContent}}
            >
            </div>
        </ColumnFlex>
    )
}

Preview.propTypes = {
    markdownContent: PropTypes.string.isRequired
  }

export default Preview;
Enter fullscreen mode Exit fullscreen mode

在这个组件中,我们编译 Markdown 内容并将其存储在 mardownFormattedContent 变量中。为了在 HTML 中显示内容预览,我们必须使用dangerouslySetInnerHTMLprop 将 HTML 内容直接显示在 DOM 中,我们通过dangerouslySetInnerHTML={{__html: mardownFormattedContent}}为 div 元素添加此 prop 来实现。

最后,我们准备好了创建 Markdown 编辑器应用程序所需的所有组件。让我们将它们全部导入到我们的App.tsx文件中。

/* src/App.tsx */

import React from 'react'
import { css, jsx } from '@emotion/core'

// Components
import Header from './components/Header'
import Main from './components/Main'
import Footer from './components/Footer';
import useDarkMode from './userDarkMode';

function App() {
  const { theme, toggleTheme } = useDarkMode();
  const themeStyles = theme === 'light'? {
    backgroundColor: '#eee',
    color: '#000'
  }: {
    backgroundColor: '#171616',
    color: '#fff'
  }
  return (
    <div 
      className="App"
      style={themeStyles}
      >
      <Header theme={theme} toggleTheme={toggleTheme}/>
      <Main theme={theme}/>
      <Footer />
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

在我们的 App 组件中,我们导入子组件并传递主题道具。

现在,如果您已遵循上述所有步骤,您将拥有一个正在运行的 markdown 编辑器应用程序,对于我使用的样式,您可以使用我提到的链接查看我的源代码。

现在,是时候为我们的项目创建 Github 操作,以便在每次推送到 master 时创建持续部署工作流。

4 通过 Github Actions 设置持续部署

我们将使用 Github 操作工作流在每次推送到 master 时构建和部署我们的 Web 应用程序。

由于这不是一个拥有生产和开发分支的企业应用程序,我将为主分支设置我的工作流程,但如果在未来的任何时候,您需要为您的企业应用程序设置 Github 操作工作流程,请小心处理分支。

为此,我们将遵循以下步骤:

  1. 在我们的项目根目录中创建一个文件夹.github/workflows/,它将保存所有工作流程配置。
  2. 我们将使用JamesIves/github-pages-deploy-action操作来部署我们的应用程序。
  3. 接下来,我们将在这里创建.yml文件,该文件将负责构建和部署我们的应用程序到 GitHub 页面。我们将其命名为build-and-deploy-to-gh-pages.yml

让我们看看里面有什么build-and-deploy-to-gh-pages.yml

# build-and-deploy-to-gh-pages.yml

name: Build & deploy to GitHub Pages
on:
  push:
    branches:
      - master
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v1
      - name: Set up Node
        uses: actions/setup-node@v1
        with:
          node-version: 10.x 
      - name: Set email
        run: git config --global user.email "${{ secrets.adminemail }}"
      - name: Set username
        run: git config --global user.name "${{ secrets.adminname }}"
      - name: npm install command
        run: npm install
      - name: Run build command
        run: npm run build
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BASE_BRANCH: master
          BRANCH: gh-pages # The branch the action should deploy to.
          FOLDER: build # The folder the action should deploy.
Enter fullscreen mode Exit fullscreen mode

gh-pages这个工作流程每次都会运行,我们将一些内容推送到 master 并通过分支部署应用程序。

让我们分解一下工作流文件

name: Build & deploy to GitHub Pages
on:
  push:
    branches:
      - master
Enter fullscreen mode Exit fullscreen mode

这定义了我们的工作流名称以及在其中运行作业的触发器。这里我们设置触发器来监听分支Push中的任何事件master。

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v1
      - name: Set up Node
        uses: actions/setup-node@v1
        with:
          node-version: 10.x 
      - name: Set email
        run: git config --global user.email "${{ secrets.adminemail }}"
      - name: Set username
        run: git config --global user.name "${{ secrets.adminname }}"
      - name: npm install command
        run: npm install
      - name: Run build command
        run: npm run build
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BASE_BRANCH: master
          BRANCH: gh-pages # The branch the action should deploy to.
          FOLDER: build # The folder the action should deploy.
Enter fullscreen mode Exit fullscreen mode

这是我们工作流程中最重要的部分,它声明了jobs要完成的任务。配置中的一些行是不言自明的runs-on: ubuntu-latest,它定义了它将在哪个系统上运行。

- name: Checkout
        uses: actions/checkout@v1
Enter fullscreen mode Exit fullscreen mode

这是一个检出仓库的操作,在后续的工作中,我们将通过安装 Node 并设置 git profile 配置来设置开发环境。然后,我们将运行npm install并提取所有依赖项,最后运行build命令。

- name: Deploy
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BASE_BRANCH: master
          BRANCH: gh-pages # The branch the action should deploy to.
          FOLDER: build # The folder the action should deploy.
Enter fullscreen mode Exit fullscreen mode

构建命令完成后,我们使用JamesIves/github-pages-deploy-action@releases/v3操作将构建文件夹部署到gh-pages。

无论何时,当您在主分支中推送某些内容时,此工作流程都会运行并将您的静态构建文件夹部署到gh-pages分支。

替代文本

现在,部署完成后,您的应用程序就可以在您的 github 链接https://yourusername.github.io/markdown-editor/上运行了。

不要忘记添加"homepage" : "https://yourusername.github.io/markdown-editor/"文件package.json,否则提供静态内容可能会导致问题。

如果你喜欢我的文章,可以在 Twitter 上关注我的每日动态The JavaSc®ipt Showcase,也可以在 Github 上关注我的个人项目。欢迎在评论区留言,看看你觉得这篇文章怎么样。谢谢!

Liquid 错误:内部

GitHub 徽标 ashwamegh / react-typescript-markdown-editor

Markdown 编辑器与 React.js 和 TypeScript 结合使用,并结合使用 Github 操作工作流进行持续部署

鏂囩珷鏉ユ簮锛�https://dev.to/ashwamegh/creating-a-markdown-editor-in-react-js-typescript-with-deployment-through-github-actions-hfn