- 文档
- 教程
- 后端
【免费下载链接】koa2-note
《Koa2进阶学习笔记》已完结🎄🎄🎄
本篇技术指南聚焦 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/pages | render 入口,调用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几个值得注意的实践点:
- UI 库选用 antd:从
antd按需引入Layout、Menu、Breadcrumb,并通过import 'antd/lib/layout/style/css'单独引入组件样式; - 组件复用:页头导航
<HeadeNav/>与页脚<FooterCommon/>都是从../components/引入的公共组件,实现跨页面复用; - 类组件写法:
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进阶学习笔记》已完结🎄🎄🎄
相关推荐
Koa2 实战项目框架设计:koa2 + MySQL + ejs 服务端渲染与 webpack4 + React 前端渲染的工程化实践
Koa2 实战项目框架设计:koa2 + MySQL + ejs 服务端渲染与 webpack4 + React 前端渲染的工程化实践 导读 本文基于《Koa2
文档教程后端F2 图表库 Vue 集成实战指南:从 JSX 编译配置到组件化图表渲染
F2 图表库 Vue 集成实战指南:从 JSX 编译配置到组件化图表渲染 导读 本文基于 F2 官方文档 如何在 Vue 中使用 https://link.gi
UI组件前端设计系统高性能前端渲染:simple-virtual-dom表格排序的实现与原理
高性能前端渲染:simple virtual dom表格排序的实现与原理 引言:虚拟DOM如何解决表格排序的性能痛点 你是否遇到过这样的问题:当表格数据量超过1
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考