Loading...

# 路由组件开发

我们进入到 app/renderer 文件夹下,会发现这里有搭建环境时写的 <Title /> 组件,我们将其进行删除(已无用),我们用脚趾头都能知道,之后会存在诸多模块入口,所以我们在 renderer 下,创建一个路由文件 router.tsx ,管理所有的模块入口,先来编写一下 router.tsx

// renderer/router.tsx
import React from 'react';
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom';
import Root from './container/root';
function Router() {
  return (
    <HashRouter>
      <Switch>
        {/* 精确路由匹配 */}
        <Route path="/" exact>
          <Root />
        </Route>
      </Switch>
      {/* 重定向到首页 */}
      <Redirect to="/" />
    </HashRouter>
  );
}
export default Router;

创建一个文件夹 container ,该文件夹存放着所有模块的代码文件,此时我们添加一个新文件夹,取名为: root ,表明这是首页模块,并创建入口文件 index.tsx 和 index.less

// renderer/container/root/index.tsx
import React from 'react';
import './index.less';
function Root() {
  return <div>我是首页</div>;
}
export default Root;

回到根组件 app.tsx ,将路由组件 router.tsx 引入

// renderer/app.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import Router from './router';
function App() {
  return <Router />;
}
ReactDOM.render(<App />, document.getElementById('root'));

大功告成,运行一下,看看效果如何

npm run start:main
npm run start:render

不出意外,渲染进程窗口很顺利的展示了我们想要的页面效果,此时看看我们的文件结构

image.png

# 首页开发

通过效果图,我们可以将首页拆分成:

  • logo 图片
  • title 应用名称
  • tips 应用简介特性
  • entry 模块入口
  • copyright 应用版权

我们先将 logo 图引入,通过 CSS 实现布局效果.

// 首页模块的入口文件
import React from 'react';
import './index.less';
import Logo from '../../../../assets/logo.jpg';
function Root() {
  return (
    <div styleName="root">
      <div styleName="container">
        <img src={Logo} alt="" />
        <div styleName="title">onlineResume</div>
        <div styleName="tips">一个模板简历制作平台, 让你的简历更加出众 ~</div>
        <div styleName="action">
          {['介绍', '简历', '源码'].map((text, index) => {
            return (
              <div key={index} styleName="item">{text}</div>
            );
          })}
        </div>
        <div styleName="copyright">
          <div styleName="footer">
            <p styleName="copyright">
              Copyright © 2018-{new Date().getFullYear()} All Rights Reserved. Copyright By pengdaokuan
            </p>
          </div>
        </div>
      </div>
    </div>
  );
}
export default Root;

CSS 如下

.root {
    height: 100vh;
    width: 100vw;
    text-align: center;
    background-color: #27292c;
    .container {
      width: 100%;
      color: #ffffff;
      padding-top: calc(8vh + 60px);
      img {
        width: 112px;
        height: 112px;
      }
      .title {
        font-size: 24px;
        line-height: 36px;
      }
      .tips {
        font-size: 16px;
        line-height: 24px;
        margin-top: 24px;
      }
      .theme {
        margin: 24px 0;
        height: 30px;
      }
      .action {
        width: 300px;
        margin: 0 auto;
        display: flex;
        justify-content: center;
        align-items: center;
        margin-top: 24px;
        .item {
          width: 25%;
          cursor: pointer;
        }
      }
    }
  }

首页界面

刷新一下页面,可以发现我们距离成功只剩一步之遥。接下来我们来实现一下基本点击跳转等功能。

# 模块入口跳转功能

在 React 中我们可以通过 react-router 这个强大路由库进行页面之间的跳转,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。

在环境搭建篇中我们已经安装了 react-router,由于我们采用 Hooks 的写法,react-router 提供了一个 API 叫做 useHistory ,接下来我们就通过它,来实现我们的跳转功能吧~

回到我们上面的代码,我们为其添加一个 onClick 事件

// 首页模块的入口文件
import React from 'react';
import './index.less';
import { useHistory } from 'react-router';
import Logo from '../../../../assets/logo.png';
function Root() {
  // 👇 通过 history.push 进行跳转
  const history = useHistory();
  
  const onRouterToLink = (text: string) => {
     if (text === '简历') {
       console.log('跳转到简历页面')
       history.push('/resume')
     } else {
       console.log('进入到 github ')
     }
  }
  return (
    <div styleName="root">
      ...
      <div styleName="action">
        {['介绍', '简历', '源码'].map((text, index) => {
          return (
            <div key={index} styleName="item" onClick={() => onRouterToLink(text)} >
              {text}
            </div>
          );
       )}
      </div>
      ...
    </div>
  );
}
export default Root;

解读一下上面代码,我们为每个模块 div 都添加 onClick 事件,点击模块后,进行条件判断,从而做对应的操作。 刷新一下页面,点击 简历 ,发现页面空白,为什么呢?回过头想想,我们上边的 router.tsx 路由组件,不就只写了一个首页模块的路由吗?我们回去添加一个新路由。

container 下添加 resume 文件夹,并新增入口 index.tsx,我们简单写一下简历入口代码。

import React, { Component } from 'react'
export default class index extends Component {
    render() {
        return (
            <div>
                我是简历页面
            </div>
        )
    }
}

同时修改 router.tsx 文件,将其引入

// renderer/router.tsx
import React from 'react';
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom';
import Root from './container/root';
import Resume from './container/resume'
function Router() {
  return (
    <HashRouter>
      <Switch>
        {/* 精确路由匹配 */}
        <Route path="/" exact>
          <Root />
        </Route>
        {/* 👇 添加简历模块入口路由 */}
        <Route path="/resume" exact>
          <Resume />
        </Route>
      </Switch>
      {/* 重定向到首页 */}
      <Redirect to="/" />
    </HashRouter>
  );
}
export default Router;

再点击一下 简历 ,此时可成功跳转。页面内的路由切换尚能解决,窗口外的页面跳转无从下手。我们期望点击 介绍源码 处,能够脱离应用窗口,在我们默认浏览器中打开页面,进入到 github 中。

electron 提供一个 shell 模块,它模块提供与桌面集成相关的功能。并且此模块也能用于渲染进程中,下面我们通过此模块,实现此功能(👇 部分代码省略)

import { shell } from 'electron';
function Root() {
   const onRouterToLink = (text: string) => {
    if (text !== '简历') {
      // 通过 shell 模块,打开默认浏览器,进入 github
     shell.openExternal('https://github.com/PDKSophia/visResumeMook');
    } else {
        history.push('/resume');
     }
  };
}

到目前为止,我们首页的基本功能已经开发完成。

# 🤔 思考代码优化

上面我们是以简单粗暴形式,将页面和逻辑撸了出来,但代码简直 “不堪入目”,作为一个有追求、有代码洁癖的工程师,简直无法容忍,接下来我们对它进行美化。

# 1. webpack alias 别名

我们回过头看,当我们引入图片时,路径要些一连串的 ../../../../ ,有没有想骂 x 的冲动,好在 webpack 提供 alias 配置,让我们能够配置别名,接下来我们上手试试。

我们的图片都放在项目根路径下的 assets 中,我们给它加个别名,修改 webpack.base.js 文件

module.exports = {
  resolve: {
    // 👇 添加别名
    alias: {
      '@assets': path.join(__dirname, '../', 'assets/'),
      '@src': path.join(__dirname, '../', 'app/renderer'),
    },
  },
};

添加之后,我们将文件的引入改成下面这种形式

// 未修改前
import Logo from '../../../../assets/logo.png';
// 修改后
import Logo from '@assets/logo.png';
// 未修改前
import Root from './container/root';
import Resume from './container/resume';
// 修改后
import Root from '@src/container/root';
import Resume from '@src/container/resume';

重跑一下项目(运行 npm run start:render)发现没啥问题,完美

# 2. 模块入口的常量定义与类型约束

上面我们写了一段 “粗暴” 代码,我们能否将其进行抽离,思考一下, 路由常量数据 是一个只会在首页用到的数据还是其他模块也会用到的数据呢?

其他模块是否也会通过 history.push 方式跳转到其他模块页面,如果是,我们将来在其他模块也要写一段 “粗暴” 代码?还可能出现的问题是:我们期望数据一致,当往往出于疏忽,两边数据不一致。

那么我们将其抽离成一个路由常量文件,进行统一维护,是不是更好呢?

我们在 app/renderer 文件夹下新增一个文件夹,取名为:common,顾名思义,这里存放的是项目中所有公共通用的代码文件,在里边我们创建一个 constants 文件夹,表示这里维护所有常量数据。

我们在 contants 下维护一份路由专用的文件,取名为 router.ts ,我们来写一下该文件:

// 模块路径
const ROUTER = {
  root: '/',
  resume: '/resume',
};
export default ROUTER;
export const ROUTER_KEY = {
  root: 'root',
  resume: 'resume',
};
// 入口模块,TS 定义类型必须为 TSRouter.Item
export const ROUTER_ENTRY: TSRouter.Item[] = [
  {
    url: 'https://github.com/PDKSophia/visResumeMook',
    key: 'intro',
    text: '介绍',
  },
  {
    url: ROUTER.resume,
    key: ROUTER_KEY.resume,
    text: '简历',
  },
  {
    url: 'https://github.com/PDKSophia/visResumeMook',
    key: 'code',
    text: '源码',
  },
];

既然我们使用了 Typescript,那么我们先小试牛刀一下,上面定义的 ROUTER_ENTRY 我们将它的类型约束为 TSRouter.Item ,我们在 common 文件夹下新增一个名为 types 文件夹,表示此文件存放着应用中用到的类型定义。我们来新增一个用于路由的 router.d.ts 文件

// router.d.ts
// 路由类型约束
declare namespace TSRouter {
  export interface Item {
    /**
     * @description 路由跳转链接
     */
    url: string;
    /**
     * @description 关键词
     */
    key: string;
    /**
     * @description 文本
     */
    text: string;
  }
}

紧接着我们在 webpack 中配置一下此文件夹的别名

alias: {
  // ...
  '@common': path.join(__dirname, '../', 'app/renderer/common'),
}

我们进行改造,首先先来修改一下路由组件 router.tsx

//router.tsx 路由组件
// 👇 引入路由常量
import ROUTER from '@common/constants/router';
function Router() {
  return (
    <HashRouter>
      <Switch>
        <Route path={ROUTER.root} exact>
          <Root />
        </Route>
        <Route path={ROUTER.resume} exact>
          <Resume />
        </Route>
      </Switch>
      <Redirect to={ROUTER.root} />
    </HashRouter>
  );
}
export default Router;

我们在首页入口 index.tsx 文件进行改造

// 首页入口 index.tsx
import { ROUTER_ENTRY, ROUTER_KEY } from '@common/constants/router';
// 在方法调用上
const onRouterToLink = (router: TSRouter.Item) => {
  if (router.text !== '简历') {
    shell.openExternal(router.url);
  } else {
    history.push(router.url)
  }
};
// 在遍历上
<div styleName="action">
  {ROUTER_ENTRY.map((router: TSRouter.Item) => {
    return (
      <div key={router.key} styleName="item" onClick={() => onRouterToLink(router)} >
        {router.text}
      </div>
    );
  })}
</div>

# 3. utils 方法抽离

虽然我们代码优化了一部分,但还是存在一些小问题的,比如 router.text !== '简历' 这个条件判断就有些突兀了,我们回到问题本质,这里进行判断原因是:如果这个 url 是外部可访问的链接,则通过 shell 模块打开浏览器,如果是页面之间跳转,则跳转到对应的路由页面。

所以问题聚焦在,如何判断 url 是不是可访问的外部链接?这很简单,我们写一个方法,判断 url 是不是 http 或 https 开头,该方法返回 boolean 值,下面我们来实现此方法。

首先在 common 下新增一个 utils 文件夹,并新增 router.ts,表示这是路由相关的工具处理函数,在里面实现我们的函数方法:

// renderer/common/utils/router.ts
/**
 * @desc 判断是否属于外部连接
 * @param {string} url - 链接
 */
export function isHttpOrHttpsUrl(url: string): boolean {
  let regRule = /(http|https):\/\/([\w.]+\/?)\S*/;
  return regRule.test(url.toLowerCase());
}

接下来我们进行修改的条件判断

import { isHttpOrHttpsUrl } from '@common/utils/router';
// 在方法调用上
  const onRouterToLink = (router: TSRouter.Item) => {
    if (isHttpOrHttpsUrl(router.url)) {
      shell.openExternal(router.url);
    } else {
      history.push(router.url);
    }
  };

# 4. 页面存在空白间隙

最懒惰的解决方式是,在 index.html 中,修改一下样式

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>VisResumeMook</title>
    <style>
      * {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

至此,我们的首页终于开发完毕,并且经过思考,不断优化,将项目的整个文件结构进行丰富。一张图回顾一下我们现在的文件结构

image.png

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

jluyeyu 微信支付

微信支付

jluyeyu 支付宝

支付宝