1.1 配置Electron开发环境
问题
electron在安装时卡在reify:@types/node: timing reifyNode:node_modules/global-agent Completed in 200ms的解决办法

1.2 创建 BrowserWindow
使用nodemon监控文件变化

每次这个main.js有修改的时候呢,都要这个关闭现在的命令呢,再重启一遍。应该经常会遇到这个问题,所以呢,需要下一个辅助工具来完成,让我们这个工作更轻松一些。就可以使用这个 nodemon 。它可以监控文件的这个变化,然后自动的运行这个命令,这样就省去我们手动的操作。不再需要关闭,再重启可以说是非常方便啊
1
   | nodemon --watch main.js --exec \"electron\"
   | 
 
写一个父子窗口


1.3 进程中的通信
安装Devtron插件

这个插件已经停止更新,不推荐使用


1.4 使用IPC进行通信
使用Electron的ipc模块在不同进程间进行通信。首先,安装了devtron插件,然后在主进程和渲染进程中使用ipc模块进行通信。具体来说,当点击按钮时,将事件发送到主进程,主进程再回话,渲染进程将消息输出到web界面上。在渲染进程中使用ipc renderer,通过send方法将事件发送到主进程。在主进程中使用ipc main监听事件,通过on方法添加事件监听器。当接收到来自渲染进程的消息时,主进程会打印出消息内容。IPC是Inter-Process Communication的缩写,用于在不同进程之间进行数据交换和事件传递。
1.5 使用remote实现跨进程访问
除了通过IPC发送事件的方式,渲染进程中有一个名为remote的模块,可以提供一种更简单的方法完成这个任务。在代码中,可以通过require.electron来使用remote模块,取出主进程中的特定API进行使用。例如,在渲染进程中,可以通过remote模块取出main process特有的browser window API,实现新建窗口的操作。此外,Electron框架允许开发者使用HTML、CSS和JavaScript等Web技术来构建跨平台的桌面应用程序。
2.1 npx是什么
优点:
3.1 需求分析

3.2 将ui拆分成组件

4.1 配置开发环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | const { app, BrowserWindow } = require('electron');
  let mainWindow;
  app.on('ready', async () => {          const isDev = await import('electron-is-dev');          mainWindow = new BrowserWindow({         width: 1024,         height: 680,         webPreferences: {             nodeIntegration: true,         }     });
      const urlLocation = isDev.default ? 'http://localhost:3000' : 'dummy';     mainWindow.loadURL(urlLocation); });
 
  | 
 
为什么需要动态 import
由于 electron-is-dev 是一个 ES 模块,它不能使用 CommonJS 的 require 语句。要在 CommonJS 模块中导入 ES 模块,必须使用动态 import,因为它可以在运行时导入模块,并且支持异步操作。
关键点:
- 异步函数: 
createWindow 被定义为异步函数,以便可以使用 await。 
- 动态导入: 使用 
await import('electron-is-dev') 导入 ES 模块。 
- 模块的 
default 属性: 动态导入返回一个模块对象,其中 default 属性是模块的默认导出。 
通过这种方式,可以在 CommonJS 环境中使用 ESM 模块,同时保持代码的同步执行逻辑。这解决了兼容性问题,并且不需要将整个项目转换为 ESM。
4.2 配置开发环境
安装concurrently

安装wait-on

安装cross-env

4.3 选择样式库 bootstrap
4-4 选择图标库 fontawesome
4-5 第一个组件 FileSearch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
   | import React, {useState, useEffect, useRef} from 'react' import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' import {faSearch,faTimes} from '@fortawesome/free-solid-svg-icons'
  const FileSearch = ({ title, onFileSearch }) => {     const [inputActive, setInputActive] = useState(false)     const [value, setValue] = useState('')     let node = useRef(null)     const closeSearch =(e) =>{         e.preventDefault()         setInputActive(false)         setValue('')     }     useEffect(() => {         const handleInputEvent = (event) =>{             const {keyCode} = event             if (keyCode === 13 && inputActive) {                 onFileSearch(value)             }else if(keyCode === 27 && inputActive) {                 closeSearch(event)             }         }         document.addEventListener('keyup', handleInputEvent)         return () =>{             document.removeEventListener('keyup', handleInputEvent)         }     });     useEffect(()=>{         if (inputActive) {             node.current.focus()         }     },[inputActive])     return (         <div className="alert alert-primary d-flex justify-content-between align-items-center">             {                 !inputActive &&                 <>                     <span>{title}</span>                     <button                         type="button"                         className="icon-button"                         onClick={() => setInputActive(true)}                     >                        <FontAwesomeIcon title="搜索" size="lg" icon={faSearch} />                     </button>                 </>}             {                 inputActive &&                 <>                     <input                         className="form-control"                         value={value}                         ref={node}                         onChange={e => setValue(e.target.value)}                     />                     <button                         type="button"                         className="icon-button"                         onClick={closeSearch}                     >                         <FontAwesomeIcon title="关闭" size="lg" icon={faTimes} />                     </button>                 </>             }         </div>     ) } export default FileSearch
  | 
 
4.6 使用PropTypes检查属性类型

