使用react实现一个简版的印象笔记App

一、效果图展示

使用react实现一个简版的印象笔记App_第1张图片

二、初始化项目

① 安装react脚手架工具

// 全局安装 create-react-app
sudo npm -g install create-react-app
// 通过create-react-app命令创建react项目
create-react-app evernote

除了通过全局安装create-react-app来创建react项目外,我们还可以通过npm init命令去创建,即

npm init react-app evernote

因为执行npm init命令的时候,会自动在init之后的包名加上create前缀,所以相当于安装并执行create-react-app包,所以我们再传入项目名称即可创建对应的react项目了。注意,react的项目名不能以大写字母开头

② 修改public下的index.html文件
react项目和vue项目一样也是单页面应用,所以public目录下也会有一个index.html页面,用于挂载react渲染的结果。由于我们的项目中会用到一些字体图标,所以我们需要把我们的字体链接对应的css引入进来,如:

// public/index.html


    
         
    

这个在线css链接可以到阿里巴巴iconfont字体库中找到,如图所示:
使用react实现一个简版的印象笔记App_第2张图片

③ 修改src/App.js文件
src/App.js是项目的根组件,我们将App.js中的代码删除,默认App是函数组件,我们这里改成类组件,因为我们需要让App组件拥有自己的状态,然后修改为如下代码:

import React from 'react';
import './App.css';

class App extends React.Component {
  render() {
    return (
      
hello evernote.
); } } export default App;

④ 修改src/App.css
将原来的App.css内容清空,这里样式就不做过多解释,然后修改如下:

.app-container {
  display: flex;
  height: 100%;
}
.app-left {
  width: 10%;
  min-width: 190px;
  background: #343434;
}
.app-left-header, .app-left-body-title {
  display: flex;
  align-items: center;
  color: white;
  font-weight: bold;
  padding: 10px 0;
}
.add {
  width: 25px;
  height: 25px;
  display: inline-block;
  border-radius: 50%;
  margin: 0 10px;
  background:#6fcb66;
  text-align: center;
  line-height: 25px;
  font-size: 15px;
  font-weight: bold;
}
.notebook-icon {
  height: 25px;
  text-align: center;
  line-height: 25px;
  margin: 0 5px 0 10px;
}
.notebook-list, .app-center-list{
  margin: 0;
  list-style: none;
  padding: 0;
  color: white;
}
.notebook-list li {
  margin-top: 10px;
  font-size: 14px;
  display: flex;
  padding: 5px 0  5px 25px;
}
.notebook-list .active {
  background: #1a1a1a;
}
.notebook-list li i {
  margin-right: 3px;
}
.app-center{
  width: 12%;
  min-width: 180px;
  background: #ececec;
  display: flex;
  flex-direction: column;
}
.app-center-header {
  padding: 10px 0px 10px 10px;
  font-weight: bold;
  border-bottom: 1px solid #ccc;
}
.app-center-list {
  padding: 0;
  height: 100%;
  overflow: scroll;
}
.app-center-list .active {
  background: yellow;
}
.app-center-list li {
  margin: 5px 10px;
  height: 180px;
  background: white;
  color: black;
}
.app-center-list-item .note-header {
  font-weight: bold;
  text-align: center;
  height: 30px;
  line-height: 30px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  padding-left: 5px;
  font-size: 12px;
}
.app-center-list-item .note-content {
  padding: 0px 10px;
  font-size: 13px;
  line-height: 22px;
  overflow: hidden;
  height: 130px;
  line-clamp: 3;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
}

