使用webpack打包react多页应用
demo地址: https://github.com/zhuweileo/koa-login
1. 依赖清单
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"autoprefixer": "^9.4.7",
"babel-loader": "^8.0.5",
"cross-env": "^5.2.0",
"css-loader": "^2.1.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"postcss-loader": "^3.0.0",
"react-hot-loader": "^4.6.4",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.4.1"
},
2. webpack配置文件(部署)
webpack.config.js
var path = require('path');
var htmlPlugin = require('html-webpack-plugin');
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
var {pages} = require('./page-map');
module.exports = {
entry: getEntry(pages),
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name]/[name].js', // 保证每个页面可以打包进不同文件夹
},
mode: 'production',
externals:{
react: 'React', //不将react打包进来
'react-dom': 'ReactDOM'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use:{
loader: "babel-loader",
}
},
{
test: /\.css$/,
// exclude: /node_modules/, //不能加这个,否则import 'antd/dist/antd.css' 时会报错
use: [
process.env.NODE_ENV === 'production'? MiniCssExtractPlugin.loader: 'style-loader',
"css-loader",
]
},
{
test: /\.scss$/,
use: [
// fallback to style-loader in development
process.env.NODE_ENV === 'production'? MiniCssExtractPlugin.loader: 'style-loader',
"css-loader",
"sass-loader",
]
},
]
},
plugins: getPlugins(pages),
}
function getEntry(pages) {
var entry = {}
pages.forEach(function (item) {
entry[item.name] = item.entry;
})
return entry
}
function getPlugins(pages) {
var plugins = [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name]/[name].css", //保证每个页面的css可以放在自己的文件夹内
chunkFilename: "[name]/[id].css"
})
]
pages.forEach(function (item) {
var plugin =new htmlPlugin({
filename: `${item.name}/${item.name}.html`, //保证每个页面的html可以放在自己的文件夹内
template: path.resolve(__dirname,item.template),
chunks: [item.name], //html只引入自己的js和css
})
plugins.push(plugin);
})
return plugins
}
page-map.js
var path = require('path');
var pages = [
{
name: 'home',
entry: path.resolve(__dirname, '../src/entry/home.js'),
template: path.resolve(__dirname, '../src/templates/home.html'),
},
{
name: 'login',
entry: path.resolve(__dirname, '../src/entry/login.js'),
template: path.resolve(__dirname, '../src/templates/login.html'),
},
]
module.exports = {
pages,
}
.babelrc
{
"presets": ["@babel/preset-env","@babel/preset-react"]
}
如何使用react hook 获取数据
该总结翻译自以下文章的部分内容(根据自己理解做了修改,文章有点长没翻译完):
https://www.robinwieruch.de/react-hooks-fetch-data/
使用hook获取数据
这里使用axios获取数据,第一版代码如下:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios(
'http://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
});
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
上面代码的问题在于:useEffect会在组件装载和更新的时候执行,然后我们又会在useEffect的回掉中去更新组件,就会引起一个死循环。我们实际上只想在组件装载的时候获取数据。
第二版修改如下:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios(
'http://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
}, []); //修改在这里
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
useEffect的第二个参数,可以effect所依赖的变量,依赖发生变化,effect回掉才会执行。如果第二个参数为空数组,代表effect不应依赖任何变量,因此,effect回掉只在组件装载时执行。
上面代码仍然是有问题的,我们把一个async函数作为useEffect函数的回掉,async函数默认返回一个promise对象,但是useEffect的回掉函数应该不返回内容,或者返回一个纯函数,控制台中会有如下警告Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect.
我们进行第三版代码调整:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'http://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
}, []);
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
如何使用程序触发一个hook式的数据获取
以上我们讲了如何在组件装载时通过hook获取数据,下面举例如何通过input出发数据的获取。
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [search, setSearch] = useState('redux');
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://hn.algolia.com/api/v1/search?query=${search}`,
);
setData(result.data);
};
fetchData();
}, [search]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="button" onClick={() => setSearch(query)}>
Search
</button>
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
上面的例子中,useEffect的二个参数中多了一个search变量,因此useEffect不仅会在装载组件时执行,也会在search变化时执行,注意search是一个state hook。根据代码,search的值会在点击按钮时,取query的值,query的值会在input的值改变时改变。因此流程就是:
- input值改变query值改变
- 点击按钮,取此时的query值去作为search的值
- search的值改变触发useEffect hook去请求接口获取数据。
使用hook显示加载中
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [url, setUrl] = useState(
'http://hn.algolia.com/api/v1/search?query=redux',
);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const result = await axios(url);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [url]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button
type="button"
onClick={() =>
setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
}
>
Search
</button>
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;
原理很简单:当数据开始加载时isLoading状态赋值为true,加载完成时赋值为false
使用hook进行错误处理
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [url, setUrl] = useState(
'http://hn.algolia.com/api/v1/search?query=redux',
);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button
type="button"
onClick={() =>
setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
}
>
Search
</button>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;
道理和loading是一样的,使用try catch捕获错误,改变isError状态。