基本使用
-
安装:
npm i create-react-app -g
-
查看版本:
create-react-app -V
-
创建
Javascript
项目:create-react-app projectname
-
创建
Typescript
项目:create-react-app projectname --typescript
备注:新版本推荐不全局安装create-react-app
。而是采用npx
或者yarn
的方式创建项目
# 如果有全局安装过,先卸载
npm uninstall -g create-react-app
# npx方式创建项目
npx create-react-app my-app
# npm方式创建项目
npm init react-app my-app
# yarn方式创建项目
yarn create react-app my-app
# 创建typescript项目,以npx方式为例
npx create-react-app my-app --template typescript
# 创建typescript项目,以yarn方式为例
yarn create react-app my-app --template typescript
-
弹出配置(单向操作):
npm run eject
-
项目改造
├── package.json
├── public # 这个是webpack的配置的静态目录
│ ├── favicon.ico
│ ├── index.html # 默认是单页面应用,这个是最终的html的基础模板
│ └── manifest.json
├── src
│ ├── assets # 图片等静态资源
│ ├── store # 状态
│ │ ├── actions.js # actions
│ │ ├── actionType.js # action type
│ │ ├── reducers # reducers目录
│ │ │ ├── index.js # 根reducer
│ │ │ └── todos.js # 其他reducer,每个reducer一个文件
│ │ └── index.js # 主文件
│ ├── router # 路由
│ │ ├── config.js # 配置
│ │ ├── FrontendAuth.js # 路由守卫
│ │ └── index.js # 主文件
│ ├── serve # 请求
│ │ └── index.js # axios
│ ├── views # 页面
│ ├── App.css # App根组件的css
│ ├── App.js # App组件代码
│ ├── App.test.js
│ ├── index.css # 启动文件样式
│ ├── index.js # 启动的文件(开始执行的入口)!!!!
│ ├── logo.svg
│ └── serviceWorker.js
└── yarn.lock
参考链接
配置
修改端口号
- 修改
package.json
文件
{
// ...
"scripts": {
"start": "set PORT=8080 && react-scripts start",
// ...
},
// ...
}
关闭自动启动浏览器
- 方法一:修改
package.json
文件
{
// ...
"scripts": {
"start": "set BROWSER=none&& react-scripts start",
// ...
},
// ...
}
注意:BROWSER=none
和&&
之间不要留有空格。否则会出现异常弹窗。
这里是环境变量中将空格也设置在了BROWSER
字段中,但是create-react-app
没有做trim
处理导致的。
- 方法二:在根目录下新建
.env
文件
BROWSER=none
使用相对路径
- 修改
package.json
文件,添加"homepage": "."
{
// ...
"homepage": "."
}
配置多环境
- 安装
dotenv-cli
:npm i dotenv-cli --save-dev
- 在根目录下添加
.env.dev
文件
REACT_APP_URL_API=http://dev.com
REACT_APP_URL_UPLOAD=http://upload.dev.com
- 在根目录下添加
.env.sit
文件
REACT_APP_URL_API=http://sit.com
REACT_APP_URL_UPLOAD=http://upload.sit.com
- 在根目录下添加
.env.prod
文件
REACT_APP_URL_API=http://prod.com
REACT_APP_URL_UPLOAD=http://upload.prod.com
- 修改
package.json
文件
{
// ...
"scripts": {
"start": "dotenv -e .env.dev react-scripts start",
"build:sit": "dotenv -e .env.sit react-scripts build",
"build:prod": "dotenv -e .env.prod react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
// ...
}
- 在
index.html
中使用%REACT_APP_URL_API%
- 在
js/jsx
中:process.env.REACT_APP_URL_API
样式
CSS行内样式
- 可以写行内样式
const styleDemo = {
background: 'red'
}
// ...
<div style={styleDemo}></div>
引入外部CSS样式
- 写在单独的文件中(比如
index.css
),通过import './index.css'
引入
.theList {
background: lightcoral
}
.theList p{
background: greenyellow;
}
// 备注:这种方式引入的css样式是全局的,很容易造成全局污染
import './index.css'
// ...
<div className="theList">
<div>theList</div>
<p>backgound</p>
</div>
- 使用
css module
方式(css
文件必须带有.module
,比如index.module.css
),通过import styles from './index.module.css'
引入
.red {
--red: red;
background: var(--red);
}
.theList {
background: lightcoral
}
.theList p{
background: greenyellow;
}
.theList .blue{
background: lightskyblue;
}
// 备注:css module的方式不用全局污染,会自动生成 home_theList__1uczA 这种类名
import styles from './index.module.css'
// ...
<div className={styles.theList}>
<div>theList</div>
<p>backgound</p>
<div className={styles.blue}>我们都是中国人</div>
</div>
使用less
-
弹出配置:
npm run eject
,执行后会在根目录下出现config/
目录和scripts/
目录 -
下载
less
和less-loader
:npm i less less-loader --save-dev
-
修改
config/webpack.config.js
文件
// ...
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
// ...
module.exports = function(webpackEnv) {
// ...
return {
// ...
module: {
// ...
rules: [
// ...
{
oneOf: [
// ...
// 在 sassModuleRegex 后面,必须在 file-loader 之前
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}, 'less-loader'),
sideEffects: true,
},
{
test: lessModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
}, 'less-loader'),
},
// ...
]
}
// ...
]
// ...
}
// ...
}
// ...
}
- 具体使用参考上面的引入外部CSS样式
多个类名
-
下载
classnames
:npm i classnames --save
-
在组件中使用
// ...
import cx from 'classnames'
// ...
<span className={cx(
"todo-item__text",
todo && todo.completed && "todo-item__text--completed"
)}>
</span>
路由-Router
-
下载:
npm i react-router-dom --save
-
如果用
withRouter
的话,还需要下载react-router
:npm i react-router --save
-
新建
src/router/
目录 -
在
src/router/
目录下新建index.jsx
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import HomePage from '../views/home'
import ProductsPage from '../views/products'
import ProductsDetailPage from '../views/products/detail'
import AboutPage from '../views/about'
import NotFound from '../views/error/404'
function Routes() {
return (
<Switch>
<Route path="/" exact component={HomePage}></Route>
{/* <Route path="/" exact render={() => <Redirect to="/products"></Redirect>}></Route> */}
<Route path="/products" exact component={ProductsPage}></Route>
<Route path="/products/:id" component={ProductsDetailPage}></Route>
<Route path="/about" component={AboutPage}></Route>
<Route component={NotFound}></Route>
</Switch>
)
}
export default Routes
- 修改
src/App.js
文件
import React from 'react';
import { HashRouter } from 'react-router-dom'
import Routes from './router'
import './App.css';
function App() {
return (
<HashRouter>
<Routes></Routes>
</HashRouter>
)
}
export default App;
- 子路由示例:修改
src/views/about/index.jsx
文件
import React from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import LayoutDefault from '../layout'
import AboutCompany from './company'
import AboutHistory from './history'
import AboutLocation from './location'
import AboutServices from './services'
function AboutPage() {
return (
<LayoutDefault>
<div>About page</div>
<Route path='/about' exact component={AboutCompany}/>
<Route path="/about/history" component={AboutHistory}></Route>
<Route path="/about/services" component={AboutServices}></Route>
<Route path="/about/location" component={AboutLocation}></Route>
</LayoutDefault>
)
}
export default AboutPage
数据管理-Redux
参考:Redux笔记
路由切换动画
react-transition-group
-
安装:
npm i react-transition-group --save
-
在
src/components/
目录下新建AnimatedSwitch/
目录 -
在
src/components/AnimatedSwitch/
目录下新建index.css
/* 很多动画都需要给路由对应组件最外层元素设置position: absolute; */
.page {
/* will-change: transform; */
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
/* 帧动画 */
.fade-enter {
opacity: 0;
}
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 300ms ease-in;
}
.fade-exit {
opacity: 1;
}
.fade-exit.fade-exit-active {
opacity: 0;
transition: opacity 300ms ease-in;
}
- 在
src/components/AnimatedSwitch/
目录下新建index.jsx
import React from 'react'
import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { Route, Switch } from 'react-router-dom'
import './index.css'
const AnimatedSwitch = props => {
const { children } = props
return (
<Route render={({ location }) => (
<TransitionGroup>
<CSSTransition
key={location.pathname}
classNames={props.type || 'fade'}
timeout={props.duration || 300}
>
<Switch location={location}>{children}</Switch>
</CSSTransition>
</TransitionGroup>
)} />
)
}
export default AnimatedSwitch
- 修改
src/router/index.jsx
,将其中的Switch
修改为AnimatedSwitch
// ...
import AnimatedSwitch from '../components/AnimatedSwitch'
// ...
function Routes() {
return (
<AnimatedSwitch>
<Route path="/" exact component={HomePage}></Route>
<Route path="/products" exact component={ProductsPage}></Route>
<Route path="/products/:id" component={ProductsDetailPage}></Route>
<Route path="/about" component={AboutPage}></Route>
<Route path="/todos" component={Todos}></Route>
<Route component={NotFound}></Route>
</AnimatedSwitch>
)
}
// ...
- 修改
src/views/layout/index.jsx
文件,给最外层添加className="page"
react-animated-router
-
安装
npm i react-animated-router --save
-
修改
src/router/index.jsx
,将其中的Switch
修改为AnimatedRouter
// ...
import AnimatedRouter from 'react-animated-router'
import 'react-animated-router/animate.css'
// ...
function Routes() {
return (
<AnimatedRouter>
<Route path="/" exact component={HomePage}></Route>
<Route path="/products" exact component={ProductsPage}></Route>
<Route path="/products/:id" component={ProductsDetailPage}></Route>
<Route path="/about" component={AboutPage}></Route>
<Route path="/todos" component={Todos}></Route>
<Route component={NotFound}></Route>
</AnimatedRouter>
)
}
// ...
ant-motion
-
官方网站:Ant-Motion
-
一个强大的动画库。能够快速在
React
框架中使用动画 -
提供了单项,组合动画,以及整套解决方案
-
安装
npm i rc-queue-anim --save
-
修改
src/router/index.jsx
文件
import React from 'react'
import { Route, withRouter } from 'react-router-dom'
import QueueAnim from 'rc-queue-anim'
import HomePage from '../views/home'
import ProductsPage from '../views/products'
import ProductsDetailPage from '../views/products/detail'
import AboutPage from '../views/about'
import Todos from '../views/todos'
// import NotFound from '../views/error/404'
const routes = [
{ path: '/', name: 'Home', Component: HomePage },
{ path: '/products', name: 'Products', Component: ProductsPage },
{ path: '/products/:id', name: 'ProductDetail', Component: ProductsDetailPage },
{ path: '/about', name: 'About', Component: AboutPage },
{ path: '/todos', name: 'Todos', Component: Todos },
]
function Routes(props) {
const { pathname } = props.location
const page = routes.find(v=>v.path === pathname)
return (
<QueueAnim type="scaleX" duration={800}>
<Route key={page.path} exact path={page.path} component={page.Component}></Route>
</QueueAnim>
)
}
export default withRouter(Routes)
antd-mobile
按需引入
-
下载
antd-mobile
:npm i antd-mobile --save
-
按需引入需要下载
babel-plugin-import
:npm i babel-plugin-import --save-dev
-
修改
package.json
文件
{
// ...
"babel": {
// ...
"plugins": [
["import", { "libraryName": "antd-mobile", "style": true }] // true: less "css": css
]
},
// ...
}
- 在组件中使用
import { Button } from 'antd-mobile'
<Button type="primary">primary</Button>
修改主题
-
需要提前下载和配置
less
。配置的时候注意,.less
文件不能开启module
-
修改
package.json
文件,在这里添加要覆盖的变量。变量参考:ant-design-mobile 默认主题样式
{
// ...
"theme": {
"brand-primary": "red",
"color-text-base": "#333",
"primary-button-fill": "green"
},
// ...
}
- 修改
config/webpack.config.js
文件
// ...
const theme = require('../package.json').theme;
// ...
module.exports = function(webpackEnv) {
// ...
return {
// ...
module: {
// ...
rules: [
// ...
{
oneOf: [
// ...
// 在 sassModuleRegex 后面,必须在 file-loader 之前
{
test: lessRegex,
exclude: lessModuleRegex,
use: [
...getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
{ loader: 'less-loader', options: { modifyVars: theme } }
],
sideEffects: true,
},
{
test: lessModuleRegex,
use: [
...getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
}),
{ loader: 'less-loader', options: { modifyVars: theme } }
],
},
// ...
]
}
// ...
]
// ...
}
// ...
}
// ...
}
- 重启即可看到效果
使用svg
-
下载
svg-sprite-loader
:npm i svg-sprite-loader --save-dev
-
在
src/
目录下新建icons/
目录 -
在
src/icons/
目录下新建svg/
目录,用来放置svg
图标文件 -
在
src/components/
目录下新建SvgIcon/
目录 -
在
src/components/SvgIcon/
目录下新建style.module.less
文件
.svg-class {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
- 在
src/components/SvgIcon/
目录下新建index.jsx
文件
import React from "react";
import PropTypes from "prop-types";
import styles from "./style.module.less";
const SvgIcon = props => {
const { iconClass, fill } = props;
return (
<i aria-hidden="true" className="svg-icon">
<svg className={styles["svg-class"]}>
<use xlinkHref={"#icon-" + iconClass} fill={fill} />
</svg>
</i>
);
};
SvgIcon.propTypes = {
// svg名字
iconClass: PropTypes.string.isRequired,
// 填充颜色
fill: PropTypes.string
};
SvgIcon.defaultProps = {
fill: "currentColor"
};
export default SvgIcon;
- 在
src/icons/
目录下新建index.js
文件
const requireAll = requireContext => requireContext.keys().map(requireContext);
const svgs = require.context("./svg", false, /\.svg$/);
requireAll(svgs);
- 修改
App.js
文件
// ...
import "./icons"
// ...
- 修改
config/webpack.config.js
文件,让src/icons
目录下面的svg
文件使用svg-sprite-loader
打包
// ...
module.exports = function(webpackEnv) {
// ...
return {
// ...
module: {
// ...
rules: [
// ...
{
oneOf: [
// ...
// 必须在 file-loader 之前
{
test: /\.(svg)$/i,
loader: 'svg-sprite-loader',
include: path.resolve(__dirname, "../src/icons"),
options: {
// symbolId和use使用的名称对应 <use xlinkHref={"#icon-" + iconClass} />
symbolId: "icon-[name]"
}
},
// ...
]
}
// ...
]
// ...
}
// ...
}
// ...
}
- 在组件中使用
import SvgIcon from './components/SvgIcon'
<SvgIcon iconClass="android" />
服务器端渲染
-
首先需要运行在服务器端。这里以
koa2
为例 -
基本的服务器端代码
// server/server.js
const Koa = require('koa')
const cors = require('koa2-cors')
const router = require('./router')
const app = new Koa()
// cors
app.use(cors({
origin: function(ctx) {
return '*'
},
// exposeHeaders: [],
maxAge: 5,
credentials: true,
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept']
}))
// router
app.use(router.routes()).use(router.allowedMethods())
app.listen(9093)
// server/router/index.js
const Router = require('koa-router')
const userRouter = require('./user')
let router = new Router()
router.use('/user', userRouter.routes())
// 所有的路径 最前面加一个 /api
router.use('/api', router.routes())
module.exports = router
思路
-
node
只支持commonjs
规范的模块,使用babel-node
将ES6 module
转成commonjs
模块 -
测试
jsx
语法的支持情况 -
骨架
public/index.html
,复制一份,删除掉其中的注释等。供后续手动拼接 -
使用
renderToString
将jsx
转成字符串 -
使用
css-modules-require-hook
处理代码中require
的css
文件 -
使用
asset-require-hook
处理代码中require
的图片、字体等资源文件(如果代码中使用import
引入的,需要修改成require
方式引入) -
代码打包后,会在
build/
目录下有asset-manifest.json
,该文件记录了初始时需要加载的文件和所有的文件 -
将初始时需要引入的
css
和js
文件手动引入
实现
让 node 支持 ES6 module
-
安装
@babel/cli
、@babel/node
:npm i @babel/cli @babel/node --save
-
修改
package.json
文件的scripts
部分:"server": "cross-env NODE_ENV=test nodemon --config nodemon --exec babel-node --harmony ./server/server.js"
-
修改
server/server.js
等文件,改成ES6 module
规范方式引入测试
// server/server.js
import Koa from 'koa'
import cors from 'koa2-cors'
import router from './router'
const app = new Koa()
// cors
app.use(cors({
origin: function(ctx) {
return '*'
},
// exposeHeaders: [],
maxAge: 5,
credentials: true,
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept']
}))
// router
app.use(router.routes()).use(router.allowedMethods())
app.listen(9093)
测试 jsx 支持情况
- 修改
server/server.js
文件
// ...
import Router from 'koa-router'
import React from 'react'
const router = new Router()
function App() {
return (<h2>react jsx test</h2>)
}
router.get('/', ctx => {
ctx.response.body = <App></App>
})
- 如果不对,需要配置
.babelrc
文件。比如如下配置
{
"presets": [
"react-app"
]
}
改造client代码
- 在
src/
目录下新建一个app.js
文件,将客户端和服务器端公共的代码放在这里。src/index.js
修改后代码类似如下
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './app'
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App></App>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
src/store/index.js
文件中如果有加window.devToolsExtension
,需要区分下。比如可以配置process.env
。修改package.json
文件的scripts
部分"server": "cross-env NODE_ENV=test platform=server nodemon --config nodemon --exec babel-node --harmony ./server/server.js"
。然后修改src/store/index.js
文件如下
import { createStore, applyMiddleware, compose } from 'redux'
import rootReducer from './reducers'
import thunk from 'redux-thunk'
const store = process.env.platform === 'client'
? createStore(rootReducer, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f=>f
))
: createStore(rootReducer, compose(
applyMiddleware(thunk)
))
export default store
改造server代码
- 修改
server/server.js
文件,添加静态文件支持
// ...
import koaStatic from 'koa-static'
// ...
// static 放在router之前
const staticPath = '../build'
app.use(koaStatic(
path.join(__dirname, staticPath)
))
// router
app.use(router.routes()).use(router.allowedMethods())
// ...
- 修改
server/server.js
文件,添加非静态文件和接口的处理
// ...
import templateController from './template'
// ...
app.use(templateController)
// static
const staticPath = '../build'
app.use(koaStatic(
path.join(__dirname, staticPath)
))
// router
app.use(router.routes()).use(router.allowedMethods())
// ...
- 添加
server/template.js
export default (ctx, next) => {
if (ctx.url.startsWith('/api/') || ctx.url.startsWith('/static/')) {
return next()
}
ctx.response.body = 'test'
}
- 修改
server/template.js
文件,添加renderToString
编译jsx
代码
import React from 'react';
// 服务端需要使用 StaticRouter
import { StaticRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from '../src/store'
import App from '../src/app'
import { renderToString } from 'react-dom/server'
// 服务器端使用 renderToString 解析对应的jsx,生成html模板
function template(url) {
let context = {}
const markup = renderToString(
(<Provider store={store}>
<StaticRouter
location={url}
context={context}
>
<App></App>
</StaticRouter>
</Provider>)
)
return markup
}
export default (ctx, next) => {
if (ctx.url.startsWith('/api/') || ctx.url.startsWith('/static/')) {
return next()
}
ctx.response.body = template(ctx.url)
}
- 修改
server/template.js
文件,添加hooks
处理引入的css
文件、图片和字体等资源文件
// hooks - 放在文件最上面
import csshook from 'css-modules-require-hook/preset'
import assethook from 'asset-require-hook'
assethook({
extensions: ['png']
})
// ...
- 跟目录下新建
cmrh.conf.js
文件
module.exports = {
// Same scope name as in webpack build
generateScopedName: '[name]__[local]___[hash:base64:5]',
}
- 修改
server/template.js
文件,将骨架和解析后的代码合并
// ...
export default (ctx, next) => {
if (ctx.url.startsWith('/api/') || ctx.url.startsWith('/static/')) {
return next()
}
const page = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">${template(ctx.url)}</div>
</body>
</html>
`
ctx.response.body = page
}
- 修改
server/template.js
文件,引入初始的css
、js
资源
// ...
import { entrypoints } from '../build/asset-manifest.json'
// ...
function getFiles(arr) {
const obj = {}
arr.forEach(file => {
const arr = file.split('.')
const ext = arr[arr.length - 1]
const types = ['css', 'js']
if (types.indexOf(ext) !== -1) {
obj[ext] ? obj[ext].push(file) : (obj[ext] = [file])
}
})
return obj
}
// 将需要引入的css,组成 link 字符串
function cssLinks(arr) {
let res = ``
arr.forEach(item => {
res += `<link rel="stylesheet" href="${item}">`
})
return res
}
// 将需要引入的css,组成 script 字符串
function jsScripts(arr) {
let res = ``
arr.forEach(item => {
res += `<script src="${item}"></script>`
})
return res
}
export default (ctx, next) => {
if (ctx.url.startsWith('/api/') || ctx.url.startsWith('/static/')) {
return next()
}
// const page = fs.readFileSync('./build/index.html', 'utf-8')
const fileObj = getFiles(entrypoints)
const page = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
${cssLinks(fileObj['css'])}
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">${template(ctx.url)}</div>
${jsScripts(fileObj['js'])}
</body>
</html>
`
ctx.response.body = page
}
server/template.js
文件完整代码
// hooks
// css hook,处理通过 require 引入的 css
// 需要在根目录下新建并配置 cmrh.conf.js 文件
import csshook from 'css-modules-require-hook/preset'
// asset hook 处理图片、字体等资源文件
import assethook from 'asset-require-hook'
assethook({
extensions: ['png']
})
import React from 'react';
// 服务端需要使用 StaticRouter
import { StaticRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from '../src/store'
import App from '../src/app'
import { renderToString } from 'react-dom/server'
import { entrypoints } from '../build/asset-manifest.json'
// 服务器端使用 renderToString 解析对应的jsx,生成html模板
function template(url) {
let context = {}
const markup = renderToString(
(<Provider store={store}>
<StaticRouter
location={url}
context={context}
>
<App></App>
</StaticRouter>
</Provider>)
)
return markup
}
// css、js需要手动插入
// build/asset-manifest.json 文件的 entrypoints 就是初始化时需要加载的css、js文件
// 该方法将css、js区分开,放在不同的数组内。方便后续处理
function getFiles(arr) {
const obj = {}
arr.forEach(file => {
const arr = file.split('.')
const ext = arr[arr.length - 1]
const types = ['css', 'js']
if (types.indexOf(ext) !== -1) {
obj[ext] ? obj[ext].push(file) : (obj[ext] = [file])
}
})
return obj
}
// 将需要引入的css,组成 link 字符串
function cssLinks(arr) {
let res = ``
arr.forEach(item => {
res += `<link rel="stylesheet" href="${item}">`
})
return res
}
// 将需要引入的css,组成 script 字符串
function jsScripts(arr) {
let res = ``
arr.forEach(item => {
res += `<script src="${item}"></script>`
})
return res
}
export default (ctx, next) => {
if (ctx.url.startsWith('/api/') || ctx.url.startsWith('/static/')) {
return next()
}
// const page = fs.readFileSync('./build/index.html', 'utf-8')
const fileObj = getFiles(entrypoints)
const page = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
${cssLinks(fileObj['css'])}
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">${template(ctx.url)}</div>
${jsScripts(fileObj['js'])}
</body>
</html>
`
ctx.response.body = page
}
发表评论