CRA+React17项目ES6导入module.exports自定义钩子报错
问题本质与报错说明
这个问题不是IDE配置导致的,是CRA默认的Babel模块规则限制引发的ES模块(ESM)与CommonJS模块规范混用冲突,Next.js默认对两种模块规范做了兼容处理,所以你此前在Next.js项目中运行正常。
两个报错的具体含义:
- 第一个报错
Attempted import error: 'usePrevious' is not exported from '../customHooks.js':CRA默认将.js后缀的文件识别为ESM规范,ESM使用import/export语法处理导入导出,你在customHooks.js中用的module.exports是CommonJS的导出语法,ESM解析逻辑无法识别CommonJS的具名导出,因此抛出找不到对应导出内容的错误。 - 第二个报错
TypeError: Cannot assign to read only property 'exports' of object '#<Object>':当文件中同时出现ESM的import语法和CommonJS的module.exports语法时,Babel会将该文件判定为ESM规范,而ESM规范下exports是只读属性,不允许赋值,因此你改用require导入时会触发这个错误。
解决办法
方案1:统一改为ESM导出(最推荐,适配CRA默认规则)
无需修改导入逻辑,仅调整customHooks.js的导出写法即可,两种写法可选:
写法1:定义时直接导出
import { useCallback, useEffect, useRef, useState } from "react"; export const usePrevious = (stateValue) => { const ref = useRef(); useEffect(() => { ref.current = stateValue; }) return ref.current } export const getUrlVars = (url) => { let vars = {}; if (url) { let parts = url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; }); } else { console.error('No url specified') } return vars; } export const setCookieManually = async (cname, cvalue, exdays) => { const d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); let expires = "expires="+ d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; } export const removeCookieManually = async (cname) => { document.cookie = `${cname}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;` } export const useSetState = (initialValue) => { let [val, setVal] = useState(initialValue); let cbRef = useRef(null); let first = useRef(true); useEffect(() => { if (first.current) { first.current = false; return; } if (typeof cbRef.current === "function") { console.log("calling cb"); cbRef.current(); } }, [val]); let setValCB = useCallback((newVal, cb) => { console.log("setValCB", newVal); cbRef.current = cb; setVal(newVal); }, []); return [val, setValCB]; }
写法2:定义完成后统一导出
import { useCallback, useEffect, useRef, useState } from "react"; const usePrevious = (stateValue) => { const ref = useRef(); useEffect(() => { ref.current = stateValue; }) return ref.current } const getUrlVars = (url) => { let vars = {}; if (url) { let parts = url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; }); } else { console.error('No url specified') } return vars; } const setCookieManually = async (cname, cvalue, exdays) => { const d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); let expires = "expires="+ d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; } const removeCookieManually = async (cname) => { document.cookie = `${cname}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;` } const useSetState = (initialValue) => { let [val, setVal] = useState(initialValue); let cbRef = useRef(null); let first = useRef(true); useEffect(() => { if (first.current) { first.current = false; return; } if (typeof cbRef.current === "function") { console.log("calling cb"); cbRef.current(); } }, [val]); let setValCB = useCallback((newVal, cb) => { console.log("setValCB", newVal); cbRef.current = cb; setVal(newVal); }, []); return [val, setValCB]; } // 统一导出所有方法 export { usePrevious, getUrlVars, setCookieManually, removeCookieManually, useSetState }
改完后原有import { usePrevious } from '../customHooks'的导入逻辑无需修改,可直接运行。
方案2:强制使用CommonJS规范
如果不想修改原有导出代码,可以将customHooks.js的后缀改为.cjs,CRA会自动识别.cjs文件为CommonJS模块,导入时改用require语法即可:
const { usePrevious } = require('../customHooks.cjs')
方案3:修改Babel配置兼容混用(不推荐)
需要eject CRA配置或者通过craco等工具修改Babel配置,添加sourceType: 'unambiguous'参数让Babel自动判断模块类型,该方案修改成本高,容易引发其他依赖的模块冲突,不推荐使用。
内容的提问来源于stack exchange,提问作者HeavenlyEntity
相关产品推荐
相关产品推荐

