11.3.2 动态加载分片
您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!
11.3.2 动态加载分片
针对webpack的配置只是告诉webpack分片打包,但是webpack没有“页面”的概念,还是需要修改JavaScript代码来确定怎样按照页面分片。
继续我们的应用实例,我们希望Home、About和NotFound页面每个都是按需加载的,这三个页面都应该有自己的分片,它们的内容也就不应该包含在主体的bundle.js文件中。
因为webpack的工作方式是根据代码中的import语句和require函数来找到所有的文件模块,所以,要让这三个页面不出现在bundle.js文件中,我们就不能再直接使用import命令来导入它们。
提示
对ES语法有一个提议是增加import函数从而实现动态的import,注意动态import是函数形式,代码类似import('./pages/Home.js'),和不带括号的静态的import语句不同。目前这个动态import的提议处于ES的Stage3阶段,本书中的例子没有使用这种语法,读者可以尝试使用动态import修改本书的例子取代require.ensure。
在src/Stores.js中,我们首先注释或者删掉针对Home、About和NotFound的import语句,代码如下:
import App from './pages/App.js';
//import Home from './pages/Home.js';
//import About from './pages/About.js';
//import NotFound from './pages/NotFound.js';
然后,我们在src/Routes.js中要利用Route的getComponent属性异步加载React组件,代码如下:
const getHomePage = (location, callback) => {
require.ensure([], function(require) {
callback(null, require('./pages/Home.js').default);
}, 'home');
};
const getAboutPage = (location, callback) => {
require.ensure([], function(require) {
callback(null, require('./pages/About.js').default);
}, 'about');
};
const getNotFoundPage = (location, callback) => {
require.ensure([], function(require) {
callback(null, require('./pages/NotFound.js').default);
}, '404');
};
Route的getComponent函数有两个参数,第一个参数nextState代表匹配到当前Route的信息,不过这里我们用不上这个参数;第二个参数是一个回调函数,回调函数遵从Node.js的回调函数风格,第一个参数代表是否有错误,第二个参数代表装载成功的组件,正因为这个回调函数的存在,使得异步加载组件成为可能,我们会在异步加载了对应组件之后再调用这个回调函数。
在这里,异步加载模块的方法使用require.ensure,ensure是require对象的一个属性,实际是一个函数。当webpack在做静态代码分析时,除了特殊处理import和require,也会特殊处理require.ensure,当遇到require.ensure函数调用,就知道需要产生一个动态加载打包文件。
require.ensure函数有三个参数,第一个参数是一个数组,第二个参数是一个函数,第三个参数是分片模块名。require.ensure所做的事情就是确保第二个函数参数被调用时,第一个参数数组中所有模块都已经被装载了。对于我们的应用,页面模块没有特殊的依赖关系,所以第一个参数保持一个空数组就行,要做的只是在第二个参数中通过require语句来装载对应的页面文件。
至于require.ensure函数的第三个参数,代表的是模块名,对应的就是上面在webpack配置文件中添加的chunkFilename参数中的[name]值,如果第三个参数没有的话,webpack会给每个模块分配一个数字代表的唯一ID作为chunk的名字,为了让分片文件名清晰,我们在代码中分别指定了模块名为home、about和404,那么我们预期最终会有三个分片文件产生,名字分别是home.chunk.js、about.chunk.js和404.chunk.js。
在Routes函数中使用getComponent而不是componet属性来使用异步加载页面的函数,代码如下:
const Routes = () => (
);
所以,完成动态加载分片要两个方面。
·使用require.ensure让webpack产生分片打包文件;
·使用React-Router的getComponent异步加载页面分片文件。
这个神奇的过程,关键在于webpack会对require.ensure函数调用做特殊处理,而且React-Router通过getComponent函数支持异步加载组件。
读者看到上面的代码可能会有一个疑问,我们给每个页面的getComponent属性都定义了一个函数,分别是getHomeComponent、getAboutComponent和getNotFoundComponent,这三个函数中的代码几乎相同,唯一的区别就是页面组件的文件位置和模块名。出于避免重复代码的目的,为什么不把这三个函数的共同部分提取成一个函数,把差别体现的函数参数中呢?比如使用像下面这样的代码:
const getPageCompnent = (pagePath, chunkName) => (nextState, callback) => {
require.ensure([], function(require) {
callback(null, require(pagePath).default);
}, chunkName);
};
然而,并不能这样做。因为webpack的打包过程是对代码静态扫描的过程,也就是说,webpack工作的时候,我们缩写的代码并没有运行,webpack看到的import和require的参数如果不是字符串而是一个需要运算的表达式,webpack就无从知道表达式运算结果是什么。
如果require的参数是字符串,那webpack就可以明确知道对应的文件模块位置,一切顺利;如果require的参数是一个变量,那么webpack就无法在静态扫描状态下确定哪些文件应该放在对应分片中,分片也就失效了。
现在,我们刷新网页,访问http://localhost:3000,在浏览器的网络工具中可以看到下载了三个文件,分别是common.bundle.js、bundle.js和home.chunk.js,其中home.chunk.js就是特定于Home的分片文件,当我们通过点击顶栏的About链接时,可以看到只有一个新下载的文件about.chunk.js,效果如图11-6所示。
图11-6 动态分页的效果
本节关于动态加载分片的代码,在本书Github代码库https://github.com/mocheng/react-and-redux/的目录chapter-11/react_router_chunked下可以找到。 深入浅出React和Redux