博客管理系统开发 -- 构建React开发环境

一、开发环境

1、开发环境

  • windows 10操作系统;
  • Node.js v10.16.0;
  • webstorm 2019.3.4 x64;

2、前端技术栈

  • react v16.13.1 hooks + redux v7.2  + react-router v5.1.2;
  • antd v4.1.0;
  • marked hightlight.js;
  • webpach打包优化;
  • axios封装;

3、后端技术栈

  • spring boot;
  • mybatis-plus;
  • swagger;
  • spring security;

二、前端知识回顾

1、基础学习

如果没有接触过react的话,推荐先学习一下react基础知识:

1、react的入门教学视频入口:React 入门教程(开发文档);

2、官方教程入口:入门教程: 认识 React;

3、redux教程:从零实现一个 redux;

2、命名规范

html标签:小写字符开始;

自定义React组件:大写字符开始;

其它变量、方法、函数:驼峰命名法;

文件夹、文件命名:全部小写,中间使用_分割,如data_assets;

三、npm使用

1、npm介绍

npm是随同Node.js一起安装的包管理工具,能解决Node.js代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从npm服务器下载别人编写的第三方包到本地使用;
  • 允许用户从npm服务器下载并安装别人编写的命令行程序到本地使用;
  • 允许用户将自己编写的包或命令行程序上传到npm服务器供别人使用;

由于新版的Node.js已经集成了npm,所以npm也一并安装好了。同样可以通过输入如下命令 来测试是否成功安装。命令如下,出现版本提示表示安装成功:

npm -v

2、npm使用

如果你安装的是旧版本的 npm,可以很容易得通过 npm 命令来升级,命令如下:

npm install npm -g

由于npm安装较慢,我们可以使用淘宝镜像的命令:

npm install -g cnpm --registry=https://registry.npm.taobao.org

这样就可以使用 cnpm 命令来安装模块了:

npm 安装 Node.js 模块语法格式如下:

npm install 

以下实例,我们使用 npm 命令安装常用的 Node.js web框架模块 express:

npm install express

安装好之后,express 包就放在了工程目录下的 node_modules 目录中,因此在代码中只需要通过 require('express') 的方式就好,无需指定第三方包路径。

var express = require('express');

3、全局安装与本地安装

npm 的包安装分为本地安装(local)、全局安装(global)两种,从敲的命令行来看,差别只是有没有-g而已,比如:

npm install express          # 本地安装
npm install express -g      # 全局安装

本地安装

  • 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录;
  • 可以通过 require() 来引入本地安装的包;

全局安装

  • npm 全局模块的存放路径以及cache的路径的配置,默认是在 C 盘 "C:\Users\用户" 下;
  • 可以直接在命令行里使用;

我们可以指定全局模块以及cache路径:

(1).在 nodejs 安装目录下,创建 ”node_global” 和 ”node_cache” 两个文件夹;

(2). 进入 cmd 命令行,输入如下命令:

npm config set prefix "D:\nodejs\node_global"
npm config set cache "D:\nodejs\node_cache"

设置全局模块的安装路径到 "node_global" 文件夹, 设置缓存到 "node_cache" 文件夹;

(3).由于 node 全局模块大多数都是可以通过命令行访问的,还要把 “D:\nodejs\node_global” 加入到系统环境变量 PATH 中,方便直接使用命令行运行;

查看安装信息

你可以使用以下命令来查看所有全局安装的模块:

npm list -g

4、使用 package.json

package.json 位于模块的目录下,用于定义包的属性。接下来让我们来看下 express 包的 package.json 文件,位于 node_modules/express/package.json 内容:

