Electron & Next.js & Hooks

Question

使用 Electron,Next.js,Hooks 组成一个前端开发框架

Reference

electron

next.js

react

Electron

electron-quick-start,从中可以理解到,electron 运行并不需要太多的东西。观察可知,

  • package.json 指出 main 位置。
  • main.js 则是加载 HTML。
  • index.html 负责被加载。
  1. 要运行:                              读取 package.json,去找到main.js
  2. 要把应用包裹在浏览器中: main.js能够找到一个 html 或者页面给他。

package.json ,找 main.js ,找index.html,这三步应该都可以替换。

electron 对我们框架的文件组织无影响。甚至我们可以在这个结合框架中,把 electron 客户端开发交给另一个人的解耦程度吧。研究了更多的文档,找到了 application-architecture 果然如此。

值得注意的是,它还支持在其中 node.js 的 api 和 npm module,这给我们灵活使用电子提供了很大的可选择性。

可能会用到的 module

Next.js

【Next.js 甚至支持静态导出同时还支持者本来的路由等大部分功能。static-html-export

生成一个项目,或者引入你的项目:manual-setup

$ npx create-next-app

由路径 localhost:3000 可知使用 mainWindow.loadURL(‘http://localhost:3000’) 即可沟通 electron。

image.png

查看 docs 可以看到

Directory

由上述可知,目录结构需要包括 ./pages 和 ./public , electron 和 UI 开发可以独立,那么新建一个 ./electron 用来存储 main.js ,于是保留 node 的目录结构之后加上 electron。

开始🏃跑吧。

  1. 先把  electron 的 main.js 移动到项目的 ./electron/main.js 中,改动其中的函数变为加载 URLimage.png
  1. 然后改 ./package.json,加入这个路径 "main": "electron/main.js",和 electron 运行的脚本 "start-electron": "electron ."                              image.png
  1. 这是目录结构

image.png

  1. npm install -D electron@latest 并尝试运行image.png
  1. 检测页面image.png

路由方案

但是值得注意的是不能返回操作,如果想回到最开始的初始路由必须使用 electron Reload, 这意味着单页应用需要新的解决方案。【更新:分别找了两个准备尝试,一个是 next.js 的 static-html-export ,一个是 electron 的 electron-serve。】

Graceful Start

  1. 安装 concurrently 实现单命令启动
npm install concurrently --save-dev
  1. 修改 scripts 部分
"start": "concurrently -k \"npm run start:render\" \"npm run start:main\"",
"start:main": "electron .",
"start:render": "next dev",
  1. 启动
npm run start

Hooks Persistence Global State

本文讲述如何分析设计 通过 React Hooks 进行 State 持久化管理

分析

正常前端,组件为类文件,自己维持状态,不易复用。

首先把组件中的 UI 和 状态分开,用 Action 连接,如下图。

UI=f(State).png

Action 是算子

Function

则可成为以下函数

  • UI = f(S)
    • 状态驱动组件重新渲染 UI
  • Scu =f(Sc, ∆)
    • 组件会用到的 Scu 和 更改 Sc 的 ∆ 方法决定。

S

每一个组件有他自己的状态集 s。

scu

即,component use :组件用到的状态,比如计数器中的数字

所有组件的使用到 scu 共同组成一个状态 Scu–渲染一个 UI。

sc

即,收到组件影响的状态,如登录组件可能每登录一次就会增加计数器,但是对于登录组件并不会用到这个状态,虽然它会更改它。

入参

设计 State 框架时,让每一个组件声明 s状态时,提供一个更改自己的函数 ma ,在 Action 事件时调用用于更改 State,而多个 ma 的集合为 ∆。

S

Sa =f(S, ∆) 中的 S 作为 f 的参数传入,因为并不知道 Action 会更改哪些 State 【甚至不知道有哪些】,故把所有 State 都作为入参。

局部渲染

更改的状态驱动 UI 渲染,如果相同可以不改变。

如上所说,UI 由于入参为 S ,会接收所有的 State,组件自己根据自己需要的 sa 变动渲染,而不是 UI 根据 S 改动分发事件。

观察 Hooks 可知,useState() 方法使用Object.is 比较算法 来比较 state。

而 useEffect()则提供选择让它 在只有某些值改变的时候 才执行的参数。

设计

实虚部数学模型

实数并不完备,引入虚部。

虚数,只需要去掉虚部就可以表示实数。

Curry Func 也如此。

以上同理:f(S,∆)f(S) 代表实数,不完备,加入 ∆ ,可以表达所有情况。

更改的维度从一维的 线 成为了 二维的平面。

另:框架使用的f(S, ∆)还是一维的线,但其实是该平面 任意一条线 ,因为 f(S,∆) 已经中的 ∆ 和 S 已经经由使用者确定,即在多维度选择了一个平面降维实现在代码中了。

Persistence

需要一个地方存储数据,local,session,remote 等.

Connector

组件如何把触发的事件分发给 State 处理?需要通信。

由于 js 单线程模型,选择共享内存设计新增一个 Connector 用于通信。

组件 Component 如何通知 State 改动。共享内存,采用 Connector 中间层。

Action by CurryFunc

State 如何知道框架使用者定义的 Action 改动了哪些 State ?即不知道 ∆ 的具体值。采用 Curry Func 满足延迟求值的需求。

使用 fg(S){return f(∆)} 代替 f(S,∆)

State 框架使用者自己使用 f(∆) 注册自己的状态更改算子 ∆。

State 框架开发者使用 fg(S) ,只管自己传入所有的 State 即可。

由于 React Hooks 的存在,state 自带使用 f(S,∆) 进行更新的功能。故框架留出 useState() 接口,返回 f(∆) ,供使用者进行状态管理。

Redux

Redux 也是基于此函数模型,而在 Hooks 中官方已经使用 useReducer(reducer, initialState) 实现了它。其中 reducer 是设定好的 f(S,∆) ,而它返回 state 和 dispatch,其中 state 就是 S而 dispatch 就是 f(∆)

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);
  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }
  return [state, dispatch];
}