FileSearch.propTypes 使用 PropTypes 进行类型检查和属性验证。它指定了 title 必须是一个字符串,onFileSearch 必须是一个函数,并且是必需的(即,如果没有传入 onFileSearch,会在开发模式下产生警告)。 
FileSearch.defaultProps 设置了 FileSearch 组件在未接收到 title 属性时的默认值,这里设置为 '我的云文档'。 
这种方式有助于确保组件在使用时,能够获得正确的属性类型,并提供了默认值以防止未定义属性值的错误。
4.7 第一个自定义Hook 键盘事件useKeyPress
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | import { useState, useEffect} from "react"; const useKeyPress = (targetKeyCode) => {     const [keyPressed,setKeyPressed] = useState(false);
      const keyDownHandler = ({keyCode}) => {         if(keyCode ===targetKeyCode){             setKeyPressed(true);         }     }     const keyUpHandler = ({keyCode}) => {         if(keyCode === targetKeyCode){             setKeyPressed(false);         }     }
      useEffect(()=>{         document.addEventListener('keydown', keyDownHandler);         document.addEventListener('keyup', keyUpHandler);         return ()=>{             document.removeEventListener('keydown', keyDownHandler);             document.removeEventListener('keyup', keyUpHandler);         }     },[])
      return keyPressed }
  export default useKeyPress;
  | 
 
4.8 第二个组件 FileList
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
   | import React, {useState, useEffect} from 'react' import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' import {faEdit, faTimes, faTrash} from '@fortawesome/free-solid-svg-icons' import {faMarkdown} from '@fortawesome/free-brands-svg-icons' import PropTypes from "prop-types"; import useKeyPress from "../hooks/useKeyPress";
  const FileList = ({ files,onFileClick,onSaveEdit,onFileDelete}) => {     const [editStatus, setEditStatus] = useState(false);     const [value, setValue] = useState('');     const enterPressed = useKeyPress(13)     const escPressed = useKeyPress(27)     const closeSearch =() =>{         setEditStatus(false)         setValue('')     }
      useEffect(() => {         if(enterPressed && editStatus ){             const editItem = files.find(item => item.id === editStatus)             onSaveEdit(editItem.id,value)             setEditStatus(false);             setValue('')         }         if(escPressed && editStatus ){             closeSearch()         }                                                                                                                 });     return(         <ul className="list-group list-group-flush file-list">             {                 files.map(file => (                     <li className="list-group-item bg-light row d-flex align-items-center file-item"                         key={file.id}>                         {                             (file.id !== editStatus) &&                             <>                         <span className="col-2">                             <FontAwesomeIcon size="lg" icon={faMarkdown}/>                         </span>                         <span className="col-8 c-link"                               onClick={()=>onFileClick(file.id)}>{file.title}                         </span>                         <button                             type="button"                             className="icon-button col-1"                             onClick={() => {setEditStatus(file.id);setValue(file.title)}}>                             <FontAwesomeIcon title="编辑" size="lg" icon={faEdit}/>                         </button>                         <button                             type="button"                             className="icon-button col-1"                             onClick={() => onFileDelete(file.id)}>                             <FontAwesomeIcon title="删除" size="lg" icon={faTrash}/>                         </button>                         </>                         }                         {                             (file.id === editStatus) &&                             <>                                 <input                                     className="form-control col-10"                                     value={value}                                     onChange={e => setValue(e.target.value)}                                 />                                 <button                                     type="button"                                     className="icon-button col-2"                                     onClick={closeSearch}                                 >                                     <FontAwesomeIcon title="关闭" size="lg" icon={faTimes}/>                                 </button>                             </>                         }                     </li>                 ))             }         </ul>     ) }
  FileList.propTypes = {     files: PropTypes.array,     onFileClick: PropTypes.func,     onFileDelete: PropTypes.func,     onSaveEdit: PropTypes.func, }
  export default FileList;
  | 
 
5.1 第三个组件 TabList
需求分析

一种思路:
将状态作为属性传入给组件

另一种思路:
将状态定义在文件属性上,传入文件给组件

5.2  第三个组件 TabList 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | import React from 'react' import PropTypes from "prop-types"; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' import {faTimes} from '@fortawesome/free-solid-svg-icons' import classNames from "classnames"; import './TabList.scss'
  const TabList = ({ files,activeId,unSaveIds,onTabClick,onCloseTab}) => {     return (         <ul className="nav nav-pills tablist-component">             {files.map(file => {                 const withUnsavedMark = unSaveIds.includes(file.id)                 const fClassName = classNames({                     'nav-link':true,                     'active': file.id === activeId,                     'withUnsaved': withUnsavedMark                 })                 return(                     <li className="nav-item" key={file.id}>                         <a href="#"                            className={fClassName}                            onClick={(e) => {                                e.preventDefault();                                onTabClick(file.id)                            }}>                             {file.title}                             <span                                 className="ml-2 close-icon" onClick={(e)=>{e.stopPropagation();onCloseTab(file.id)}}>                                 <FontAwesomeIcon  icon={faTimes}/>                             </span>                             {withUnsavedMark && <span className="rounded-circle unsaved-icon ml-2"></span>}                         </a>                     </li>                 )             })}         </ul>     ) }
 
  TabList.propTypes = {     files: PropTypes.array,     activeId: PropTypes.string,     unSaveIds: PropTypes.array,     onTabClick: PropTypes.func,     onCloseTab: PropTypes.func, }
  TabList.defaultProps = {     unSaveIds: [], } export default TabList
   | 
 
中间遇到了bootstrap版本问题,教程使用的是b4版本,而我安装的是b5版本,所以重新安装b4,样式问题得到解决。
5.3 Markdown编辑器的基本需求
引入easyMDE编辑器



6.1 分析设计State结构




6.2 分析应用数据流


6.3