用 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 ? (<Splitsizes={[30, 70]}direction="horizontal"className="split"><Sidebarnotes={notes}currentNote={findCurrentNote()}setCurrentNoteId={setCurrentNoteId}newNote={createNewNote}/>{currentNoteId && notes.length > 0 && (<EditorcurrentNote={findCurrentNote()}updateNote={updateNote}/>)}</Split>) : (<div className="no-notes"><h1>You have no notes</h1><buttonclassName="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}><divclassName={`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"><ReactMdevalue={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)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/61609.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux探索学习】第十八弹——进程等待:深入解析操作系统中的进程等待机制

Linux学习笔记&#xff1a;https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 在Linux操作系统中&#xff0c;进程是资源的管理和执行单元&#xff0c;每个进程都有其自己的生命周期。在进程的执行过程中&#xff0c;进程…

大数据技术Kafka详解 ② | Kafka基础与架构介绍

目录 1、kafka的基本介绍 2、kafka的好处 3、分布式发布与订阅系统 4、kafka的主要应用场景 4.1、指标分析 4.2、日志聚合解决方法 4.3、流式处理 5、kafka架构 6、kafka主要组件 6.1、producer(生产者) 6.2、topic(主题) 6.3、partition(分区) 6.4、consumer(消费…

嵌入式Linux之wifi配网脚本分析

嵌入式Linux系统,一般都支持wifi联网,可以通过sh脚本或其它语言代码编程来实现wifi联网。 本篇来介绍一种通过sh脚本来配置wifi的脚本执行原理。 1 sh脚本wifi联网介绍 这里以飞凌开发板中的wifi启动脚本为例来介绍。 在飞凌开发板的串口中,执行如下命令(调用fltest_wif…

如何通过 JWT 来解决登录认证问题

1. 问题引入 在登录功能的实现中 传统思路&#xff1a; 登录页面时把用户名和密码提交给服务器服务器验证用户名和密码&#xff0c;并把检验结果返回给后端如果密码正确&#xff0c;则在服务器端创建 session&#xff0c;通过 cookie 把 session id 返回给浏览器 但是正常情…

【Linux】文件操作的艺术——从基础到精通

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 道阻且长&#xff0c;行则将至 目录 &#x1f4da;前言&#xff1a;一切皆文件 &#x1f4da;一、C语言的文件接口 &#x1f4d6;1.文件打…

JAVA |日常开发中常见问题归纳讲解

JAVA &#xff5c;日常开发中常见问题归纳讲解 前言一、语法错误相关问题1.1 分号缺失或多余1.2 括号不匹配1.3 变量未定义或重复定义 二、数据类型相关问题2.1 数据类型不匹配2.2 整数溢出和浮点数精度问题 三、面向对象编程相关问题3.1 空指针异常&#xff08;NullPointerExc…

Git常用命令参考手册

Git常用命令参考手册 整理了一篇git常用的命令参考手册&#xff0c;命令顺序按照一个项目从头到尾的常用命令顺序做了排序&#xff0c;后续会继续完善内容示例并补全其他命令使用说明&#xff0c;希望对不熟悉的小伙伴有所帮助。 git config # 配置列表 git config --list #…

翰高4.5.7安装测试手册centos

翰高4.5.7centos版本安装 1 环境准备 产品包&#xff1a;hgdb4.5.7-see-centos7-x86-64-20210804.rpm 环境&#xff1a;10.1.5.172 root/sh88861158 操作系统安装完成后&#xff0c;执行常规操作&#xff1a;修改hosts、关闭防火墙、关闭senliunx 2 安装步骤 2.1 安装前准…

港科夜闻 |香港科大推出 InvestLM生成式人工智能平台,支持金融中小企应用AI技术潜力...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大推出 InvestLM生成式人工智能平台&#xff0c;支持金融中小企应用AI技术潜力。香港科大商学院继去年研究团队成功开发本港首个专为金融界而设、应用于生成式人工智能(生成式AI)的开源大语言模型InvestLM后&#…

【计算机网络】实验6:IPV4地址的构造超网及IP数据报

实验 6&#xff1a;IPV4地址的构造超网及IP数据报 一、 实验目的 加深对IPV4地址的构造超网&#xff08;无分类编制&#xff09;的了解。 加深对IP数据包的发送和转发流程的了解。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实验内容 1、了解IPV4地址的构造超网…

FreeRtos开发之计数信号量

前面介绍过了计数信号量的定义取值只有0与1两种状态的信号量称之为二值信号量 取值大于1的信号量称之为计数信号量 计数信号量的取值也可以为1&#xff0c;但通常大于1&#xff0c;如果取值为1&#xff0c;相当于只有0与1两种状态&#xff0c;用二值信号量即可。 计数信号量应用…

Profinet转EtherNet/IP网关是如何解决西门子S7-1500PLC与AB PLC的通讯问题的

一、 案例背景 在一个工业现场&#xff0c;一端是AB的PLC&#xff0c;IP地址192.168.1.20;另一端西门子是S7-1500系列&#xff0c;IP地址192.168.2.248。AB的PLC内有 B3、N7、F8 三个寄存器文件涉及到通讯&#xff0c;分别对应西门子PLC的M、DB1、DB2三个存储区域。通过捷米特…

【C语言】扫雷游戏(一)

我们先设计一个简单的9*9棋盘并有10个雷的扫雷游戏。 1&#xff0c;可以用数组存放&#xff0c;如果有雷就用1表示&#xff0c;没雷就用0表示。 2&#xff0c;排查(2,5)这个坐标时&#xff0c;我们访问周围的⼀圈8个位置黄色统计周围雷的个数是1。排查(8,6)这个坐标时&#xf…

Unity开发FPS游戏之完结篇

这个系列的前几篇文章介绍了如何从头开始用Unity开发一个FPS游戏&#xff0c;感兴趣的朋友可以回顾一下。这个系列的文章如下&#xff1a; Unity开发一个FPS游戏_unity 模仿开发fps 游戏-CSDN博客 Unity开发一个FPS游戏之二_unity 模仿开发fps 游戏-CSDN博客 Unity开发一个F…

浅析RPC—基础知识

该文章会简单介绍一下 RPC 相关的基础概念。 什么是RPC&#xff1f; RPC&#xff08;Remote Procedure Call&#xff09; 即远程过程调用&#xff0c;通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。 为什么要 RPC &#xff1f; 因为&#xff0c;两个不同的服务器…

mysql数据库varchar截断问题

用了这么多年mysql数据库&#xff0c;才发现varchar是可以截断的&#xff0c;而且是在我们线上数据库。个人觉得dba的这个设置是非常有问题的&#xff0c;用户往数据库里存东西&#xff0c;就是为了以后用的&#xff0c;截断了存放&#xff0c;数据不完整&#xff0c;就用不了了…

EwoMail邮箱服务器软件安装教程

EwoMail是基于Linux的开源邮件服务器软件,集成了众多优秀稳定的组件,是一个快速部署、简单高效、多语言、安全稳定的邮件解决方案,帮助你提升运维效率,降低 IT 成本,兼容主流的邮件客户端,同时支持电脑和手机邮件客户端。 一、系统版本 二、关闭selinux vi /etc/sysconf…

【机器学习】机器学习的基本分类-监督学习-支持向量机(Support Vector Machine, SVM)

支持向量机是一种强大的监督学习算法&#xff0c;主要用于分类问题&#xff0c;但也可以用于回归和异常检测。SVM 的核心思想是通过最大化分类边界的方式找到数据的最佳分离超平面。 1. 核心思想 目标 给定训练数据 &#xff0c;其中 是特征向量&#xff0c; 是标签&#xf…

Linux命令进阶·如何切换root以及回退、sudo命令、用户/用户组管理,以及解决创建用户不显示问题和Ubuntu不显示用户名只显示“$“符号问题

目录 1. root用户&#xff08;超级管理员&#xff09; 1.1 用于账户切换的系统命令——su 1.2 退回上一个用户命令——exit 1.3 普通命令临时授权root身份执行——sudo 1.3.1 为普通用户配置sudo认证 2. 用户/用户组管理 2.1 用户组管理 2.2 用户管理 2.2.1 …

Zero to JupyterHub with Kubernetes中篇 - Kubernetes 常规使用记录

前言&#xff1a;纯个人记录使用。 搭建 Zero to JupyterHub with Kubernetes 上篇 - Kubernetes 离线二进制部署。搭建 Zero to JupyterHub with Kubernetes 中篇 - Kubernetes 常规使用记录。搭建 Zero to JupyterHub with Kubernetes 下篇 - Jupyterhub on k8s。 参考&…