在我们看来,它也内部实现了 Connector 的作用。

实现

Persistence

首先是通过 Hooks 实现存储, 使用 Local Store

function useLocalJSONStore(key, defaultValue) {
    const [state, setState] = useState(
      () => JSON.parse(localStorage.getItem(key)) || defaultValue
    );
    useEffect(() => {
      localStorage.setItem(key, JSON.stringify(state));
    }, [key, state]);
    return [state, setState];
}

存储位置

解决了持久化存储,提供外在的状态管理支持。考虑到我们会使用 Go 来做前端:

  1. 使用 Hooks 加 sqlite3 库本地存储
  2. 使用 Hooks 和 Go 通信完成

Connector

为了使用 Hooks 实现全局的状态通知。

首先明白 useState() 获取到的 setState() 会触发当前组件的渲染:https://zh-hans.reactjs.org/docs/hooks-state.html

Connector 让使用全局状态的组件订阅来连接上全局的状态更新,将自己的 setState() 传入更新队列,当其中任何一个组件使用 dispatch() 更改状态时会触发这个命名空间下的全部状态更新,从而达到刷新所有状态组件的目的。

import { useEffect } from "react"

const Connector = {}

const Broadcast = (name, state) => {
    if (!Connector[name]) return;
    Connector[name].forEach(setter => setter(state))
}

const Subscribe = (name, setter) => {
    if (!Connector[name]) Connector[name] =[];
    Connector[name].push(setter)
}

const UnSubscribe = (name, setter) => {
    if (!Connector[name]) return
    const index = Connector[name].indexOf(setter)
    if (index !== -1) Connector[name].splice(index, 1)
}

const connect = (name,setState) => {
    console.log('connect')
    useEffect(() =>{
        Subscribe(name, setState)
        console.log('subscirbe',name)
        return () => {
            UnSubscribe(name,setState)
            console.log('unsubscribe',name)
        }
    },[])
}

useStore

使用者使用 useStore() 来获取全局状态和 dispatch() 函数。内部实现就是 State Hook ,并拿到 setState()注册到订阅列表中。

import {Broadcast,connect} from './Connector'
import {useState} from 'react'

export function useStore(key,value) {
    const [state,setState] = useState(value)
    connect(key,setState)

    return [state, (key,value) => {
        Broadcast(key,value)
    }]
}

目前状况

react-store-hook.svg

使用

使用 useStore(key, value) 即可。

import {useStore} from './useStore'

export function Counter({key,initialCount}) {
    // const [count, setCount] = useLocalJSONStore(keyname, initialCount);
    const [state, dispatch] = useStore(key,initialCount)
    return (
      <>
        Count: {state}
        <button onClick={() => dispatch(keyname,initialCount)}>Reset</button>
        <button onClick={() => dispatch(keyname,state-1)}>-</button>
        <button onClick={() => dispatch(keyname,state+1)}>+</button>
      </>
    );
  }

image.png

来源: Electron & Next.js & Hooks · 语雀


0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注