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