如何在React Hook中正确使用服务类解决TypeError报错
报错根因
抛出TypeError: Cannot read property 'exampleParams' of undefined是典型的类方法this上下文丢失问题:
你在Hook中直接返回checkExamples.isPresent的方法引用,脱离了checkExamples实例上下文。当外部调用这个返回的方法时,严格模式下类方法内部的this会指向undefined,自然无法读取实例上的exampleParams属性。
另外原实现还有一个性能问题:每次Hook所在组件重渲染,都会新建一个ExampleService实例,会产生不必要的开销。
修复方案
以下三种方案任选其一即可:
方案1:返回方法时绑定实例上下文
不需要修改原有服务类代码,在返回方法时通过bind固定this指向,同时用useMemo缓存服务实例避免重复创建:
import { ExampleService } from '../somewhere/example-service'; import { useContext, useMemo } from 'react'; import { ExampleContext } from '../contexts/example-context'; export const useExample = () => { const { currentExamples } = useContext(ExampleContext); const checkExamples = useMemo( () => new ExampleService(currentExamples), [currentExamples] ); return { isPresent: checkExamples.isPresent.bind(checkExamples), getAll: checkExamples.getAll.bind(checkExamples), setExamples: checkExamples.setExamples.bind(checkExamples) }; };
方案2:修改服务类方法为箭头函数
从类定义层面解决this绑定问题,箭头函数会继承定义时的上下文,不会出现this丢失:
export class ExampleService { constructor(private exampleParams: string[]) {} isPresent = (exampleName: string): boolean => { return this.exampleParams.includes(exampleName); }; getAll = (): string[] => { return this.exampleParams; }; setExamples = (exampleParams: string[]): void => { this.exampleParams = exampleParams; }; }
类修改完成后,Hook侧不需要额外bind,配合useMemo缓存实例即可正常使用:
export const useExample = () => { const { currentExamples } = useContext(ExampleContext); const checkExamples = useMemo( () => new ExampleService(currentExamples), [currentExamples] ); return { isPresent: checkExamples.isPresent, getAll: checkExamples.getAll, setExamples: checkExamples.setExamples }; };
方案3:返回包裹的箭头函数
不需要修改类定义,在Hook侧用箭头函数包裹实例方法调用,明确调用主体,也不会出现this丢失问题:
export const useExample = () => { const { currentExamples } = useContext(ExampleContext); const checkExamples = useMemo( () => new ExampleService(currentExamples), [currentExamples] ); return { isPresent: (exampleName: string) => checkExamples.isPresent(exampleName), getAll: () => checkExamples.getAll(), setExamples: (params: string[]) => checkExamples.setExamples(params) }; };
注意:原代码返回值用数组包裹方法,实际使用时解构需要严格对应顺序,改成对象返回的方式使用成本更低,也不容易出现顺序对应错误的问题。
内容的提问来源于stack exchange,提问作者Shlomi Zadok
相关产品推荐
相关产品推荐

