在这篇文章中,我们将使用React、Material UI和Firebase构建一个Google Docs克隆。
最终的应用程序将看起来像这样:
如果我们点击任何文档,它就会被打开,我们可以根据需要对其进行编辑:
而最令人惊奇的是,我们可以实时地编辑文档。这意味着,如果两个人在同一个文件上工作,他们的进展将反映在两个实例上。
但在我们开始之前,请确保你的系统中已经安装了Node。如果没有,请到nodejs.org/en/download…,下载并安装它。
基本项目设置
让我们首先使用下面的命令创建一个React应用程序。
npx create-react-app google-docs-clone
这将把所有的软件包和依赖项安装到本地文件夹中。
然后,简单地导航到项目文件夹,运行npm start来运行该应用。
我们将在这里看到所有这些我们需要删除的代码。我们将从一个空白的画布开始。
接下来创建一个名为组件的文件夹。在该文件夹内,让我们创建一个名为docs.js的文件。
让这个组件成为一个功能组件,像这样。
import React from 'react'
export default function Docs() {
return (
<div>
<h1>docs</h1>
</div>
)
}
现在,把这个文件导入到主App.js文件中。
import './App.css';
import Docs from './components/docs';
function App() {
return (
<Docs />
);
}
export default App;
然后我们会在屏幕上看到这样的输出。
Google docs的克隆,在左上角显示输出 "docs"。
现在,让我们让标题出现在中间。因此,在docs.js中,给maindiv一个 docs-main的className。
import React from 'react'
export default function Docs() {
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
</div>
)
}
并在App.css文件中,添加以下样式。
.docs-main{
text-align: center;
}
现在我们的应用程序看起来像这样。
标题在中间的Google docs克隆
现在,我们需要一个按钮来添加我们的文档。所以,让我们用这个代码来创建它。
import React from 'react'
export default function Docs() {
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
<button className='add-docs'>
Add a Document
</button>
</div>
)
}
而CSS看起来是这样的。
.add-docs{
height: 40px;
width: 200px;
background-color: #ffc107;
border: none;
cursor: pointer;
}
让我们从Google Fonts导入一些字体。把这个放在CSS文件的顶部。
@import url('https://fonts.googleapis.com/css2?family=Poppins&family=Roboto&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Poppins&family=Roboto&display=swap');
.docs-main{
text-align: center;
font-family: 'Roboto', sans-serif;
}
.add-docs{
height: 40px;
width: 200px;
background-color: #ffc107;
border: none;
cursor: pointer;
font-family: 'Poppins', sans-serif;
}
要添加字体,只需在各自的classNames中这样做。
如何安装Material UI
要安装Material UI,只需输入下面的命令。如果你想阅读文档,请到mui.com/。
npm install @mui/material @emotion/react @emotion/styled
现在,让我们再为模态创建一个组件。我们将使用这个模态来向Firebase数据库添加文件。
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function Modal() {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Text in a modal
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
</Box>
</Modal>
</div>
);
}
这是一个来自Material UI的简单模态组件。现在我们要把这个组件导入我们的Docs.js组件中。
而且我们需要把一些东西从Modal.js移到Docs.js中。
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
如果我们点击添加文档按钮,模态将使用这些函数打开。
import React, { useState } from 'react';
import Modal from './Modal';
export default function Docs() {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
<button
className='add-docs'
onClick={handleOpen}
>
Add a Document
</button>
<Modal
open={open}
setOpen={setOpen}
/>
</div>
)
}
所以,将这些函数和状态作为道具传入模态组件,并接收它们。
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function ModalComponent({
open,
setOpen,
}) {
const handleClose = () => setOpen(false);
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Text in a modal
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
</Box>
</Modal>
</div>
);
}
现在,这就是我们的页面与模态的外观。
显示模型的Google docs克隆页面
让我们在模态中添加一个输入,用于输入文件名。
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<input
placeholder='Add the Title'
className='add-input'
/>
</Box>
</Modal>
让我们用下面的方法给它一些样式。
.add-input{
width: 95%;
height: 40px;
outline: none;
border: 1px solid #676767;
border-radius: 0px;
padding: 10px;
font-family: 'Poppins', sans-serif;
}
现在,这就是我们的模版的样子。
添加了样式的Modal
让我们也添加一个按钮。我们可以复制Add a Document Button。
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 500,
height: 150,
bgcolor: 'background.paper',
boxShadow: 24,
p: 5,
};
export default function ModalComponent({
open,
setOpen,
}) {
const handleClose = () => setOpen(false);
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<input
placeholder='Add the Title'
className='add-input'
/>
<div className='button-container'>
<button
className='add-docs'
>
Add
</button>
</div>
</Box>
</Modal>
</div>
);
}
而CSS看起来像这样。
.button-container{
text-align: center;
margin: 30px;
}
这就是它现在的样子。
添加了样式和按钮的模态
如何在我们的应用程序中添加Firebase
现在,让我们为数据库安装Firebase。使用下面的命令简单地安装Firebase。
npm install firebase
前往firebase.google.com/,点击右上方的Go to console。
然后,点击添加项目。
创建项目后,点击代码按钮,在Firebase中创建一个Web应用。给它起个名字,我们就可以开始了。
现在,我们将添加所有这些我们必须存储在React应用中的配置数据。所以,创建一个名为firebaseConfig.js的文件,并添加它们。
我们将需要数据库,所以让我们把它激活。同时,像这样导出const应用程序和数据库。
import { initializeApp } from "firebase/app";
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
//Your Firebase Data
};
export const app = initializeApp(firebaseConfig);
export const database = getFirestore(app)
将应用程序和数据库导入App.js文件。并将数据库作为道具传递给Docs组件。我们以后会用它来向Firebase Firestore添加数据。
import './App.css';
import Docs from './components/docs';
import { app, database } from './firebaseConfig';
function App() {
return (
<Docs database={database}/>
);
}
export default App;
而在Docs组件中。同时,让我们从props中接收数据库的导出。
import React, { useState } from 'react';
import Modal from './Modal';
export default function Docs({
database
}) {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
<button
className='add-docs'
onClick={handleOpen}
>
Add a Document
</button>
<Modal
open={open}
setOpen={setOpen}
/>
</div>
)
}
现在,让我们来配置我们的Firestore数据库。
从左边的侧边栏转到Firestore数据库,然后点击创建数据库。
我们将在生产模式下启动我们的数据库。所以,点击下一步,然后启用。
我们必须使安全规则公开,只是现在。所以,点击顶部标签的规则,编辑以下规则。这意味着任何人都可以写入数据或读取它们,即使没有认证。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
如何向Firestore数据库添加文档数据
现在,让我们实际添加我们的数据。但在这之前,我们需要从输入字段中获取数据。
所以在Docs组件中,创建一个状态来保存这些数据。
const [title, setTitle] = useState('')
把标题和setTitle传给模态组件。
<Modal
open={open}
setOpen={setOpen}
title={title}
setTitle={setTitle}
/>
把它们都作为道具接收,并在输入框中设置它们。
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 500,
height: 150,
bgcolor: 'background.paper',
boxShadow: 24,
p: 5,
};
export default function ModalComponent({
open,
setOpen,
title,
setTitle
}) {
const handleClose = () => setOpen(false);
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<input
placeholder='Add the Title'
className='add-input'
onChange={(event) => setTitle(event.target.value)}
value={title}
/>
<div className='button-container'>
<button
className='add-docs'
>
Add
</button>
</div>
</Box>
</Modal>
</div>
);
}
现在,如果我们在输入框中输入一些东西,它将被保存在标题状态中。
接下来,我们需要一个函数来触发添加数据的函数,所以让我们来创建它。
在Docs.js中,创建一个函数并把它传递给模态组件。
const addData = () => {
}
在模态组件中接收它,并像这样简单地将它绑定到添加按钮上。
<div className='button-container'>
<button
className='add-docs'
onClick={addData}
>
Add
</button>
</div>
现在,addData函数将在我们点击添加按钮时运行。
现在,为了将数据从React动态地发送到Firebase,让我们从Firebase Firestore导入一些东西。
import { addDoc, collection } from 'firebase/firestore';
在这里,我们将使用collection ,在Firebase中创建一个数据集合,addDoc将向该集合添加数据。
让我们首先创建一个集合引用。它将接受我们从firebaseConfig.js中得到的数据库和我们想使用的集合的名称。
const collectionRef = collection(database, 'docsData')
现在,在addData函数中,让我们使用addDoc。这个addDoc 函数将接受集合的引用,以及数据本身。
const addData = () => {
addDoc(collectionRef, {
title: title
})
.then(() => {
alert('Data Added')
})
.catch(() => {
alert('Cannot add data')
})
}
现在,在文本输入中添加一些东西,然后点击添加。它将被添加到Firebase Firestore中,并有一个提示说数据已经被添加。但是如果失败了,我们会得到 "无法添加数据"。
如果我们刷新数据库,我们会看到这个新条目。
这就是我们添加数据的方法。让我们也在添加数据后关闭模态。
创建一个函数handleClose,并在addData 函数的then 块之后直接调用这个函数。
const addData = () => {
addDoc(collectionRef, {
title: title
})
.then(() => {
alert('Data Added');
handleClose()
})
.catch(() => {
alert('Cannot add data')
})
}
如何从Firebase读取数据
现在,让我们来读取我们添加到Firebase的数据。我们将需要onSnapshot 函数来实现。onSnapshot 函数实时地获取数据。
首先,像这样从Firebase导入它。
import { addDoc, collection, onSnapshot } from 'firebase/firestore';
然后,创建一个函数getData ,它将在我们的页面加载时被触发。因此,我们将把这个onSnapshot 放入ReactuseEffect Hook中。
const getData = () => {
onSnapshot(collectionRef, (data) => {
console.log(data.docs.map((doc) => {
return {...doc.data(), id: doc.id}
}))
})
}
然后,在useEffect Hook里面调用这个函数。
useEffect(() => {
getData()
}, [])
但正如你所看到的,我们得到了两次数据。这是因为我们使用的是React 18版本,其中包括并发渲染。这就是为什么useEffect钩子会运行两次。
为了解决这个问题,我们需要创建一个useRef 引用。
const isMounted = useRef()
然后在useEffect Hook中,我们必须检查isMounted.current是否为真。所以,如果它是真的,我们将不返回任何东西。然后,我们将设置isMounted.current为真,然后我们将调用我们的getData函数。
useEffect(() => {
if(isMounted.current){
return
}
isMounted.current = true;
getData()
}, [])
如果我们现在刷新页面,我们将只得到一次数据。
现在,我们必须将这些数据纳入一个数组状态。所以,让我们来做这件事。
创建一个docsData的状态。
const [docsData, setDocsData] = useState([]);
并使用setDocsData在这个状态中设置传入的数据。
const getData = () => {
onSnapshot(collectionRef, (data) => {
setDocsData(data.docs.map((doc) => {
return {...doc.data(), id: doc.id}
}))
})
}
现在,让我们映射我们的数组,让数据显示在用户界面上。
<div>
{docsData.map((doc) => {
return (
<div>
<p>{doc.title}</p>
</div>
)
})}
</div>
这将在我们的React应用程序中显示所有的数据。
我们将在我们的页面上看到两个文档。但让我们让它们出现在一个网格中。给这些div容器取一个grid-main和grid-child的类名。
<div className='grid-main'>
{docsData.map((doc) => {
return (
<div className='grid-child'>
<p>{doc.title}</p>
</div>
)
})}
</div>
并在CSS中,添加以下类。
.grid-main{
display: grid;
grid-template-columns: auto auto auto auto;
color: whitesmoke;
margin-top: 20px;
gap: 20px;
justify-content: center;
}
.grid-child{
padding: 20px;
background-color: rgb(98, 98, 98);
width: 300px;
cursor: pointer;
}
现在,我们的应用程序将看起来像这样。
如何获得ID并重定向到编辑文档页面
现在,上面这些项目中的每一个都有一个ID。我们将使用这些ID重定向到另一个页面,在那里我们可以编辑这些项目并编写我们的主要内容。
为此,我们需要两个包。一个是React-Router,用于重定向,另一个是React-Quill,用于我们的编辑器。像这样安装它们。
npm i react-quill react-router-dom@6
现在,让我们来配置路由到另一个页面。但我们首先需要另一个页面。所以,让我们来创建它。
创建一个叫做EditDocs 的组件**。** 让它成为一个功能性组件。
import React from 'react'
export default function EditDocs() {
return (
<div>EditDocs</div>
)
}
为了配置路由,来到index.js,即应用程序的入口点。将包裹在BrowserRouter里面。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
现在,我们可以在任何地方使用路由,因为我们在基本层面上声明了BrowserRouter。
现在,来看看App.js文件。从React-Router导入Routes和Route 。我们还在editDocs路径中追加ID,这样我们就可以在地址栏中看到ID。
import { Routes, Route } from "react-router-dom";
import './App.css';
import Docs from './components/docs';
import EditDocs from './components/EditDocs';
import { Routes, Route } from "react-router-dom";
import { app, database } from './firebaseConfig';
function App() {
return (
<Routes>
<Route path="/" element={<Docs database={database} />} />
<Route path="/editDocs/:id" element={<EditDocs database={database}/>} />
</Routes>
);
}
export default App;
并添加以下路由。如果我们去 '/editDocs/:id',我们将看到我们的editDocs页面。
现在,我们需要从文档中获取特定的ID,并将其发送到editDocs 页面。
创建一个函数getID,并将该函数分配给文档。
const getID = () => {
}
<div className='grid-main'>
{docsData.map((doc) => {
return (
<div className='grid-child' onClick={() => getID(doc.id)}>
<p>{doc.title}</p>
</div>
)
})}
</div>
现在,如果我们点击文档,如果我们在控制台中记录它,我们将得到它的ID。
const getID = (id) => {
console.log(id)
}
现在,让我们使用useNavigate将这个ID发送到editDocs 页面。
首先,从 react-router 中导入 useNavigate 。
import { useNavigate } from 'react-router-dom';
然后,像这样创建一个useNavigate的实例。
let navigate = useNavigate();
然后,要传递ID,只需这样做。我们将把自己和 ID 一起发送到 editDocs 页面。
const getID = (id) => {
navigate(`/editDocs/${id}`)
}
现在,让我们在另一端接收我们的ID。在editDocs 组件中,我们需要使用 react-router 的Params 。
所以,导入它并创建一个实例。
import { useParams } from 'react-router-dom';
let params = useParams();
同样,如果我们控制台,我们将看到ID。
import { useParams } from 'react-router-dom';
let params = useParams();
console.log(params)
我们可以看到,我们在地址栏以及控制台中得到了ID。
现在,让我们把React Quill添加到我们的editDocs 页面。
import React from 'react';
import { useParams } from 'react-router-dom';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
export default function EditDocs() {
let params = useParams();
return (
<div>
<h1>EditDocs</h1>
<ReactQuill />
</div>
)
}
我们必须导入React-Quill和CSS。
但是我们可以看到我们在这里有两个工具条。要解决这个问题,只要从index.js中删除React.StrictMode。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
我们就会没事了。
现在,我们需要为这个React Quill数据建立一个状态。所以,让我们来创建它。同时,我们将创建一个函数,在我们输入时触发。
const [docsDesc, setDocsDesc] = useState('');
const getQuillData = () => {
}
现在,让我们把这个函数和状态绑定到React Quill。
<ReactQuill
value={docsDesc}
onChange={getQuillData}
/>
在getQuillData 函数中,让我们使用setDocsDesc 函数,将值绑定到docsDesc 状态中。
const getQuillData = (value) => {
setDocsDesc(value)
}
我们就在这里完成了。你可以用控制台来检查这个docsDesc状态。
现在我们有了ID,以及我们可以用来更新文档的数据。所以,让我们来做这件事。
如何更新文档
我们需要两样东西,updateDoc 和集合 函数。我们将使用一个Debounce函数来调用updateDoc函数。这意味着,当我们打完字,5或10秒后,我们的updateDoc函数将运行。
所以,让我们创建一个函数。
const updateDocsData = () => {
}
我们还需要指定集合。为此,我们需要App.js中的数据库 **。**所以,让我们使用props来获得它。
<Route path="/editDocs/:id" element={<EditDocs database={database}/>} />
现在,让我们创建一个集合引用。
const collectionRef = collection(database, 'docsData')
现在,为了去伪存真,我们需要在useEffect钩中的updateDocsData 。
useEffect(() => {
const updateDocsData = () => {
}
}, [])
现在,让我们添加一个有间隔的setTimeout函数。这意味着该函数将在指定的时间间隔后运行。把时间间隔定为1000毫秒,或1秒。
useEffect(() => {
const updateDocsData = setTimeout(() => {
}, 1000)
return () => clearTimeout(updateDocsData)
}, [])
现在,在setTimeOut里面,让我们添加updateDoc函数。所以在文档变量里面,我们要从参数中传递collectionRef 和ID 。然后,updateDoc 将变量document作为第一个参数。
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
})
}, 1000)
让我们也导入doc 函数。它指定使用ID作为主键来更新哪个文档。
import {
updateDoc,
collection,
doc
} from 'firebase/firestore';
现在让我们在 updateDoc 函数的第二个参数中传递数据。
useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [])
在依赖数组中,添加docsDesc 的状态**。** 所以在我们输入一些东西后**,** updateDoc函数将在1秒后运行。
useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
.then(() => {
alert('Saved')
})
.catch(() => {
alert('Cannot Save')
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [docsDesc])
所以,在编辑器中输入一些东西,它将被保存在数据库里面。
而这里的数据。
如果我们进一步添加一些东西,我们将附加上之前的数据。
如何将数据从数据库中取回到编辑器中
现在,如果我们回去点击任何一个文件,数据就会变成空的,或者被删除。所以,我们必须从数据库中获取数据并将其设置到编辑器中。
我们将使用onSnapshot 函数来做到这一点。
import {
updateDoc,
collection,
doc,
onSnapshot
} from 'firebase/firestore';
const getData = () => {
}
useEffect(() => {
if(isMounted.current){
return
}
isMounted.current = true;
getData()
}, [])
所以,这就像我们在Docs 组件中所做的那样。我们需要使用ID参数指定要获取的数据。然后我们把这个文档传递给onSnapshot函数来获取我们需要的数据。
const getData = () => {
const document = doc(collectionRef, params.id)
onSnapshot(document, (docs) => {
console.log(docs.data().docsDesc)
})
}
让我们使用setDocsDesc将这个docs.data().docsDesc 设置在docsDesc状态。 所以,如果文档加载,它将被设置在那里。
添加一些数据,然后再回去。如果你回到同一个组件,文档描述就会出现在那里。
现在在我们看到所有数据的主页上,我们也需要添加描述,如果它存在的话。
<div dangerouslySetInnerHTML={{__html: doc.docsDesc}} />
我们使用危险的SetInnerHTML ,因为在React Quill中,数据是以标签的形式添加的。这使得渲染格式化更容易。
看,我已经添加了一些格式,如粗体和斜体 文本。
现在,我们需要做一些轻微的修改。在App.js文件中(我们正在添加文档标题),让我们也添加描述,它最初将是空的。
const addData = () => {
addDoc(collectionRef, {
title: title,
docsDesc: ''
})
.then(() => {
alert('Data Added');
handleClose()
})
.catch(() => {
alert('Cannot add data')
})
}
因此,如果我们创建一个文档,我们将在Firestore文档中拥有docsDesc。这将防止我们的应用程序在进入EditDocs页面时崩溃。
现在,在EditDocs页面,让我们添加文档的标题,以便它显示在顶部。创建一个叫做 documentTitle 的状态并设置它。
const [documentTitle, setDocumentTitle] = useState('')
const getData = () => {
const document = doc(collectionRef, params.id)
onSnapshot(document, (docs) => {
setDocumentTitle(docs.data().title)
setDocsDesc(docs.data().docsDesc);
})
}
然后在顶部显示这个状态。
<h1>{documentTitle}</h1>
下面是到目前为止EditDocs 页面的全部代码。
import React, { useEffect, useState, useRef } from 'react';
import { useParams } from 'react-router-dom';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import {
updateDoc,
collection,
doc,
onSnapshot
} from 'firebase/firestore';
export default function EditDocs({
database
}) {
const isMounted = useRef()
const collectionRef = collection(database, 'docsData')
let params = useParams();
const [documentTitle, setDocumentTitle] = useState('')
const [docsDesc, setDocsDesc] = useState('');
const getQuillData = (value) => {
setDocsDesc(value)
}
useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
.then(() => {
alert('Saved')
})
.catch(() => {
alert('Cannot Save')
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [docsDesc])
const getData = () => {
const document = doc(collectionRef, params.id)
onSnapshot(document, (docs) => {
setDocumentTitle(docs.data().title)
setDocsDesc(docs.data().docsDesc);
})
}
useEffect(() => {
if (isMounted.current) {
return
}
isMounted.current = true;
getData()
}, [])
return (
<div>
<h1>{documentTitle}</h1>
<ReactQuill
value={docsDesc}
onChange={getQuillData}
/>
</div>
)
}
如何添加一些样式
现在让我们在这个EditDocs页面中添加一些样式。
<div className='editDocs-main'>
<h1>{documentTitle}</h1>
<div className='editDocs-inner'>
<ReactQuill
className='react-quill'
value={docsDesc}
onChange={getQuillData}
/>
</div>
</div>
在CSS中,添加以下样式。
.editDocs-main {
font-family: 'Poppins', sans-serif;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.editDocs-inner {
width: 800px;
box-shadow: 0px -2px 5px 2px rgba(181, 181, 181, 0.75);
-webkit-box-shadow: 0px -2px 5px 2px rgba(181, 181, 181, 0.75);
-moz-box-shadow: 0px -2px 5px 2px rgba(181, 181, 181, 0.75);
padding: 20px;
height: 750px;
}
.ql-container.ql-snow {
border: none !important;
}
我们正在添加一个盒状阴影,我们正在删除React Quill的边框,并且我们正在将所有东西居中。
这就是我们的编辑文档页面现在的样子。
现在是我们的最后一件事:让我们用吐司信息取代我们的警报。我们还需要一个叫React Toastify的包。所以,让我们安装它。
npm i react-toastify
然后我们需要导入这两个东西。
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
然后,****组件。
现在,对于吐司信息,只需这样做。
useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
.then(() => {
toast.success('Document Saved', {
autoClose: 2000
})
})
.catch(() => {
toast.error('Cannot Save Document', {
autoClose: 2000
})
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [docsDesc])
我们有toast.success用于成功警报,toast.error用于错误警报。
结论
就这样,你已经建立了一个Google Docs的克隆。你可以自由地进行实验,使其变得更好。