dpw*_*pwr 5 javascript reactjs webpack isomorphic-javascript
我正在研究在 React 应用程序中使用代码分割。
我似乎找不到一种方法来为服务器端渲染引入代码分割(和导入),从而干净地传递到客户端。
仅供参考:我知道有一种方法可以使用 React Router 来做到这一点,但我认为这是一个更普遍的问题,并不是每个人都想使用它。另外,我觉得代码分割不一定与路由同义。
下面是一个非常基本的类示例,它将加载和呈现分割代码包的内容SplitComponent。
如果服务器端渲染的路由包含此组件,则将确保在调用之前componentWillMount同步加载代码。它检查它是否是服务器端,因此它不会执行此客户端操作。requirerender
然后对于客户端,componentDidMount将异步SplitComponent加载System.import.
这样做的结果是服务器端呈现正确的页面并且客户端将显示它,但随后将立即componentDidMount导致客户端加载SplitComponent,在此期间它将不显示任何内容(但短暂地取决于加载时间)。最后,SplitComponent将加载并渲染。但当它被移除然后再次添加时,可能会出现闪烁。这削弱了在服务器上进行渲染的优势。
有更好的方法来处理这个问题吗?
import React from 'react';
const canUseDOM = !!(
(typeof window !== 'undefined' &&
window.document && window.document.createElement)
);
class Lazy extends React.Component {
constructor() {
super();
this.state = {
module: null
};
}
componentWillMount() {
if (!canUseDOM) {
const m = require('./SplitComponent');
this.setState({
module: m.default
});
}
}
componentDidMount() {
if (!this.state.module) {
System.import('./SplitComponent').then(m => {
this.setState({
module: m.default
});
});
}
}
render() {
const { module } = this.state;
console.log('Rendering Lazy', module);
if (module) {
return React.createElement(module);
}
return null;
}
}
export default Lazy;
Run Code Online (Sandbox Code Playgroud)
这似乎是一个棘手的问题,但我有一个似乎有效的解决方案。这并不理想,我非常希望看到替代方案。
基本思想是一个 React 组件可以触发import另一个 React 组件,以促进代码分割。这相当简单,但是扩展它以支持服务器端渲染增加了很多复杂性。
规则:
这是Lazy负责管理SplitComponent. 它使用了 2 个函数split.js
当Lazy在服务器端渲染时,componentWillMount运行并检查它是否实际上是服务器端。如果是,则会导致同步加载SplitComponent。加载的模块默认值存储在组件的状态中Lazy,以便可以立即呈现。它还向 Redux 分派一个操作来注册正在渲染的视图需要此包的事实。
./SplitComponent服务器端将成功渲染应用程序,并且 redux 存储将包含客户端需要包含的包这一事实。
//Lazy.jsx
import React from 'react';
import { connect } from 'react-redux';
import { splitComponent, splitComponentSync } from './split';
const canUseDOM = !!(
(typeof window !== 'undefined' &&
window.document && window.document.createElement)
);
class Lazy extends React.Component {
constructor() {
super();
this.state = {
module: null
};
}
componentWillMount() {
// On server side only, synchronously load
const { dispatch } = this.props;
if (!canUseDOM) {
// Also, register this bundle with the current component state as on
// the server there is only a single render and thus the redux state
// available through mapStateToProps is not up-to-date because it was
// requested before the above dispatch.
this.setState({
module: splitComponentSync(dispatch)
});
}
}
componentDidMount() {
const { dispatch, modules } = this.props;
if (!modules.hasOwnProperty('./SplitComponent')) {
splitComponent(dispatch);
}
}
render() {
const { module } = this.state;
const { modules } = this.props;
// On server side, rely on everything being loaded
if (!canUseDOM && module) {
return React.createElement(module);
// On client side, use the redux store
} else if (modules.hasOwnProperty('./SplitComponent') && modules['./SplitComponent']) {
return React.createElement(modules['./SplitComponent']);
}
return null;
}
}
function mapStateToProps(state) {
const modules = state.modules;
return {
modules
};
}
export default connect(mapStateToProps)(Lazy);
Run Code Online (Sandbox Code Playgroud)
//split.js
export const splitComponent = dispatch => {
return System.import('./SplitComponent').then((m) => {
dispatch({
type: 'MODULE_IMPORT',
moduleName: './SplitComponent',
module: m.default
});
});
};
export const splitComponentSync = dispatch => {
// This must be an expression or it will cause the System.import or
// require.ensure to not generate separate bundles
const NAME = './SplitComponent';
const m = require(NAME);
// Reduce into state so that the list of bundles that need to be loaded
// on the client can be, before the application renders. Set the module
// to null as this needs to be imported on the client explicitly before
// it can be used
dispatch({
type: 'MODULE_IMPORT',
moduleName: './SplitComponent',
module: null
});
// Also, register this bundle with the current component state as on
// the server there is only a single render and thus the redux state
// available through mapStateToProps is not up-to-date because it was
// requested before the above dispatch.
return m.default;
};
Run Code Online (Sandbox Code Playgroud)
//reducer.js (Excerpt)
export function modules(
state={}, action) {
switch (action.type) {
case 'MODULE_IMPORT':
const newState = {
...state
};
newState[action.moduleName] = action.module;
return newState;
}
return state;
}
Run Code Online (Sandbox Code Playgroud)
客户端按照从服务器渲染合并 redux 存储的常规过程进行初始化。
一旦发生这种情况,有必要确保在渲染开始之前导入任何所需的包。我们检查 redux 存储modules以了解需要什么。我在这里用一个简单的 if 语句查找它们。对于每个需要的包,它都是异步加载的,它的模块默认存储在 redux 存储中,并返回一个 Promise。一旦所有这些承诺都得到解决,React 将被允许渲染。
//configureStore.js (Excerpt)
let ps;
if (initialState && initialState.hasOwnProperty('modules')) {
ps = Object.keys(initialState.modules).map(m => {
if (m === './SplitComponent') {
return splitComponent(store.dispatch);
}
});
}
// My configureStore.js returns a Promise and React only renders once it has resolved
return Promise.all(ps).then(() => store);
Run Code Online (Sandbox Code Playgroud)
展望未来,每当使用Lazy+SplitComponent时,都不需要加载代码,因为它已经存在于 redux 存储中。
如果初始应用程序不包含Lazy+ ,那么在React 渲染SplitComponent时,将触发一个异步操作来导入并注册它到 redux。与任何 redux 操作一样,这种状态更改将导致组件尝试重新渲染,并且现在已加载并注册,它可以这样做。LazycomponentDidMount./SplitComponentLazySplitComponent
| 归档时间: |
|
| 查看次数: |
1546 次 |
| 最近记录: |