使用 React.js 和 TypeScript 创建 Markdown 编辑器并通过 Github Actions 进行部署
我记得在参加 freeCodeCamp 的时候,我准备在其中一个项目中做一个 Markdown 编辑器。所以这次我决定用 Markdown 编辑器,并结合 React.js 和 TypeScript。
您将学到什么
- 使用 TypeScript 设置 React.js 项目
- 通过将 Markdown 编译为 HTML 来创建 Markdown 编辑器
- 使用 React hooks 为应用程序创建主题
- 通过 Github Actions 进行持续部署
我是个懒人,我想你们大多数人也一样。所以,如果你想直接看,这里是代码和演示链接。
项目源代码:
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
这个--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
我将用它来emotion创建组件的样式以及react-icons项目中使用的图标。因此,你需要安装emotion并react-icons运行以下命令:
npm i -S @emotion/core @emotion/styled react-icons
或者如果你yarn像我一样,你可以运行
yarn add @emotion/core @emotion/styled react-icons
继续前进,首先我们将创建一个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;
`
在这个文件中,我们声明了两个样式组件 for
flex-column和flex-rowstyleddivs,稍后我们会用到它们。
想了解更多关于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,
}
}
在我们的钩子文件中,我们将主题的初始状态设置为
light使用useState钩子。并使用钩子useEffect检查浏览器本地存储中是否存在任何主题项,如果存在,则从中选择主题并将其设置为我们的应用程序。
由于我们已经定义了共享组件和自定义主题反应钩子,让我们深入了解我们的应用程序组件。
因此,我将我们的应用程序结构分为 5 个组件,分别是:Header、Main(包含应用程序的主要部分以及编辑器和预览组件)和 Footer 组件。
- 标题//包含普通标题代码和切换主题的开关
- 主//编辑器和预览组件的容器 i. 编辑器//包含编辑器的代码 ii. 预览//包含用于将 Markdown 代码预览为 HTML 的代码
- 页脚//包含正常的页脚代码
/* 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;
在这个组件中,我们使用 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> & <a href="https://www.typescriptlang.org/" target="_blank">TypeScript</a></span>
</div>
</footer>
)
}
export default Footer;
页脚组件包含简单的代码来呈现常见的信用内容。
/* 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;
因为,前面组件中的一些代码看起来可能很熟悉,现在您可以自己理解了。除此之外,我们使用了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;
这是一个简单的组件,用于
<textarea/>为用户提供一种输入方式,这些输入必须进一步编译才能在预览组件中呈现为 HTML 内容。
现在,除了预览组件之外,我们已经创建了几乎所有用于保存代码的组件。
我们需要一些东西来将用户的 Markdown 内容编译为简单的 HTML,但我们不想编写所有的编译器代码,因为我们有很多选项可供选择。
在这个应用中,我们将使用marked库将 Markdown 内容编译为 HTML。因此,你需要通过运行以下命令来安装它:
npm i -S marked
或用纱线
yarn add marked
如果你想了解更多关于这个图书馆的信息,你可以在这里看到
让我们看看预览组件的代码
/* 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;
在这个组件中,我们编译 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;
在我们的 App 组件中,我们导入子组件并传递主题道具。
现在,如果您已遵循上述所有步骤,您将拥有一个正在运行的 markdown 编辑器应用程序,对于我使用的样式,您可以使用我提到的链接查看我的源代码。
现在,是时候为我们的项目创建 Github 操作,以便在每次推送到 master 时创建持续部署工作流。
4 通过 Github Actions 设置持续部署
我们将使用 Github 操作工作流在每次推送到 master 时构建和部署我们的 Web 应用程序。
由于这不是一个拥有生产和开发分支的企业应用程序,我将为主分支设置我的工作流程,但如果在未来的任何时候,您需要为您的企业应用程序设置 Github 操作工作流程,请小心处理分支。
为此,我们将遵循以下步骤:
- 在我们的项目根目录中创建一个文件夹
.github/workflows/,它将保存所有工作流程配置。 - 我们将使用
JamesIves/github-pages-deploy-action操作来部署我们的应用程序。 - 接下来,我们将在这里创建
.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.
gh-pages这个工作流程每次都会运行,我们将一些内容推送到 master 并通过分支部署应用程序。
让我们分解一下工作流文件
name: Build & deploy to GitHub Pages
on:
push:
branches:
- master
这定义了我们的工作流名称以及在其中运行作业的触发器。这里我们设置触发器来监听分支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.
这是我们工作流程中最重要的部分,它声明了jobs要完成的任务。配置中的一些行是不言自明的runs-on: ubuntu-latest,它定义了它将在哪个系统上运行。
- name: Checkout
uses: actions/checkout@v1
这是一个检出仓库的操作,在后续的工作中,我们将通过安装 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.
构建命令完成后,我们使用
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 错误:内部
