资讯专栏INFORMATION COLUMN

使用React、Electron、Dva、Webpack、Node.js、Websocket快速构建

Caicloud / 2711人阅读

摘要:是一个使用和等技术创建原生程序的框架,它负责比较难搞的部分,你只需把精力放在你的应用的核心上即可。谈谈技术选型使用去做底层的绘制,大项目首选状态管理的最佳实践肯定不是,目前首选,或者。

目前Electrongithub上面的star量已经快要跟React-native一样多了
这里吐槽下,webpack感觉每周都在偷偷更新,很糟心啊,还有Angular更新到了8,Vue马上又要出正式新版本了,5G今年就要商用,华为的系统也要出来了,RN还没有更新到正式的1版本,还有号称让前端开发者失业的技术flutter也在疯狂更新,前端真的是学不完的


回到正题,不能否认,现在的大前端,真的太牛了,PC端可以跨三种平台开发,移动端可以一次编写,生成各种小程序以及React-native应用,然后跑在ios和安卓以及网页中      , 这里不得不说-------京东的Taro框架  这些人 已经把Node.jswebpack用上了天

webpack不熟悉的,看我之前的文章 ,今天不把重点放在webpack

欢迎关注我的专栏 《前端进阶》 都是百星高赞文章

手写React优化版脚手架

前端性能优化不完全手册

手写vue脚手架

本文源码git仓库地址

先说说Electron官网介绍: 使用 JavaScript, HTML 和 CSS 构建跨平台的桌面应用 ,如果你可以建一个网站,你就可以建一个桌面应用程序。 Electron 是一个使用 JavaScript, HTML 和 CSS 等 Web 技术创建原生程序的框架,它负责比较难搞的部分,你只需把精力放在你的应用的核心上即可。

什么意思呢?

Electron = Node.js + 谷歌浏览器 + 平常的JS代码生成的应用,最终打包成安装包,就是一个完整的应用

Electron分两个进程,主进程负责比较难搞的那部分,渲染进程(平常的JS代码)部分,负责UI界面展示

两个进程之间可以通过remote模块,以及IPCRenderIPCMain之间通信,前者类似于挂载在全局的属性上进行通信(很像最早的命名空间模块化方案),后者是基于发布订阅机制,自定义事件的监听和触发实现两个进程的通信。

Electron相当于给React生成的单页面应用套了一层壳,如果涉及到文件操作这类的复杂功能,那么就要依靠Electron的主进程,因为主进程可以直接调用Node.jsAPI,还可以使用C++插件,这里Node.js的牛逼程度就凸显出来了,既可以写后台的CRUD,又可以做中间件,现在又可以写前端。

谈谈技术选型

使用React去做底层的UI绘制,大项目首选React+TS

状态管理的最佳实践肯定不是Redux,目前首选dva,或者redux-saga

构建工具选择webpack,如果不会webpack真的很吃亏,会严重限制你的前端发展,所以建议好好学习Node.jswebpack

选择了普通的Restful架构,而不是GraphQL,可能我对GraphQL理解不深,没有领悟到精髓

在通信协议这块,选择了websoket和普通的http通信方式

因为是demo,很多地方并没有细化,后期会针对electron出一个网易云音乐的开源项目,这是一定要做到的

先开始正式的环境搭建

config文件放置webpack配置文件

server文件夹放置Node.js的后端服务器代码

src下放置源码

main.jsElectron的入口文件

json文件是脚本入口文件,也是包管理的文件~

开发模式项目启动思路:

先启动webpack将代码打包到内存中,实现热更新

再启动Electron读取对应的url地址的文件内容,也实现热更新

设置webpack入口
        app: ["babel-polyfill", "./src/index.js", "./index.html"],
        vendor: ["react"]
        }
    
忽略Electron中的代码,不用webpack打包(因为Electron中有后台模块代码,打包就会报错)
externals: [
        (function () {
            var IGNORES = [
                "electron"
            ];
            return function (context, request, callback) {
                if (IGNORES.indexOf(request) >= 0) {
                    return callback(null, "require("" + request + "")");
                }
                return callback();
            };
        })()
    ]
    
加入代码分割
optimization: {
        runtimeChunk: true,
        splitChunks: {
            chunks: "all"
        }
    },
设置热更新等
 
    plugins: [
        new HtmlWebpackPlugin({
            template: "./index.html"
        }),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin(),
    ],
    mode: "development",
    devServer: {
        contentBase: "../build",
        open: true,
        port: 5000,
        hot: true
    },

#### 加入babel

{
    loader: "babel-loader",
    options: {   //jsx语法
        presets: ["@babel/preset-react",
            //tree shaking 按需加载babel-polifill  presets从后到前执行 
            ["@babel/preset-env", {
                "modules": false,
                "useBuiltIns": "false", "corejs": 2,
            }],
        ],

    plugins: [
        //支持import 懒加载    plugin从前到后
        "@babel/plugin-syntax-dynamic-import",
        //andt-mobile按需加载  true是less,如果不用less style的值可以写"css" 
        ["import", { libraryName: "antd-mobile", style: true }],
        //识别class组件
        ["@babel/plugin-proposal-class-properties", { "loose": true }],
        //
    ],
    cacheDirectory: true
},
}
看看主进程的配置文件main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain, Tray, Menu } = require("electron")
const path = require("path")
// Keep a global reference of the window object, if you don"t, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
app.disableHardwareAcceleration()
// ipcMain.on("sync-message", (event, arg) => {
//   console.log("sync - message")
//   // event.returnValue("message", "tanjinjie hello")
// })
function createWindow() {
  // Create the browser window.
  tray = new Tray(path.join(__dirname, "./src/assets/bg.jpg"));
  tray.setToolTip("wechart");
  tray.on("click", () => {
    mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
  });
  const contextMenu = Menu.buildFromTemplate([
    { label: "退出", click: () => mainWindow.quit() },
  ]);
  tray.setContextMenu(contextMenu);
  mainWindow = new BrowserWindow({
    width: 805,
    height: 500,
    webPreferences: {
      nodeIntegration: true
    },
    // titleBarStyle: "hidden"
    frame: false
  })

  //自定义放大缩小托盘功能
  ipcMain.on("changeWindow", (event, arg) => {
    if (arg === "min") {
      console.log("min")
      mainWindow.minimize()
    } else if (arg === "max") {
      console.log("max")
      if (mainWindow.isMaximized()) {
        mainWindow.unmaximize()
      } else {
        mainWindow.maximize()
      }
    } else if (arg === "hide") {
      console.log("hide")
      mainWindow.hide()
    }
  })
  // and load the index.html of the app.
  // mainWindow.loadFile("index.html")
  mainWindow.loadURL("http://localhost:5000");
  BrowserWindow.addDevToolsExtension(
    path.join(__dirname, "./src/extensions/react-dev-tool"),
  );


  // Open the DevTools.
  // mainWindow.webContents.openDevTools()

  // Emitted when the window is closed.
  mainWindow.on("closed", function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
    BrowserWindow.removeDevToolsExtension(
      path.join(__dirname, "./src/extensions/react-dev-tool"),
    );
  })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow)

// Quit when all windows are closed.
app.on("window-all-closed", function () {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== "darwin") app.quit()
})

app.on("activate", function () {
  // On macOS it"s common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) createWindow()
})


// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.


今天只讲开发模式下的配置,因为实在太多,得分两篇文章写了~ 剩下的配置去git仓库看 在开发模式下启动项目:

使用 "dev": "webpack-dev-server --config ./config/webpack.dev.js", 将代码打包到内存中

使用 "start": "electron ." 开启electron,读取对应的内存地址中的资源,实现热更新

项目起来后,在入口处index.js文件中,注入dva
import React from "react"
import App from "./App"
import dva from "dva"
import Homes from "./model/Homes"
import main from "./model/main"
const app = dva()
app.router(({ history, app: store }) => (
  
));
app.model(Homes)
app.model(main)
app.start("#root")
这里不得不说redux,redux-sage,dva的区别 直接看图 首先是Redux

