Debugger 一个动态配置代码异步加载引发的状态错误问题, 想起以前在某厂学习的一个解决问题的方法论:
现象背后真实问题是啥?
真实问题背后原因是啥?
对策是要基于原因的, 不是基于现象的.
最后从 webpack 的角度利用静态代码分析的能力来解决问题.
现象
父组件 kitten.tsx
- componentDidMount() {
- console.log('cc kitten didMount');
- setTimeout(
- () => {
- console.log('cc kitten render_content before');
- this.render_content();
- console.log('cc kitten render_content after');
- },
- 0,
- );
- }
- render_content() {
- console.log('cc action_fetch_bcm_by_url before');
- // 一个异步操作, 拉取到在线资源后会调用 workspace 上的方法
- this.props.action_fetch_bcm_by_url();
- console.log('cc action_fetch_bcm_by_url after');
- }
子组件 BKWorkspaceContainer.tsx
- async componentDidMount() {
- console.log('cc BKWorkspaceContainer didMount');
- console.log('cc BKBridge.init before');
- // 初始化 workspace, 初始化完成前为 null
- await BKBridge.init();
- console.log('cc BKBridge.init after');
- }
执行顺序
- cc BKWorkspaceContainer didMount
- cc BKBridge.init before
- cc kitten didMount
- cc kitten render_content before
- cc kitten render_content after
- cc action_fetch_bcm_by_url before
- cc action_fetch_bcm_by_url after
- cc BKBridge.init after
上面顺序可能一个个看会看得眼花, 而且这只是最外层的函数, 里面还很深很杂, 描述一下现象:
方法调用 http://workspace.xxx 报错了, 因为 workspace 还是 null
子组件先 render 没毛病, 但是子组件里面的 init 方法似乎没有执行完就把控制权交回给父组件了
父组件 componentDidMount 中使用 setTimeout 0 企图将 render 任务推到 task 中, 甚至这是个 Ajax 请求操作, 但是在 Ajax 请求完成后还是比 BKBridge.init() 完成得要早, Ajax 后面的操作用到了 workspace, 但是它是 null
最后
cc BKBridge.init after
打印出来了, workspace 初始化完成了
init 阻塞
- // 简化后的调用过程
- if (config().enable_test_mode) {
- await register_test_block();
- }
- const workspace_panel = new WorkspacePanel(block_xml);
- // register_test_block 代码
- async function register_test_block(registry:Registry) {
- return new Promise((resolve) => {
- require.ensure([], function(require){
- const { register_test_blocks } = require('../acceptance_test');
- register_test_blocks(registry);
- registry.load_all_block_definitions_into_bk(BK);
- resolve();
- });
- });
- }
用了 require.ensure 这种方式来异步加载代码, 代码执行到这一段的时候才去拉 JS, 所以会出现比 Ajax 还慢的情况, 它是异步的. 直接阻塞了后面 new WorkspacePanel(block_xml) 的执行.
更长的延时
- componentDidMount() {
- setTimeout(
- () => {
- this.render_content();
- },
- 100,
- );
- }
通过父组件设置 100ms 的延时, 问题就不存在了, 但是如果 require('../acceptance_test') 的时间超过了 100ms, 怎么办呢?
现象背后的问题
代码写成这样子, 最初是为了解决动态加载代码的问题, 如果 config().enable_test_mode 设置成 true 才接入 acceptance_test 相关的代码, 否则就连代码都不要进入到打出来的包中.
问题背后的原因
所以我们的初衷是为了让某段代码可以通过配置决定是否打包进 boundle. 这就好办了, 可以不用把精力放在如何沟通父子组件上面, 不用想类似代码暂停这种复杂化操作, 只要利用 Webpack 打包时候的静态分析即可.
对策: Webpack 静态分析
webpack 读 config 文件
- const runtime_cfg = require('../config')();
- module.exports = {
- // ...
- plugins: [
- new webpack.DefinePlugin({
- '__TEST_MODE__': runtime_cfg.client.enable_test_mode
- }),
- ],
- // ...
- }
为全局定义一个 __TEST_MODE__ 变量, 在代码的任何地方都可以使用, 当然如果是 ts 代码的话需要配置:
- // global.d.ts
- declare var __TEST_MODE__:boolean;
代码中直接写 require
- if (__TEST_MODE__) {
- const { register_test_blocks } = require('../acceptance_test');
- register_test_blocks(registry);
- registry.load_all_block_definitions_into_bk(BK);
- }
结果
- // config.ts
- "enable_test_mode": true
- // config.ts
- "enable_test_mode": false
可以看到, 搜索 register_test_blocks 模块里面的相关代码已经搜索不到了.
博客原文引流
来源: https://juejin.im/post/5c0e48b6f265da611510a139