StencilJS开发拖拽上传组件时内部函数调用异常
StencilJS拖拽上传组件方法不工作问题排查
问题描述
我正在跟随教程开发拖拽式文件上传组件,出于练习与兴趣选用StencilJS框架,开发过程中遇到运行错误。
以下为部分核心代码,如有需要我可以补充完整项目代码:
@Component({ tag: 'dynamic-uploader', styleUrl: 'dynamic-uploader.css', shadow: true, }) export class DynamicUploader { @Element() public dynamicUploader: HTMLElement; @State() dropzoneActive: boolean = false; @State() stagedFiles: Array<StagedFile> = []; componentDidLoad() { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { this.dynamicUploader.addEventListener(eventName, this.handleDefaultPrevention, false); document.body.addEventListener(eventName, this.handleDefaultPrevention, false); }); ['dragenter', 'dragover'].forEach(eventName => { this.dynamicUploader.addEventListener(eventName, () => (this.dropzoneActive = true), false); }); ['dragleave', 'drop'].forEach(eventName => { this.dynamicUploader.addEventListener(eventName, () => (this.dropzoneActive = false), false); }); this.dynamicUploader.addEventListener('drop', this.handleDroppedFile, false); } handleDefaultPrevention(event: Event) { event.preventDefault(); event.stopPropagation(); } handleFiles(file) { // files = [...files]; // files.forEach(this.handleFilePreview); } // handleFiles = files => { // files = [...files]; // files.forEach(this.handleFilePreview); // }; handleDroppedFile(event: InputEvent) { let dataTransfer = event.dataTransfer; let files = dataTransfer.files; console.log(this.dynamicUploader); Array.from(files).forEach(i => this.handleFilePreview(i)); } handleFilePreview(file: File) { this.stagedFiles = [...this.stagedFiles, <staged-file file={file}></staged-file>]; } render() { return ( <Host> {`The dropzone is ${this.dropzoneActive ? 'active' : 'inactive'}`} <form id="dynamic-uploader" class={{ 'is-active': this.dropzoneActive }}> <p>Upload Evidence</p> <input type="file" id="file-input" multiple accept="image/*" /> <label class="button" htmlFor="file-input"> Select some files </label> </form> <div class="stage-files">{this.stagedFiles}</div> </Host> ); } }
调试时发现handleFiles方法无法正常工作,最初判断是作用域问题,尝试将其改写为箭头函数形式,但调试时提示该函数不存在;改回标准类方法写法后问题仍然存在,未得到解决。
问题根因与修复方案
代码里有三个直接导致异常的问题,按影响优先级排序:
- 事件监听器
this绑定丢失
直接把类方法引用传给addEventListener时,事件触发时方法内部的this会指向绑定事件的DOM元素,而非Stencil组件实例,所以在handleDroppedFile、handleDefaultPrevention里访问this.handleFilePreview、this.stagedFiles这类组件属性/方法时,会直接报找不到对应属性的错误。
修复方式:所有类方法作为事件回调传入时,外层包一层箭头函数固定this指向,示例修改:
之前改箭头函数类属性提示不存在,是因为Stencil的类字段编译需要对应配置支持,用上述箭头函数包裹的写法不需要改编译配置,兼容性更好。// 原写法 this.dynamicUploader.addEventListener(eventName, this.handleDefaultPrevention, false); // 修改后 this.dynamicUploader.addEventListener(eventName, (e) => this.handleDefaultPrevention(e), false); // 原drop事件写法 this.dynamicUploader.addEventListener('drop', this.handleDroppedFile, false); // 修改后 this.dynamicUploader.addEventListener('drop', (e) => this.handleDroppedFile(e), false); handleFiles方法从未被调用
现有代码逻辑里,文件拖入后直接走handleDroppedFile->handleFilePreview链路,完全没有调用handleFiles的逻辑,这个方法本身就没被执行,自然表现为“不工作”,如果要走统一的文件处理逻辑,在handleDroppedFile和后续文件选择input的change事件里调用this.handleFiles(files)即可。- State存储内容与类型定义不匹配
定义stagedFiles类型为Array<StagedFile>,实际存储的是<staged-file>JSX元素,类型不匹配会导致TS编译告警,且直接把JSX存在state里会造成视图和状态强耦合,不利于后续更新、删除文件的逻辑开发。
更合理的写法是state里只存原始File对象,渲染时动态生成对应组件:// 修改state定义 @State() stagedFiles: File[] = []; // 修改文件添加逻辑 handleFilePreview(file: File) { this.stagedFiles = [...this.stagedFiles, file]; } // 修改render里的文件列表渲染 <div class="stage-files"> {this.stagedFiles.map(file => <staged-file file={file}></staged-file>)} </div>
额外注意:在componentDidLoad里给组件和document.body绑定的拖拽事件,需要在disconnectedCallback生命周期里对应移除,避免组件卸载后造成内存泄漏。
内容的提问来源于stack exchange,提问作者Rue
相关产品推荐
相关产品推荐