React 只负责页面渲染, 而不负责页面逻辑, 页面逻辑可以从中多带带抽取出来, 变成 store,状态及页面逻辑从 里面抽取出来, 成为独立的 store,

页面逻辑就是 reducer,都是 Pure Component, 通过 connect 方法可以很方便地给它俩加一层 wrapper 从而建立起与 store 的联系: 可以通过 dispatchstore 注入 action, 促使 store 的状态进行变化, 同时又订阅了 store 的状态变化, 一旦状态有变, 被 connect 的组件也随之刷新,使用 dispatchstore 发送 action 的这个过程是可以被拦截的, 自然而然地就可以在这里增加各种 Middleware, 实现各种自定义功能, eg: logging这样一来, 各个部分各司其职, 耦合度更低, 复用度更高, 扩展性更好

然后是注入Redux-sage

上面说了, 可以使用 Middleware 拦截 action, 这样一来异步的网络操作也就很方便了, 做成一个 Middleware 就行了, 这里使用 redux-saga 这个类库, 举个栗子:

点击创建 Todo 的按钮, 发起一个 type == addTodo 的 action

saga 拦截这个 action, 发起 http 请求, 如果请求成功, 则继续向 reducer 发一个 type == addTodoSucc action, 提示创建成功, 反之则发送 type == addTodoFailaction 即可

最后是: Dva

有了前面的三步铺垫, Dva 的出现也就水到渠成了, 正如 Dva 官网所言, Dva 是基于 React + Redux + Saga 的最佳实践沉淀, 做了 3 件很重要的事情, 大大提升了编码体验:

store saga 统一为一个 model 的概念, 写在一个 js 文件里面

增加了一个 Subscriptions, 用于收集其他来源的 action, eg: 键盘操作

model 写法很简约, 类似于 DSL 或者 RoR, coding 快得飞起✈️

约定优于配置, 总是好的

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/114724.html

相关文章

  • 使用ReactElectronDvaWebpackNode.jsWebsocket快速构建

    摘要:是一个使用和等技术创建原生程序的框架,它负责比较难搞的部分,你只需把精力放在你的应用的核心上即可。谈谈技术选型使用去做底层的绘制,大项目首选状态管理的最佳实践肯定不是,目前首选,或者。 showImg(https://segmentfault.com/img/bVbtqlI?w=1308&h=565); 目前Electron在github上面的star量已经快要跟React-nativ...

    Me_Kun 评论0 收藏0
  • React的移动端和PC端生态圈的使用汇总

    摘要:调用通过注册表调用到实例,透过的,调用到中的,最后通过,调用,根据参数相应模块执行。京东的,多端解决方案是一套遵循语法规范的多端开发解决方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 对于一项技术,我们不能停留在五分钟状态,特别喜欢一句话,用什么方式绘制UI界面一点不重要,重要的是底层的思维,解决问题和优化...

    kun_jian 评论0 收藏0
  • React的移动端和PC端生态圈的使用汇总

    摘要:调用通过注册表调用到实例,透过的,调用到中的,最后通过,调用,根据参数相应模块执行。京东的,多端解决方案是一套遵循语法规范的多端开发解决方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 对于一项技术,我们不能停留在五分钟状态,特别喜欢一句话,用什么方式绘制UI界面一点不重要,重要的是底层的思维,解决问题和优化...

    J4ck_Chan 评论0 收藏0
  • React的移动端和PC端生态圈的使用汇总

    摘要:调用通过注册表调用到实例,透过的,调用到中的,最后通过,调用,根据参数相应模块执行。京东的,多端解决方案是一套遵循语法规范的多端开发解决方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 对于一项技术,我们不能停留在五分钟状态,特别喜欢一句话,用什么方式绘制UI界面一点不重要,重要的是底层的思维,解决问题和优化...

    Travis 评论0 收藏0

发表评论

0条评论

Caicloud

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<