{
  "name": "express",
  "description": "Fast, unopinionated, minimalist web framework",
  "version": "4.13.3",
  "author": {
    "name": "TJ Holowaychuk",
    "email": "[email protected]"
  },
  "contributors": [
    {
      "name": "Aaron Heckmann",
      "email": "[email protected]"
    },
    {
      "name": "Ciaran Jessup",
      "email": "[email protected]"
    },
    {
      "name": "Douglas Christopher Wilson",
      "email": "[email protected]"
    },
    {
      "name": "Guillermo Rauch",
      "email": "[email protected]"
    },
    {
      "name": "Jonathan Ong",
      "email": "[email protected]"
    },
    {
      "name": "Roman Shtylman",
      "email": "[email protected]"
    },
    {
      "name": "Young Jae Sim",
      "email": "[email protected]"
    }
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/strongloop/express.git"
  },
  "homepage": "http://expressjs.com/",
  "keywords": [
    "express",
    "framework",
    "sinatra",
    "web",
    "rest",
    "restful",
    "router",
    "app",
    "api"
  ],
  "dependencies": {
    "accepts": "~1.2.12",
    "array-flatten": "1.1.1",
    "content-disposition": "0.5.0",
    "content-type": "~1.0.1",
    "cookie": "0.1.3",
    "cookie-signature": "1.0.6",
    "debug": "~2.2.0",
    "depd": "~1.0.1",
    "escape-html": "1.0.2",
    "etag": "~1.7.0",
    "finalhandler": "0.4.0",
    "fresh": "0.3.0",
    "merge-descriptors": "1.0.0",
    "methods": "~1.1.1",
    "on-finished": "~2.3.0",
    "parseurl": "~1.3.0",
    "path-to-regexp": "0.1.7",
    "proxy-addr": "~1.0.8",
    "qs": "4.0.0",
    "range-parser": "~1.0.2",
    "send": "0.13.0",
    "serve-static": "~1.10.0",
    "type-is": "~1.6.6",
    "utils-merge": "1.0.0",
    "vary": "~1.0.1"
  },
  "devDependencies": {
    "after": "0.8.1",
    "ejs": "2.3.3",
    "istanbul": "0.3.17",
    "marked": "0.3.5",
    "mocha": "2.2.5",
    "should": "7.0.2",
    "supertest": "1.0.1",
    "body-parser": "~1.13.3",
    "connect-redis": "~2.4.1",
    "cookie-parser": "~1.3.5",
    "cookie-session": "~1.2.0",
    "express-session": "~1.11.3",
    "jade": "~1.11.0",
    "method-override": "~2.3.5",
    "morgan": "~1.6.1",
    "multiparty": "~4.1.2",
    "vhost": "~3.0.1"
  },
  "engines": {
    "node": ">= 0.10.0"
  },
  "files": [
    "LICENSE",
    "History.md",
    "Readme.md",
    "index.js",
    "lib/"
  ],
  "scripts": {
    "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
    "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",
    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
    "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"
  },
  "gitHead": "ef7ad681b245fba023843ce94f6bcb8e275bbb8e",
  "bugs": {
    "url": "https://github.com/strongloop/express/issues"
  },
  "_id": "[email protected]",
  "_shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",
  "_from": "express@*",
  "_npmVersion": "1.4.28",
  "_npmUser": {
    "name": "dougwilson",
    "email": "[email protected]"
  },
  "maintainers": [
    {
      "name": "tjholowaychuk",
      "email": "[email protected]"
    },
    {
      "name": "jongleberry",
      "email": "[email protected]"
    },
    {
      "name": "dougwilson",
      "email": "[email protected]"
    },
    {
      "name": "rfeng",
      "email": "[email protected]"
    },
    {
      "name": "aredridel",
      "email": "[email protected]"
    },
    {
      "name": "strongloop",
      "email": "[email protected]"
    },
    {
      "name": "defunctzombie",
      "email": "[email protected]"
    }
  ],
  "dist": {
    "shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",
    "tarball": "http://registry.npmjs.org/express/-/express-4.13.3.tgz"
  },
  "directories": {},
  "_resolved": "https://registry.npmjs.org/express/-/express-4.13.3.tgz",
  "readme": "ERROR: No README data found!"
}
View Code

Package.json 属性说明

  • name - 包名;

  • version - 包的版本号;

  • description - 包的描述;

  • homepage - 包的官网 url ;

  • author - 包的作者姓名;

  • contributors - 包的其他贡献者姓名;

  • dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下;

  • repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上;

  • main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js;

  • keywords - 关键字;

5、卸载模块

我们可以使用以下命令来卸载 Node.js 模块:

npm uninstall express

6、更新模块

我们可以使用以下命令更新模块:

npm update express

7、搜索模块

使用以下来搜索模块,会打开浏览器查看存在的模块版本:

npm search express

四、快速构建React开发环境

1、全局安装create-react-app脚手架工具

cnpm install -g create-react-app

create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。 create-react-app 自动创建的项目是基于 Webpack + ES6 。

2、创建新的react项目

create-react-app react-blog-zy

博客管理系统开发 -- 构建React开发环境_第1张图片

此时会在当前工作路径创建一个文件夹react-blog-zy:

  • node_modules:用于存放项目的依赖包,也就是构建这个React项目可能会用到的工具;
  • public:文件夹是 index.html文件的存放目录;
  • src:用于存放js文件,也就是项目开发中的主要区域;
  • package.json:用于记录项目信息,以及外部依赖包的导入信息等;

跳转到新建项目目录下:

cd react-blog-zy

 运行项目:

 npm start

博客管理系统开发 -- 构建React开发环境_第2张图片

可以看到项目启动成功,打开网页 http://localhost:3000:

博客管理系统开发 -- 构建React开发环境_第3张图片

3、运行npm run eject

使用npm run eject 可以看到webpack.config配置信息,此时会多出两个文件夹:

博客管理系统开发 -- 构建React开发环境_第4张图片

config文件夹:

  • env.js :处理.env环境变量配置文件;
  • path.js:提供各种路径;
  • webpack.config.js: webpack配置文件;
  • webpackDevServer.config.js:测试服务器配置文件;

env.js用来处理.env文件中配置的环境变量:

const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
  throw new Error(
    'The NODE_ENV environment variable is required but was not specified.'
  );
}

// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotenvFiles = [
  `${paths.dotenv}.${NODE_ENV}.local`,
  `${paths.dotenv}.${NODE_ENV}`,
  // Don't include `.env.local` for `test` environment
  // since normally you expect tests to produce the same
  // results for everyone
  NODE_ENV !== 'test' && `${paths.dotenv}.local`,
  paths.dotenv,
].filter(Boolean);

// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.  Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
  if (fs.existsSync(dotenvFile)) {
    require('dotenv-expand')(
      require('dotenv').config({
        path: dotenvFile,
      })
    );
  }
});
NODE_ENV获取node运行环境,当我们运行npm start时,运行的是scripts/start.js脚本,在该脚本中指定了运行环境为development:
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

dotenvFiles 是一个数组,保存要扫描的.env*文件,然后遍历每个文件,从这些文件中加载环境变量;

scripts文件夹:

  • build.js:打包脚本;
  • start.js: 启动脚本;
  • test.js: 测试脚本;

我们可以通过修改脚本,指定项目运行在其它端口:

// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';

4、项目结构调整

删除src、public目录下所有文件:

1、在public下新建index.html文件:


"en">
  <head>
    "utf-8" />
    "icon" href="%PUBLIC_URL%/favicon.ico" />
    "viewport" content="width=device-width, initial-scale=1" />
    "theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    
    "manifest" href="%PUBLIC_URL%/manifest.json" />
    
    zy的博客

    
    "keywords" content="zy的博客">
    "keywords" content="zy">
    "description" content="前端开发爱好者">
    "og:type" content="website">
    "og:title" content="zy的博客">
    "og:url" content="https://www.cnblogs.com/zyly/">
    "og:site_name" content="zy的博客">
    "og:description" content="前端开发爱好者">
    "twitter:card" content="summary">
    "twitter:title" content="zy的博客">
    "twitter:description" content="前端开发爱好者">

    
    "viewport" content="width=device-width,initial-scale=1,user-scalable=no">

    
    

  head>
  
    
    
id="root">

2、在public下新建favicon.ico文件:

该文件就是一个icn图标;

3、在public下新建manifest.json文件:

{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}

4、在src下新建App.js文件:

import React from 'react';

export default function App(props){
    return (
        

博客

) }

5、在src下新建index.js文件:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(
    ,
    document.getElementById('root')
)

该文件也是该项目的默认入口文件,我们可以在webpack.config.js文件中看到:

// These are the "entry points" to our application.
    // This means they will be the "root" imports that are included in JS bundle.
    entry: [
      // Include an alternative client for WebpackDevServer. A client's job is to
      // connect to WebpackDevServer by a socket and get notified about changes.
      // When you save a file, the client will either apply hot updates (in case
      // of CSS changes), or refresh the page (in case of JS changes). When you
      // make a syntax error, this client will display a syntax error overlay.
      // Note: instead of the default WebpackDevServer client, we use a custom one
      // to bring better experience for Create React App users. You can replace
      // the line below with these two lines if you prefer the stock client:
      // require.resolve('webpack-dev-server/client') + '?/',
      // require.resolve('webpack/hot/dev-server'),
      isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'),
      // Finally, this is your app's code:
      paths.appIndexJs
      // We include the app code last so that if there is a runtime error during
      // initialization, it doesn't blow up the WebpackDevServer client, and
      // changing JS code would still trigger a refresh.
    ].filter(Boolean),

 paths.appIndexJs指向的正是src/index.html文件;

此时我们运行程序就可以看到:

由于create-creat-app创建的项目,在webpack.config.js中配置了webpack-dev-server热加载:
博客管理系统开发 -- 构建React开发环境_第5张图片

可以看到如果当前运行在开发环境下,我们项目将会使用自定义的webpackDevServer客户端,当我们文件发生变化时,该客户端将会通知WebpackDevServer服务器,然后进行页面刷新。

我们可以实时改变App组件输出的内容,然后查看浏览器的变化。此外,如果我们想使用默认的WebpackDevServer客户端,可以取消下面代码注释:

require.resolve('webpack-dev-server/client') + '?/',
require.resolve('webpack/hot/dev-server'),

这种页面的刷新有个缺点,每次刷新都会清空我们组件的状态值。因此,我们需要使用局部热刷新技术,这个后面我们会介绍 react-hot-loader 工具。

至此,我们的准备工作就完成了,下一节将会介绍前端框架的搭建。

五、样式配置

1、CSS Modules

[email protected] 及更高版本,项目使用.module.css 文件命名约定支持 CSS Modules  。 CSS Modules 允许通过自动创建格式唯一的classname 来确定 CSS 的作用域,从而避免污染全局样式。CSS Modules 允许你在不同的文件中使用相同的 CSS classname,而无需担心命名冲突

由create-react-app创建的项目,在webpack.config.js中指定了匹配各种样式文件的正则表达式:

// style files regexes
const cssRegex = /\.css$/;           
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;

(1).针对.css文件,样式处理器配置如下:

  // "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject