ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Koa2 项目前端渲染实战:React.js 从 JSX 到 virtual DOM 的编译与集成全流程

Koa2 项目前端渲染实战:React.js 从 JSX 到 virtual DOM 的编译与集成全流程
  • 文档
  • 教程
  • 后端

【免费下载链接】koa2-note

《Koa2进阶学习笔记》已完结🎄🎄🎄

项目地址:https://gitcode.com/gh_mirrors/ko/koa2-note
点击查看免费下载

本篇技术指南聚焦 koa2-note 项目(《Koa2 进阶学习笔记》)中前端渲染层的落地方式:以 React.js 作为页面渲染库,用 JSX 描述页面结构,再通过 webpack4 + babel7 编译为浏览器可识别的 virtual DOM 产物,并由 Koa2 服务端模板与静态资源机制完成前后端串联。读完本文,你将掌握该项目从static/src源码到output/dist编译产物的完整构建链路,理解 apps / components / pages 三层目录的职责划分,并能独立配置一套可复用的 React.js + webpack + babel 前端构建环境。

React.js 简介:前端渲染库而非框架

项目中 React.js 的角色定位需要首先明确:它是一个前端渲染的 js 库(library),而不是框架(framework)。其核心工作方式是:

  • 开发者用JSX语法描述 DOM 结构(组件树);
  • React 将 JSX 编译成virtual DOM(虚拟 DOM 树);
  • 在浏览器中基于 virtual DOM 完成 view 渲染与动态交互处理,通过 diff 算法最小化真实 DOM 的更新开销。

这套机制决定了项目前端代码的组织形态:页面以组件为单位拆分,组件之间通过 JSX 声明式组合,最终由 ReactDOM 挂载到指定的 DOM 节点上。

为什么需要编译:JSX 无法被浏览器直接运行

JSX 是类 XML 的 JavaScript 语法扩展,浏览器原生并不认识.jsx文件,因此从 JSX 开发到浏览器运行,中间必须有一个编译过程。目前最常用的方案是webpack + babel组合:

  • babel负责语法层面的转换:@babel/preset-env将 ES6/ES7 新语法降级为浏览器兼容版本,@babel/preset-react将 JSX 转换为React.createElement(...)调用;
  • webpack负责模块打包与资源处理:把多个入口(entry)、组件模块、样式资源统一打包输出到静态目录,并支持开发模式 watch 增量编译与生产模式压缩混淆。

前端待编译源文件目录结构

原笔记中所述目录为demos/project/static/,在仓库中对应的实际路径为 demo/project/static。该目录采用三层结构组织:

. ├── build # 编译的 webpack 脚本 │ ├── babel.config.js │ ├── webpack.base.config.js # 基础编译脚本(入口/输出/loader/插件) │ ├── webpack.dev.config.js # 开发环境编译脚本 │ └── webpack.prod.config.js # 生产环境编译脚本 ├── output # 输出文件 │ ├── asset # 静态资源(如图片) │ └── dist # react.js 编译后的文件目录(css/js 产物) └── src # 待编译的 ES6/7、JSX 源代码 ├── api # 前端接口请求封装(sign-in.js、sign-up.js) ├── apps # 页面 react.js 应用(admin.jsx、error.jsx、index.jsx、work.jsx) ├── components # jsx 模块、组件(footer-common、form-group、header-nav、sign-in-form、sign-up-form) ├── pages # react.js 执行 render 的文件目录(admin.js、error.js、index.js、work.js) ├── texts # 文案常量 └── utils # 通用工具(request、datetime、tool、upload)

三个关键目录的分工可以这样理解:

目录职责文件产出
src/apps页面级 React 组件(布局 + 业务组合)*.jsx组件树定义
src/components可复用 UI 组件(导航、页脚、表单等)*.jsx组件模块
src/pagesrender 入口,调用ReactDOM.render挂载应用*.js打包入口 chunk