.app-right {
  width: 78%;
  display: flex;
  flex-direction: column;
}
.app-right-header {
  height: 30px;
  line-height: 30px;
  padding-left: 10px;
  border-bottom: 1px solid #ccc;
}
.app-right-header .notebookName {
  margin-left: 5px;
  font-weight: bolder;
  font-size: 13px;
}
.app-right-title {
  height: 20px;
  line-height: 20px;
  padding: 8px 0 8px 30px;
  font-weight: bold;
  outline: none;
}
.app-right-content {
  display: flex;
  flex: 1;
  overflow: scroll;
}
.app-right-edit {
  background: #22272a;
  width: 50%;
  color: white;
  font-size: 15px;
  line-height: 25px;
  padding: 10px 0 0 10px;
  outline: none;
  resize: none;
}
.app-right-show {
  border-top: 1px solid #ccc;
  width: 50%;
  padding-left: 20px;
  overflow: scroll;
}

这里为了方便,将所有样式都写到了,App.css中,这样其他子组件都可以共享App.css中定义的样式了。

④ 修改index.css
为了让App组件能够占满全屏,需要对html、body、#root进行高度100%的设置,如:

html,body{
    height: 100%;
}
#root {
  height: 100%;
}

至此,evernote项目已经初始化完成。

三、项目分析

从效果图上可以看到,整个印象笔记分为左、中、右三块,所以我们可以把它们分成左、中、右三个组件。左边用于显示笔记本列表中间用于显示每个笔记本下的笔记列表右侧用于显示当前正在查看和编辑的笔记。所以在src目录下新建一个components目录,用于存放这三个组件。
// src/components/Left.js

import React from "react";

class Left extends React.Component {
    render() {
        return (
            
我是左边
); } } export default Left;

// src/components/Center.js

import React from "react";

class Center extends React.Component {
    render() {
        return (
            
我是中间
); } } export default Center;

// src/components/Right.js

import React from "react";

class Right extends React.Component {
    render() {
        return (
            
我是右边
); } } export default Right;

同时在App.js中引入这三个组件,如:
// src/App.js

class App extends React.Component {
  render() {
    return (
      
); } }

四、模拟数据

为了简单实现印象笔记在线操作功能,我们这里就不连接数据库了,而是采用json-server来模拟数据库,首先全局安装json-server模块,如:

sudo npm install -g json-server

然后在src目录下新建一个data目录,里面放一个db.json文件,内容如下:

