自学内容网 自学内容网

用 React 编写一个笔记应用程序

这篇文章会教大家用 React 编写一个笔记应用程序。用户可以创建、编辑、和切换 Markdown 笔记。
在这里插入图片描述
在这里插入图片描述

1. nanoid

nanoid 是一个轻量级和安全的唯一字符串ID生成器,常用于JavaScript环境中生成随机、唯一的字符串ID,如数据库主键、会话ID、文件名等场景。

2. React-split

react-split 是一个 React 组件库,它提供了一个简单易用的界面来分割面板,允许用户通过拖动来调整面板的大小。这个库非常适合需要在界面上展示可调整大小的多个区域的场景,比如编辑器、IDE、仪表板等。

import Split from "react-split";

<Split>
  <div>Panel 1</div>
  <div>Panel 2</div>
</Split>

3. react-mde

react-mde 是一个用于 React 的 Markdown 编辑器组件,它基于 Draft.js 构建,提供了一个功能强大且可扩展的界面,允许用户以 Markdown 语法或可视化方式编辑文本。

npm install react-mde --legacy-peer-deps

4. showdown

Showdown 是一个 JavaScript 库,用于将 Markdown 文本转换为 HTML。它是一个广泛使用的库,因为它兼容性好、速度快,且易于集成和使用。

5. App.jsx

这是一个用 React 编写的笔记应用程序的主组件 App。用户可以创建、编辑、和切换 Markdown 笔记。它使用了以下功能与库:

  • React:实现状态管理和组件化。
  • nanoid:生成唯一的 ID,用于每个笔记。
  • react-split:实现分屏布局(侧边栏和编辑器)。
  • 自定义组件:
    – Sidebar:显示笔记列表和用于切换当前笔记。
    – Editor:用于编辑当前笔记的内容。

主要功能

  1. 创建新笔记
  2. 更新笔记内容
  3. 切换当前笔记
  4. 显示分屏布局
  5. 无笔记时显示提示
import React from "react";
import { nanoid } from "nanoid";
import Split from "react-split";
import Sidebar from "../public/components/Sidebar";
import Editor from "../public/components/Editor";

  • React:提供 useState 和其他功能,用于创建 React 组件。
  • nanoid:生成唯一的 ID,用于标识每条笔记。
  • Split:用于实现分屏布局(侧边栏 + 编辑器)。
  • Sidebar 和 Editor 是自定义的子组件,分别处理笔记列表和笔记编辑。

状态管理

const [notes, setNotes] = React.useState([]);
const [currentNoteId, setCurrentNoteId] = React.useState(
  (notes[0] && notes[0].id) || ""
);

  • notes:存储所有笔记的状态,初始值为空数组。
  • currentNoteId:存储当前笔记的 ID,初始值为第一个笔记的 ID(若无笔记,则为空字符串)。

创建新笔记

function createNewNote() {
  const newNote = {
    id: nanoid(),
    body: "# Type your markdown note's title here"
  };
  setNotes(prevNotes => [newNote, ...prevNotes]);
  setCurrentNoteId(newNote.id);
}

  • 使用 nanoid 生成一个唯一的 ID。
  • 创建一个新笔记对象,默认内容为 “# Type your markdown note’s title here”。
  • 将新笔记添加到 notes 的顶部。
  • 更新 currentNoteId 为新笔记的 ID

更新笔记

function updateNote(text) {
  setNotes(oldNotes => oldNotes.map(oldNote => {
    return oldNote.id === currentNoteId
      ? { ...oldNote, body: text }
      : oldNote;
  }));
}

  • 遍历 notes,检查每条笔记的 ID。
  • 如果某条笔记的 ID 与 currentNoteId 相同,则更新其 body 内容。
  • 使用 ...oldNote 保留笔记其他的属性,更新 body

查找当前笔记

function findCurrentNote() {
  return notes.find(note => {
    return note.id === currentNoteId;
  }) || notes[0];
}

  • 查找与 currentNoteId 匹配的笔记。
  • 如果没有匹配到,返回第一条笔记(默认情况)。
return (
  <main>
    {notes.length > 0 ? (
      <Split
        sizes={[30, 70]}
        direction="horizontal"
        className="split"
      >
        <Sidebar
          notes={notes}
          currentNote={findCurrentNote()}
          setCurrentNoteId={setCurrentNoteId}
          newNote={createNewNote}
        />
        {currentNoteId && notes.length > 0 && (
          <Editor
            currentNote={findCurrentNote()}
            updateNote={updateNote}
          />
        )}
      </Split>
    ) : (
      <div className="no-notes">
        <h1>You have no notes</h1>
        <button
          className="first-note"
          onClick={createNewNote}
        >
          Create one now
        </button>
      </div>
    )}
  </main>
);

有笔记时的渲染:

  • 使用 Split 实现水平分屏,30% 分给侧边栏(Sidebar),70% 分给编辑器(Editor)。
  • Sidebar:显示笔记列表,并支持切换当前笔记或创建新笔记。
  • Editor:编辑当前笔记的内容。

无笔记时的渲染:

  • 显示提示文字 You have no notes
  • 提供一个按钮 Create one now,单击后调用 createNewNote 创建第一条笔记。

6. Sidebar.jsx

这是一个 React 组件 Sidebar,用于显示笔记的侧边栏列表,并允许用户切换当前笔记或创建新笔记。此组件通过 props 接收父组件传递的状态和函数来实现动态渲染和交互。

import React from "react";

export default function Sidebar(props) {

  • React:用于支持 React 的 JSX 语法。
  • export default:将 Sidebar 组件作为默认导出,使其可被其他模块导入。

Sidebar 是一个无状态函数组件,接收 props 参数(即父组件传递的数据)。

生成笔记元素

const noteElements = props.notes.map((note, index) => (
    <div key={note.id}>
        <div
            className={`title ${
                note.id === props.currentNote.id ? "selected-note" : ""  
            }`}
            onClick={() => props.setCurrentNoteId(note.id)}
        >
            <h4 className="text-snippet">Note {index + 1}</h4>
        </div>
    </div>
));

  1. props.notes
  • 父组件传入的笔记数组,每个笔记对象包含 idbody 属性。
  1. props.notes.map()
  • 遍历笔记数组,为每条笔记生成一个 JSX 元素。
  • key={note.id}:为每个顶层元素提供唯一的 key 属性,以优化 React 的渲染性能。
  1. 条件渲染类名:
  • className={title ${note.id === props.currentNote.id ? "selected-note" : ""}}
  • 如果当前笔记的 id 与 props.currentNote.id 匹配,添加 selected-note 类名。
  • 用于高亮显示当前选中的笔记。
  1. 点击事件:
  • onClick={() => props.setCurrentNoteId(note.id)}
  • 点击笔记时调用父组件传入的 setCurrentNoteId 方法,更新当前笔记的 id。
  1. 显示笔记编号:
  • <h4 className="text-snippet">Note {index + 1}</h4>:动态显示笔记的编号(从 1 开始)。

侧边栏结构

return (
    <section className="pane sidebar">
        <div className="sidebar--header">
            <h3>Notes</h3>
            <button 
                className="new-note"
                onClick={props.newNote}
            >+</button>
        </div>
        {noteElements}
    </section>
);

  1. 侧边栏头部:
  • <div className="sidebar--header">:包含标题和创建按钮。
  • 标题:<h3>Notes</h3>
  • 创建按钮:
  • <button className="new-note" onClick={props.newNote}>+</button>
    点击按钮调用父组件传入的 newNote 方法,创建一条新笔记。
  1. 笔记列表:
  • {noteElements}:动态渲染生成的笔记列表。

组件渲染逻辑总结

  1. 笔记渲染:
  • 遍历 props.notes,生成笔记列表。
  • 为选中的笔记添加 selected-note 类名。
  • 点击笔记切换当前选中的笔记。
  1. 笔记创建:
  • 提供一个按钮,点击后调用 props.newNote 创建新笔记。
  1. 动态更新:
  • 通过 props 与父组件共享状态,每次父组件状态更新时,Sidebar 会自动重新渲染。

7. Editor.jsx

此代码定义了一个名为 Editor 的 React 函数组件,用于实现 Markdown 文本编辑器。它通过 ReactMde(React Markdown Editor)库提供一个多功能的 Markdown 编辑和预览界面。

import React from "react"
import ReactMde from "react-mde"
import 'react-mde/lib/styles/css/react-mde-all.css';
import Showdown from "showdown"

  1. React:
  • 导入 React 库,支持 JSX 和组件开发。
  1. ReactMde:
  • 导入 React Markdown Editor,用于渲染 Markdown 编辑器。
  • ReactMde 提供了内置的编辑和预览功能。
  1. 样式文件:
  • 导入 react-mde 的样式文件,应用默认的编辑器样式。
  1. Showdown:
  • 导入 Showdown 库,用于将 Markdown 文本转换为 HTML。
export default function Editor({ currentNote, updateNote }) {
    const [selectedTab, setSelectedTab] = React.useState("write")

  1. 函数组件:
  • 定义一个函数组件 Editor,接收两个 props:
  • currentNote:当前笔记对象(包含 body 属性)。
  • updateNote:更新笔记内容的回调函数。
  1. 状态管理:
  • 使用 React 的 useState Hook 管理当前编辑器的选项卡状态:
  • selectedTab 的初始值为 “write”,表示当前默认显示“编辑”模式。
const convertor = new Showdown.Converter({
    tables: true,
    simplifiedAutoLink: true,
    strikethrough: true,
    tasklists: true,
})

  1. 创建 Showdown.Converter 实例:
  • 配置 Markdown 转换器,使其支持以下扩展功能:
  • tables: true:支持表格语法。
  • simplifiedAutoLink: true:自动将 URL 转换为超链接。
  • strikethrough: true:支持 删除线 语法。
  • tasklists: true:支持任务列表
  1. 作用:将 Markdown 文本转换为 HTML,以便在预览模式中显示。
return (
    <section className="pane editor">
        <ReactMde
            value={currentNote.body}
            onChange={updateNote}
            selectedTab={selectedTab}
            onTabChange={setSelectedTab}
            generateMarkdownPreview={(markdown) => 
                Promise.resolve(convertor.makeHtml(markdown))
            }
            minEditorHeight={80}
            heightUnits="vh"
        />
    </section>
)

ReactMde 编辑器:

  • value:绑定到 currentNote.body,表示当前笔记的内容。
  • onChange:当用户在编辑器中输入内容时,调用 updateNote 更新父组件的状态。
  • selectedTab:当前选中的选项卡(“write” 或 “preview”)。
  • onTabChange:切换选项卡时调用,更新 selectedTab 的状态。
  • generateMarkdownPreview:预览时调用,将 Markdown 文本转换为 HTML。使用 convertor.makeHtml(markdown) 完成转换,返回一个 Promise。
  • minEditorHeightheightUnits:设置编辑器的最小高度为 80,单位为 vh(视口高度)。

父组件如何与 Editor 交互

  • currentNote:父组件将当前笔记对象传递给 Editor。通过 currentNote.body 显示当前笔记的内容。
  • updateNote:父组件提供回调函数,用于更新笔记内容。

8. index.css

* {
  box-sizing: border-box;
}

1.* 通配符选择器:

  • 设置所有元素的 box-sizing 为 border-box。
  • 效果:元素的 width 和 height 属性包括内边距(padding)和边框(border),避免计算宽高时的复杂性。
body {
  margin: 0;
  padding: 0;
  font-family: 'Karla', sans-serif;
}

  1. body 标签:
  • 移除默认的外边距(margin)和内边距(padding)。
  • 设置全局字体为 Karla,使用无衬线字体(sans-serif)作为后备。
button:focus {
  outline: none;
}

  1. 按钮样式:移除按钮在获得焦点(focus)时的默认轮廓(outline)。
.ql-editor p,
.ql-editor.ql-blank::before {
  font-size: 1.3em;
  font-weight: 100;
}

ql-editor

  • 专为 ReactMde(Markdown 编辑器)定义。
  • 调整段落文字大小(1.3em)和字体粗细(100,非常细的字体)。
.pane {
  overflow-y: auto;
}

.pane

  • 应用于侧边栏和编辑器容器。
  • 启用垂直滚动(overflow-y: auto),以显示超出容器高度的内容。
.sidebar {
  width: 20%;
  height: 100vh;
}

.sidebar

  • 设置侧边栏宽度为 20%,高度占满整个视口(100vh)。
.editor {
  width: 80%;
  height: 100vh;
}

.editor

  • 设置编辑器宽度为 80%,高度占满整个视口。
.sidebar--header {
  display: flex;
  justify-content: space-around;
  align-items: center;
}

.sidebar--header
侧边栏标题区域:

  • 使用 flex 布局。
  • 水平排列子元素,且均匀分布(justify-content: space-around)。
  • 垂直居中对齐(align-items: center)。
.new-note, .first-note {
  cursor: pointer;
  background-color: #4A4E74;
  border: none;
  color: white;
  border-radius: 3px;
}

两类按钮(新建笔记 new-note 和初始笔记 first-note):

  • 指针悬停变成手型(cursor: pointer)。
  • 背景色为深紫色(#4A4E74),字体颜色为白色。
  • 圆角(border-radius: 3px)。
.title {
  overflow: hidden;
  width: 100%;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

单个笔记标题的样式:

  • 超出宽度的内容隐藏(overflow: hidden)。
  • 使用 flex 布局,子元素之间均匀分布。
.selected-note {
  background-color: #4A4E74;
}
.selected-note .text-snippet {
  color: white;
  font-weight: 700;
}

当前选中的笔记高亮:

  • 背景色变成深紫色。
  • 子元素 .text-snippet 的字体颜色变成白色,且加粗(font-weight: 700)。
.gutter {
  background-color: #eee;
  background-repeat: no-repeat;
  background-position: 50%;
}

.gutter

  • 分割侧边栏和编辑器的拖拽条。
  • 设置背景色为浅灰色。
.gutter.gutter-horizontal {
  background-image: url('data:image/png;base64,...');
}
.gutter.gutter-horizontal:hover {
  cursor: col-resize;
}

.gutter-horizontal
水平分割条的样式:

  • 使用 base64 图片作为背景。
  • 鼠标悬停时,显示水平调整光标(col-resize)。
.no-notes {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: whitesmoke;
}

当没有任何笔记时的提示区域:

  • 宽度和高度占满视口。
  • 子元素垂直排列(flex-direction: column),水平和垂直居中。
  • 背景色为浅白色(whitesmoke)。

原文地址:https://blog.csdn.net/weixin_57266891/article/details/144137391

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!