页面 React 应用组件:apps/*.jsx

以首页应用 index.jsx 为例,它演示了一个页面级组件如何组合布局与公共组件:

import React from 'react' import { Layout, Menu, Breadcrumb } from 'antd' import HeadeNav from './../components/header-nav.jsx' import FooterCommon from './../components/footer-common.jsx' import 'antd/lib/layout/style/css' const { Header, Content, Footer } = Layout class App extends React.Component { render() { return ( <Layout className="layout"> <HeadeNav/> <Content style={{ padding: '0 50px' }}> <Breadcrumb style={{ margin: '12px 0' }}> <Breadcrumb.Item>Home</Breadcrumb.Item> </Breadcrumb> <div style={{ background: '#fff', padding: 24, minHeight: 280 }}> <p>index</p> </div> </Content> <FooterCommon /> </Layout> ) } } export default App

几个值得注意的实践点:

  1. UI 库选用 antd:从antd按需引入Layout、Menu、Breadcrumb,并通过import 'antd/lib/layout/style/css'单独引入组件样式;
  2. 组件复用:页头导航<HeadeNav/>与页脚<FooterCommon/>都是从../components/引入的公共组件,实现跨页面复用;
  3. 类组件写法:class App extends React.Component配合render()返回 JSX,是该项目采用的组件形态。

仓库中其余三个页面应用组件与之同构,但展示了不同的布局侧重:

  • admin.jsx:在标准 Layout 中嵌入<FormGroup />组件,该组件内部使用 antdTabs组织"登录/注册"两个 TabPane,并引用 sign-in-form.jsx 与sign-up-form.jsx;
  • work.jsx:采用Layout内嵌Sider(侧边栏)的管理后台布局,通过组件state(collapsed)配合toggle()实现侧边栏的展开/收起交互,展示了类组件状态管理;
  • error.jsx:极简错误页,同样复用FooterCommon,并引入全局样式 index.less 形成统一样式基调。

执行 render 渲染:pages/*.js

每个apps/*.jsx应用组件都对应一个pages/*.js入口文件,其作用是把组件实例挂载到 HTML 中预留的id="app"节点上。以 pages/index.js 为例:

import '@babel/polyfill'; import React from 'react' import ReactDOM from 'react-dom' import App from './../apps/index.jsx' ReactDOM.render( <App />, document.getElementById("app"))

对应 pages/admin.js、pages/work.js、pages/error.js 四个入口的代码模式完全一致,仅导入的应用组件不同:

  • admin.js:额外import 'whatwg-fetch'以引入 fetch polyfill(因为 admin 页涉及登录/注册的异步请求);
  • work.js/error.js:仅导入各自应用组件后挂载。

这里@babel/polyfill的引入保证了async/await等 ES7 特性与fetch在目标浏览器中的可用性。

webpack4 + babel7 编译配置

JSX 源文件要变成浏览器可运行代码,核心依赖 static/build 目录下的四份构建配置。

babel@7 配置:babel.config.js

babel.config.js 定义了 JSX 与 ES6/7 的转换规则:

const babelConfig = { presets: [ '@babel/env', '@babel/preset-react' ], 'plugins': [ ['import', { 'libraryName': 'antd', 'libraryDirectory': 'lib' }, 'ant'], ['import', { 'libraryName': 'antd-mobile', 'libraryDirectory': 'lib' }, 'antd-mobile'], '@babel/plugin-proposal-class-properties' ] }; module.exports = babelConfig;

配置要点:

  • @babel/env:按目标环境转换 ES 新语法(代码中注释掉了按targets指定浏览器版本、useBuiltIns: 'usage'按需注入 polyfill 的进阶写法);
  • @babel/preset-react:JSX →React.createElement的转换核心;
  • babel-plugin-import:antd / antd-mobile按需加载插件,只打包用到的组件与样式,减小产物体积;
  • @babel/plugin-proposal-class-properties:支持类属性(class properties)语法。

基础编译脚本:webpack.base.config.js

webpack.base.config.js 是三类编译(开发/生产)共享的基础配置:

const path = require('path'); const webpack = require('webpack'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const babelConfig = require('./babel.config'); const srcResolve = function (file) { return path.join(__dirname, '..', 'src', file); }; const distResolve = function (file) { return path.join(__dirname, '..', 'output', 'dist', file); }; module.exports = { entry: { 'admin' : srcResolve('pages/admin.js'), 'work' : srcResolve('pages/work.js'), 'index' : srcResolve('pages/index.js'), 'error' : srcResolve('pages/error.js'), }, output: { path: distResolve(''), filename: 'js/[name].js' }, module: { rules: [ { test: /\.(js|jsx)$/, use: { loader: 'babel-loader', options: babelConfig } }, { test: /\.(css|less)$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', options: { plugins: () => [] } }, 'less-loader' ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].css' }) ], optimization: { splitChunks: { cacheGroups: { commons: { test: /[\\/]node_modules[\\/]/, name: 'vendor', chunks: 'all' } } } } };

对这份配置的解读:

  • 多入口(entry):admin、work、index、error四个页面各自独立入口,对应生成output/dist/js/admin.js、work.js、index.js、error.js;
  • JS/JSX 规则:所有.(js|jsx)文件统一走babel-loader并套用上述 babel 配置;
  • 样式规则:less-loader→postcss-loader→css-loader→MiniCssExtractPlugin.loader的链式处理,支持.less预处理,并将 CSS 抽取为独立文件css/[name].css;
  • 公共代码抽取:optimization.splitChunks.cacheGroups.commons把node_modules中的第三方依赖统一打进名为vendor的 chunk,产出js/vendor.js,实现多页面公共依赖只加载一份。

开发 / 生产环境编译脚本

为了方便统一管理,开发与生产配置都通过webpack-merge继承基础配置:

  • 开发环境 webpack.dev.config.js:

    process.env.NODE_ENV = 'development'; const merge = require('webpack-merge'); const config = require('./webpack.base.config'); module.exports = merge(config, { mode: 'development', devtool: '#source-map' });

    以development模式 +source-map提供源码调试能力,配合webpack --watch实现改动即时重编译;

  • 生产环境 webpack.prod.config.js:

    process.env.NODE_ENV = 'production'; const merge = require('webpack-merge'); const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const config = require('./webpack.base.config'); module.exports = merge(config, { mode: 'production', optimization: { minimizer: [ new UglifyJsPlugin({ cache: true, parallel: true }), new OptimizeCSSAssetsPlugin({}) ] } });

    以production模式开启代码压缩,UglifyJsPlugin(cache + parallel 加速)混淆压缩 JS,OptimizeCSSAssetsPlugin压缩 CSS。

静态页面引用 React.js 编译产物

编译完成后,服务端模板只需按约定引入vendor公共包 + 对应页面包。仓库中以 EJS 模板呈现,见 server/views/index.ejs:

<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel="stylesheet" href="/output/dist/css/vendor.css"> </head> <body> <div id="app"></div> <script src="/output/dist/js/vendor.js"></script> <script src="/output/dist/js/index.js"></script> </body> </html>

关键点:

  • <div id="app"></div>是 React 挂载的容器节点,与pages/index.js中document.getElementById("app")一一对应;
  • 先加载公共依赖vendor.js/vendor.css,再加载页面专属包index.js/index.css,保证 React、antd 等公共依赖在页面脚本执行前就绪;
  • 四个页面(index.ejs、admin.ejs、work.ejs、error.ejs)结构一致,仅替换页面包名(index.js/admin.js/work.js/error.js)。

前后端串联:Koa2 静态资源与路由渲染

React 编译产物最终由 Koa2 服务端承载。在 server/app.js 中,关键的两段配置:

// 配置静态资源加载中间件 app.use(koaStatic( path.join(__dirname , './../static') )) // 配置服务端模板渲染引擎中间件 app.use(views(path.join(__dirname, './views'), { extension: 'ejs' }))
  • koa-static把static目录作为静态资源根,因此模板中的/output/dist/...路径会命中编译产物;
  • koa-views+ EJS 负责服务端模板渲染。

路由层面,routers/index.js 将/、/api、/admin、/work、/error五个子路由挂载到各自前缀;以首页为例,routers/home.js 将GET /交给 controllers/index.js,后者执行ctx.render('index', { title: 'home' })渲染出上述 EJS 页面。由此形成完整链路:

浏览器请求 / → Koa 路由 → controller 渲染 EJS → 返回 HTML(含 #app 节点) ↓ 浏览器解析 HTML → 加载 /output/dist 编译产物(vendor.js + index.js) ↓ ReactDOM.render 执行 → React 应用接管 #app 节点 → 页面动态渲染

编译产物的动态交互延伸

React 页面不止于静态渲染,项目中还配套了前端请求层。以 utils/request.js 为例,它基于whatwg-fetch封装了get/post/form三种请求方式,统一设置credentials: 'include'以便携带 session Cookie,并将 fetch 的 Promise 链收敛为resolve/reject的 Promise 形态;api/sign-in.js 则在请求层之上封装登录接口,表单提交时先经 sign-in-form.jsx 的validateFields校验,再调用signInApi并依据result.success弹出 antd 的message成功/失败提示。这印证了 React 层与后端接口(/api/user/signIn.json)的完整闭环交互。

构建与运行命令

整个项目的启动命令集中在 demo/project/package.json 的scripts中,React 相关部分如下:

"dev": "npm run dev_server & npm run dev_static", "dev_server": "nodemon --harmony ./server/app", "dev_static": "webpack --watch --config ./static/build/webpack.dev.config.js", "start_static": "webpack --config ./static/build/webpack.prod.config.js", "start_server": "node ./server/app.js", "start": "npm run start_static & npm run start_server"
  • 开发阶段:npm run dev_static以--watch模式监听src源码变化即时重编译(开发模式 + source-map),配合dev_server启动的 Koa2 服务,改完前端代码刷新页面即可生效;
  • 生产部署:npm run start_static执行生产构建(压缩混淆),再npm run start_server启动服务;
  • 依赖方面,react/react-dom版本为^16.6.3,antd为^3.10.9,构建工具链采用webpack ^4.26.1+babel-loader ^8.0.4(见 package.json 的 dependencies 与 devDependencies),运行时要求 Node>= 7.0.0。

页面渲染效果

编译并启动服务后,浏览器访问首页即可看到 React 应用完成挂载渲染的完整页面效果:

页面中 antdLayout的整体骨架、顶部导航栏(HeaderNav)、面包屑(Breadcrumb)、内容区与页脚(FooterCommon)均由 React 组件在#app容器内动态渲染生成,验证了从 JSX 源码 → webpack+babel 编译 → virtual DOM → 浏览器真实 DOM 的整条链路是通畅可用的。

小结

通过本项目的前端实践可以看到一套标准的 React.js 接入模式:JSX 源码(apps/components)→ pages 入口 → webpack4 + babel7 编译 → output/dist 产物 → EJS 模板按约定引用 → Koa2 静态服务承载 → ReactDOM 挂载渲染。其中static/build的三份 webpack 配置(base/dev/prod)与babel.config.js可以直接复制到其他 Koa2 项目复用;若需要了解 webpack4 环境的完整搭建细节,可进一步阅读仓库笔记 note/project/webpack4.md,并结合 demo/project/static/src 下的 apps、components、pages 三类源码加深理解。

  • 文档
  • 教程
  • 后端

【免费下载链接】koa2-note

《Koa2进阶学习笔记》已完结🎄🎄🎄

项目地址:https://gitcode.com/gh_mirrors/ko/koa2-note
点击查看免费下载

相关推荐

上一篇:Driver Store Explorer完整教程:十分钟用RAPR给C盘瘦身并根治驱动冲突
下一篇:C盘告急时我做了一次Windows驱动清理:RAPR帮我在一刻钟里找回12GB

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进