{
  "notebooks": [
    {
      "id": 1,
      "name": "默认笔记本"
    },
    {
      "id": 2,
      "name": "我的2019"
    },
    {
      "id": 3,
      "name": "我的2020"
    }
  ],
  "notes": [
    {
      "id": 1,
      "title": "这是一段代码",
      "content": "## 一段react的示例代码\n## 安装react\n```\nnpm install react --save-dev\nnpm install react-dowm --save-dev\n```\n### JS部分\n```\n// 引入react\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\n// 定义函数组件\nfunction App() {\n   return (

hello react !

)\n}\n// 渲染组件到根节点\nReactDOM.render(, document.getElementById(\"root\"));\n```\n\n### html部分\n```\n\n\n \n
\n
\n\n```", "bookId": 1 }, { "id": 2, "title": "2019好笑的笑话 最好能笑死别人", "content": "## 第一个\n> 朋友去唱歌找了小姐,买单时发现没有现金只有银行卡,服务员说:“可以刷卡。”他说:“这卡是我老婆名字,刷了收到你们KTV短信,会打死我的。”服务员说:“没事,我们可以帮你刷成饭店消费。”此男一听很开心然后就刷了…结果刚一进家门老婆劈里啪啦两个大耳光,把手机短信给他看:“沙县小吃,消费8333元,你没有撑死?”\n\n## 第二个\n> 有一次,一个男人准备去办公室工作,他听说来了很多美女,就把自己打扮得漂漂亮亮,到了办公室,美女们都对男人微笑,他认为美女觉得自己很帅,其实,他的牙上沾了一颗红的和一颗绿的辣椒皮!\n\n## 第三个\n> 女婿跟老丈人抱怨,我老婆开着80多W的车,穿着几千的衣服, 用着苹果X,我天天电动车,一年四季2件外衣,用着淘汰的老年机,你知道我多惨吗? 只见老丈人神色淡定的说道:你天天睡着个开80多万的车,穿着几千块衣服,手拿苹果X的女人,还有什么不满意的!\n\n## 第四个\n> 前几天哥几个晚上去网吧玩,凌晨三点多回家,都离家比较近走着回去的,走到一半一哥们在路边冲着一棵树小解,我们走前面,他解完就追上我们,然后我说 你尿尿的姿势不对呀!哥们一脸懵逼,问我:怎么不对了?我:你应该抬起一条腿!然后就被追杀7条街!\n\n## 第五个\n> 某君儿子没考上大学,便找到在国企做董事长的老同学。董事长很爽快:让他来做副总经理吧,月薪五万,每天例行开会就行了。某君:给个一般职位就行了。董事长:做总经理助理吧,月薪2万,给总经理倒倒茶就行了。某君:还是从普通业务员做起吧。董事长:我们的业务员起码要硕士学历,薪水很低,还欠薪!\n\n## 第六个\n> 一女同事和我住同一个小区,有时我蹭她的车,有时她蹭我的车,经常一起回到公司。昨天公司门卫跟我说:“我看到你媳妇在外面搂着个男的。”我知道他误会女同事是我媳妇了,故意逗他说:“她这人就是贪玩,我也管不了。”今天女同事回来,说门卫给了她一朵玫瑰。\n\n## 第七个\n> 一新兵跑步老落后腿。班长问:为何老最后?新兵答:报告班长,吃饭时间只有二分钟,没吃饱跑不动。第二天,班长让新兵吃了个饱。结果那新兵还是跑了个倒数第一。班长问:咋还落后腿。新兵答:报告班长。吃太撑了。\n\n## 第八个\n> 我读小学的时候迟到、逃学、打架什么坏事都干,反正就是一个〝万人嫌〞,所以罚站罚跪就成了家常便饭。每次罚完跪,我都是哭着趴在姐姐背上回家的。有一次姐姐心疼的对我说:小冰呀,你以后还是带着爸爸的护膝上学吧!\n\n## 第九个\n> 我手机背景换了几百次了,老公的依旧是我的大脸照。我说:不会换张美女啊什么的,我不会不开心的?这二货道:这样很好,可以控制玩手机的欲望!\n\n## 第十个\n> 一日,和哥们去一个没去过的地方吃饭,找不到那个地方,看见路边有个协警(背对着),看身材貌似是中年妇女。于是哥们冲过去说道:“阿姨,XXX饭店怎么走。”那个协警黑着脸转过身来,原来是个大叔,但他还是指了路。哥们听完很感激,继续说:“谢谢阿姨……”\n本人一直单身,不知道为什么怎么也找不到对象。最近的几天遇上十年一遇的高温,很让人难受。我们公司的一位漂亮妹纸有天终于热得受不了了。她就问我:你住的那里有空调吗?我:有啊。妹纸:那我以后中午去你那休息,蹭一下空调好不好。我:当然不行,电费这么贵。然后就没有然后了...\n", "time": "2019-12-16", "bookId": 1 }, { "id": 3, "title": "2019年度最沙雕新闻你觉得是哪一个?", "content": "## 2019年度最沙雕新闻你觉得是哪一个?\n> 2019年度最沙雕新闻你觉得是哪一个? 1 男子带了一顶绿帽去盗窃,帽子上面写的“忘了他吧,我偷电瓶车养你”,最后因为这个帽子太鲜明被警方抓获。男子说买这个帽子是在地摊上看到,觉得很符合自己又穷又没有爱情的心境,没想到电瓶车没偷,女孩也没有跟我走就被抓了。 2 警察抓毒贩时,毒贩正在看以扫毒为主题的电视剧《破冰行动》,网友称电视剧来到现实。后因网络反响强烈,记者追问已被拘捕的毒贩对电视剧的评价,毒贩说主演演得很好。 3 女子收电信诈骗电话,将卡号密码告诉了犯罪分子,银行察觉异常叫了警察,后来警察发现女子因为记性不好给了错误密码而避免了损失。 4 男子报警称自己的山地自行车被偷,民警处理发现这车本来是报警的男子偷来的,被真车主碰上后在警察协助下骑走。警察问报警男子为何自投罗网,偷车男子说骑了一段时间也有感情了。 5 男子和邻居吵架,怕对方人多势众自己吃亏所以报警,民警做笔录发现报警男子是逃犯。 6 男子接到电话称自己被网上追逃,到派出所查询想证明清白,民警一查确为追逃对象将其拘捕。 7 四川南充民警准备抓特大电信诈骗案件嫌疑人,但强行破门有风险,后发现嫌疑人的钥匙插在门锁上忘拔,最后民警顺利破门抓获嫌疑人。 8 两男子凌晨潜入串串店自行搭配锅底偷吃串串,连吃三天后老板才发现店里进贼,第四天两男子来偷吃终于被潜伏民警抓获。 9 小偷入室盗窃,房主年纪大不会用手机报警,小偷:我帮你打110吧。最后被刑拘。 10 成都一女子要跳楼,一男子在对面看热闹不慎失足坠楼死亡,女子看到后觉得太吓人了放弃跳楼。 11 男子报警称被车压到脚,周围多个路人帮忙作证要求车主赔钱。男子善解人意说没啥大事赔5000就行,后来调监控发现是他自己把脚伸车轮下,而且路人都是同伙。", "time": "2019-12-16", "bookId": 1 }, { "id": 4, "title": "2019年我的美好回忆", "content": "## 2019年我的美好回忆\n> 时光匆匆,岁月悠悠,春去冬来,不知不觉,2019年即将过去了,2020年就要到来了。\n回忆即将过去的一年,我有甜、有酸、有苦、也有乐,但所有的甜与酸苦与乐,都在岁月的流逝中化作了醇厚的浓香,令人回味无穷。\n2019,是丰盈而充实的一年,收获了一缕阳光,一股温暖,一片友情。使我的晚年生活更加充实,更加幸福,更加美好。\n\n> 这一年,我怀着一颗热爱美篇的心,虚心学习,努力写作。编写了四十多篇文章和六十多条话题投稿美篇后,有八篇文章被美篇录取加精,有四篇文章被加荐。其中有一篇《我是怎样制作美篇文章的?》文章,被《美篇手册》录取,收录在“美友经验分享区”栏目里,引起了很大的反响,美友阅读量突破46000人,点赞留评人数达到1400多人。\n这一小小的收获,除了自己努力之外,离不开众人的帮助。在这里要感谢美篇平台的支持和关心,感谢《原创笔记》等圈子的帮助和鼓励,特别要感谢美友们的到访与点评。", "time": "2019-12-26", "bookId": 2 }, { "id": 5, "title": "怎样才能娶到一个好老婆啊", "content": "## 怎样才能娶到一个好老婆啊\n> 只要你们感情好。在一起时间长了不会觉得很烦。你爱他。他也爱你。那样生活起来才不会觉得累。那样才会永远相爱一辈子。这才叫娶个好老婆。\n\n> 如果你娶个很贤惠而且什么都好的老婆。你不喜欢她。那日子也不会长久。不会长久的生活还在乎娶什么老婆吗?衷心的祝福你能找到个诚心如意的老婆。男人年轻时,选老婆或选女友,\n第一都是看身材和脸蛋,人品性格和脾气通通不管;到了中年时,才会发现:原来,女人的美,不在外表,而在具有包容心和好脾气的个性,尤其是会撒娇的女人,一旦撒娇撒到男人的死穴,也就是打中了男人心坎里的弱点,这时,就算她要男人去死,男人也会带着微笑和满足的表情从容就义。\n男人要的只是一种类似母爱的包容和关怀,一种无怨无悔、夫唱妇随的契合感觉,我并非把女人当跟班或第二性,也不是歧视女性,真的,男人要的就只是那种即使自己再落魄再倒霉,她也不弃不离的那种生死相随的感动", "time": "2019-12-31", "bookId": 3 } ] }

此时再通过json-server去启动并监听db.json文件,就可以以REST API的形式访问db.json中的数据了,如:

json-server --watch ./src/data/db.json --port 8080

比如,我们在浏览器中通过http://localhost:8080/notebooks即可访问到db.json文件中notebooks对应的数组数据了。

这里解释一下db.json中的数据结构,notebooks表示的是有哪些笔记本,notes表示的是有哪些比较,其中有一个bookId字段表示其是属于哪个笔记本下。

五、开发Left组件

我们这里把所有的状态数据都放在App根组件上,然后通过props传递到子组件上,Left组件要显示所有的笔记本列表,所有需要notebooks这个数组以及当前选择的是哪个笔记本,所有需要知道当前笔记本索引currentIndex,在App组件上新增notebooks和currentIndex两个状态数据,我们可以在App组件挂载完成的时候去获取所有的笔记本列表,请求数据使用axios,如:

npm install axios --save-dev

// src/App.js 新增notebooks和currentIndex两个状态属性

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
          notebooks: [], // 存放笔记本列表
          currentIndex: 0, // 默认显示第一个笔记本
        }
    }
    componentDidMount() {
        axios.get("http://localhost:8080/notebooks").then((res) => {
          this.setState({
            notebooks: res.data // 更新笔记本列表
          });
        });
    }
    render() {
        return (
          
); } }

此时App组件向Left组件中传递了notebooks笔记本列表数组和currentIndex当前笔记本索引两个数据,然后开始开发Left组件,
// src/components/Left.js

import React from "react";

class Left extends React.Component {
    render() {
        return (
            
新建笔记
笔记本
    { this.props.notebooks.map((notebook, index) => { return (
  • {notebook.name}
  • ) }) }
); } } export default Left;

六、开发Center组件

Center组件用于显示笔记列表,所以需要把当前选择的比较本下的所有笔记列表传递给Center组件,在App组件中新建一个notes状态属性,同样是在componentDidMount的时候根据currentIndex获取到当前笔记本下的所有笔记,如:
// src/App.js

// 在state中新增一个notes属性
this.state = {
    notes: [], // 笔记列表 
}
// 新增一个getNotes方法用于根据笔记本的id获取其下的所有笔记列表
getNotes(bookId) { // 根据bookId获取笔记列表
    axios.get(`http://localhost:8080/notes?bookId=${bookId}`).then((res) => {
        this.setState({
          notes: res.data // 将获取到的比较列表存放到notes状态中
        });
    });
 }
// 修改componentDidMount,在获取到笔记本列表后,根据currentIndex获取到对应的笔记本信息,然后根据其id获取笔记列表
componentDidMount() {
    axios.get("http://localhost:8080/notebooks").then((res) => {
      this.setState({
        notebooks: res.data
      }); // 更新笔记本列表
      const notebook = this.state.notebooks[this.state.currentIndex];// 取出当前索引对应的笔记本
      this.getNotes(notebook.id); // 根据当前笔记本的id去获取笔记列表
    });
}
// 将notes笔记列表传递给Center组件

// src/component/Center.js

import React from "react";

class Center extends React.Component {
    render() {
        return (
            
笔记列表
    { this.props.notes.map((note, index) => { return (
  • {note.title}
    {note.content}
  • ) }) }
); } } export default Center;

七、开发Right组件

Right组件主要用于显示选择的笔记内容,所以需要将当前选择的笔记传递给Right组件,App中新增一个currentNote状态属性,用于保存当前选择的笔记,由于Right组件中还需要显示所在的笔记本名称,而这些信息都在notebooks中,所以也需要传递notebooks和currentIndex,如:
// src/App.js

// 新增currentNote状态属性
this.state = {
    currentNote: null // 当前显示的笔记
}
// 如果选择了某个笔记,才会在右边区域显示具体的笔记内容
 {
          this.state.currentNote ? 
             : null
}

// src/component/Right.js

import React from "react";

class Right extends React.Component {
    render() {
        return(
            
{this.props.notebooks[this.props.currentIndex].name}