我正在尝试在我的
react-quill
项目中使用 nextJS
但收到此错误:
1 of 1 unhandled error
Unhandled Runtime Error
TypeError: moduleClass is not a constructor
Call Stack
SnowTheme.addModule
node_modules\quill\dist\quill.js (6130:0)
SnowTheme.addModule
node_modules\quill\dist\quill.js (6774:0)
eval
node_modules\quill\dist\quill.js (6122:0)
Array.forEach
<anonymous>
SnowTheme.init
node_modules\quill\dist\quill.js (6120:0)
new Quill
node_modules\quill\dist\quill.js (1163:0)
ReactQuill.createEditor
node_modules\react-quill\lib\index.js (223:0)
ReactQuill.instantiateEditor
node_modules\react-quill\lib\index.js (187:0)
ReactQuill.componentDidMount
node_modules\react-quill\lib\index.js (152:0)
commitLifeCycles
node_modules\react-dom\cjs\react-dom.development.js (20663:0)
commitLayoutEffects
node_modules\react-dom\cjs\react-dom.development.js (23426:0)
HTMLUnknownElement.callCallback
node_modules\react-dom\cjs\react-dom.development.js (3945:0)
Object.invokeGuardedCallbackDev
node_modules\react-dom\cjs\react-dom.development.js (3994:0)
invokeGuardedCallback
node_modules\react-dom\cjs\react-dom.development.js (4056:0)
commitRootImpl
node_modules\react-dom\cjs\react-dom.development.js (23151:0)
unstable_runWithPriority
node_modules\scheduler\cjs\scheduler.development.js (468:0)
runWithPriority$1
node_modules\react-dom\cjs\react-dom.development.js (11276:0)
commitRoot
node_modules\react-dom\cjs\react-dom.development.js (22990:0)
performSyncWorkOnRoot
node_modules\react-dom\cjs\react-dom.development.js (22329:0)
eval
node_modules\react-dom\cjs\react-dom.development.js (11327:0)
unstable_runWithPriority
node_modules\scheduler\cjs\scheduler.development.js (468:0)
runWithPriority$1
node_modules\react-dom\cjs\react-dom.development.js (11276:0)
flushSyncCallbackQueueImpl
node_modules\react-dom\cjs\react-dom.development.js (11322:0)
flushSyncCallbackQueue
node_modules\react-dom\cjs\react-dom.development.js (11309:0)
scheduleUpdateOnFiber
node_modules\react-dom\cjs\react-dom.development.js (21893:0)
dispatchAction
node_modules\react-dom\cjs\react-dom.development.js (16139:0)
checkForUpdates
node_modules\use-subscription\cjs\use-subscription.development.js (85:0)
eval
node_modules\next\dist\shared\lib\loadable.js (183:44)
Set.forEach
<anonymous>
LoadableSubscription._update
node_modules\next\dist\shared\lib\loadable.js (183:24)
eval
node_modules\next\dist\shared\lib\loadable.js (164:17)
这里的代码显示了如何导入它以及如何渲染它:
import React, { useState, useEffect, useCallback } from 'react';
import 'react-quill/dist/quill.snow.css';
import { Box, TextField, Button, Typography, Input } from '@mui/material';
import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { getFirestore, collection, addDoc } from 'firebase/firestore';
import dynamic from 'next/dynamic';
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false, loading: () => <p>Loading...</p> });
const Quill = dynamic(() => import('react-quill'), { ssr: false, loading: () => <p>Loading...</p> });
const ImageUploader = dynamic(() => import('quill-image-uploader'), { ssr: false, loading: () => <p>Loading...</p> });
const CreatePost = () => {
const [titles, setTitles] = useState({
title_ar: '',
title_en: '',
title_fr: '',
title_tr: '',
});
const [body, setBody] = useState('');
const [mainImage, setMainImage] = useState(null);
const [uploading, setUploading] = useState(false);
const storage = getStorage();
const db = getFirestore();
useEffect(() => {
if (Quill && Quill.register) {
Quill.register('modules/imageUploader', ImageUploader);
}
}, []);
const handleTitleChange = useCallback((e) => {
const { name, value } = e.target;
setTitles((prevTitles) => ({ ...prevTitles, [name]: value }));
}, []);
const handleEditorChange = useCallback((content) => {
setBody(content);
}, []);
const handleMainImageChange = (e) => {
setMainImage(e.target.files[0]);
};
const uploadImage = async (image) => {
if (!image) {
throw new Error('No image provided');
}
const storageRef = ref(storage, `images/${image.name}`);
await uploadBytes(storageRef, image);
return getDownloadURL(storageRef);
};
const uploadInBodyImage = async (image) => {
if (!image) {
return Promise.reject(new Error('No image provided'));
}
const storageRef = ref(storage, `images/${image.name}`);
return uploadBytes(storageRef, image).then(() => {
return getDownloadURL(storageRef).then((url) => {
return url;
});
}).catch((error) => {
console.error('Error uploading image or getting URL:', error);
throw error;
});
};
const modules = {
toolbar: [
[{ 'header': '1'}, {'header': '2'}, { 'font': [] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['bold', 'italic', 'underline'],
['link', 'image'],
['clean']
],
imageUploader: {
upload: async (file) => {
try {
const url = await uploadInBodyImage(file);
return url;
} catch (error) {
console.error('Image upload failed:', error);
throw error;
}
},
},
};
const handleSubmit = async (e) => {
e.preventDefault();
setUploading(true);
try {
let mainImageUrl = '';
if (mainImage) {
mainImageUrl = await uploadImage(mainImage);
}
await addDoc(collection(db, 'Article'), {
...titles,
body,
mainImageUrl,
createdAt: new Date(),
});
alert('Article created successfully!');
setTitles({
title_ar: '',
title_en: '',
title_fr: '',
title_tr: '',
});
setBody('');
setMainImage(null);
} catch (error) {
console.error('Error creating article:', error);
alert('Failed to create article.');
} finally {
setUploading(false);
}
};
return (
<Box sx={{ p: 3 }}>
<Typography variant="h5" gutterBottom>Create a New Article</Typography>
<form onSubmit={handleSubmit}>
<Box mb={2}>
<TextField
fullWidth
label="Title (Arabic)"
name="title_ar"
value={titles.title_ar}
onChange={handleTitleChange}
variant="outlined"
/>
</Box>
<Box mb={2}>
<TextField
fullWidth
label="Title (English)"
name="title_en"
value={titles.title_en}
onChange={handleTitleChange}
variant="outlined"
/>
</Box>
<Box mb={2}>
<TextField
fullWidth
label="Title (French)"
name="title_fr"
value={titles.title_fr}
onChange={handleTitleChange}
variant="outlined"
/>
</Box>
<Box mb={2}>
<TextField
fullWidth
label="Title (Turkish)"
name="title_tr"
value={titles.title_tr}
onChange={handleTitleChange}
variant="outlined"
/>
</Box>
<Box mb={2}>
<Typography variant="h6" gutterBottom>Body</Typography>
<ReactQuill
value={body}
onChange={handleEditorChange}
modules={modules}
style={{ height: '400px' }}
/>
</Box>
<Box mb={2}>
<Input
type="file"
accept="image/*"
onChange={handleMainImageChange}
fullWidth
/>
</Box>
<Button type="submit" variant="contained" color="primary" disabled={uploading}>
{uploading ? 'Uploading...' : 'Create Article'}
</Button>
</form>
</Box>
);
};
export default CreatePost;
您在 Next.js 项目中遇到的带有
react-quill
的错误似乎与 Quill 模块的注册方式有关。如果模块系统未按预期工作,则可能会发生这种情况,这可能是由于 Next.js 处理模块导入的方式与传统 React 项目不同所致。
您可以尝试执行以下几个步骤来解决该问题:
react-quill
确保正确导入
react-quill
,如下所示:
import dynamic from 'next/dynamic';
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
import 'react-quill/dist/quill.snow.css';
ssr: false
选项禁用react-quill
的服务器端渲染,这是必要的,因为Quill依赖于服务器上不可用的window
对象。
确保您使用兼容版本的 Quill。有时,如果
react-quill
和 quill
之间不匹配,就会出现问题。确保您的 quill
依赖项已安装并且是最新的:
npm install quill@latest react-quill@latest
如果您手动注册 Quill 模块(例如自定义模块),请确保操作正确。有时,如果模块系统与 Next.js 处理代码的方式不兼容,可能会出现错误。
以下是如何在 Next.js 项目中设置
react-quill
的基本示例:
import dynamic from 'next/dynamic';
import 'react-quill/dist/quill.snow.css';
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
const MyEditor = () => {
return <ReactQuill theme="snow" />;
};
export default MyEditor;
如果您自定义了 Quill 主题或模块,请尝试恢复到默认设置以隔离问题。这可以帮助确定问题是否在于特定的自定义。
如果上述步骤不起作用,请尝试清除 Next.js 缓存并重建项目:
rm -rf .next
npm run build
添加一些
console.log
语句来检查到底在哪里触发了错误。它可能会为您提供有关有问题的模块的更多背景信息。
如果执行这些步骤后问题仍然存在,请随时分享更多详细信息,我们可以更深入地研究问题!