透過開發2023年度的【React x Firebase】Todo應用程式,來學習

首先

这次作为学习React的第一步,我尝试创建了一个”Todo应用”。我使用了两年前在YouTube上的视频中无法使用的代码等。

 

我們的目標是實現「登入/登出功能」以及相關的「用戶狀態管理」。通過使用Firebase Firestore,我們可以實現保存「用戶信息」和「待辦事項信息」的功能。

由于尚未编写Firestore规则,此网络应用程序尚不完善,但最终产品如下所示。

这次的成品
https://react-todolist-a2dda.web.app/

TodoApp (2).PNG

实际操作指南的资料

我相信总会有人想要学习,所以我将实践操作的步骤按照每个阶段进行了说明。

如果只是制作的话,可以通过复制粘贴并修改数值来完成,但我个人认为了解制作过程在编程学习中也是重要的,所以我会把它写上。

由於這篇文章的解說不足,我會補充一些內容。

↑↑↑我将解释CodeLab形式的解释方法。

React的参考资料

官方网站

在学习React的过程中,我也经常访问React官方网站。我认为,基本上在这里查找所需的信息是很好的。

 

个人观点是,值得注意的网站

当你想直观地理解React是什么时,以下资料是很好的参考。
在开始学习之前,我建议先轻松地浏览一下”#01″和”#02″,或者尝试一些实践。

这份资料是在「2018年」撰写的。尽管根本部分没有太大变化,不建议完全按原样继续进行。

 

必须的控制台操作和代码完成品

需要作为前提条件的东西

查看节点版本

请在Windows的cmd命令提示符中执行命令并进行确认,
node -v

我认为在VScode的cmd内执行以下命令是更好的选项。

npm create-react-app . = 使用npm创建一个React应用程序。
npm create-react-app ファイル名 = 使用npm创建一个名为ファイル名的React应用程序。

学习React时,请先执行这个命令。

 

在当前文件夹下创建↓↓↓

npm creat-react-app .

如果你在使用cd命令等移动到一个文件夹内部,想要新建一个文件夹的话↓↓↓

npm creat-react-app <ファイル名>

使用npm安装react-router-dom

这是网站内页面跳转所必需的东西。

安装bootstrap的npm包

这是网站装饰所需的物品。

数据库设计

React-TodoApp-Firestore.jpg

对于用户1来说,他有多个待办事项的信息。

文件结构

在这里这大部分需要创建的文件都有了,实际上使用create-react-app命令会生成一些文件和文件夹,但不需要的可以删除。

C:react-todolist/src
│  App.css
│  App.js
│  index.css
│  index.js
│  reportWebVitals.js
│  setupTests.js
│
├─Components
│      Header.js
│      Home.js
│      IndividualTodo.js
│      Login.js
│      Modal.js
│      NotFound.js
│      Signup.js
│      Todos.js
│
├─images
│      TodoAppicon.png
│
└─services
        firebase.config.js

代码的完整产品 (Code’s finished product)

如果你按照实际操作指南的资料进行操作,基本上应该不会有问题。

只需进行必要的安装,创建文件夹和文件,然后复制粘贴,就可以完成了。

源代码 / App.js

App.jsapp.jsx
从’react’中导入React和Component
从’./index.css’中导入’./Components/Home’和’./Components/Login’和’./Components/Signup’和’./Components/NotFound’并命名为Home和Login和Signup和NotFound
从’./services/firebase.config’中导入auth和db
从’firebase/firestore’中导入doc和getDoc和deleteDoc和query和where和collection和onSnapshot
创建一个名为App的类,继承Component类

状态 = {
currentUser: null,
todos: [],
editTodoValue: null
}

componentDidMount() {
auth.onAuthStateChanged(user => {
if (user) {
getDoc(doc(db, ‘users’, user.uid)).then(snapshot => {
this.setState({
currentUser: snapshot.data().userName
})
})
}
else {
console.log(“未登录用户”)
}
})

auth.onAuthStateChanged(user => {
if (user) {
const todoList = [];
const q = query(
collection(db, ‘todos of’ + user.uid),
where(‘userId’, ‘==’, user.uid)
);
const unsubscribe = onSnapshot(q, (snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === ‘added’) {
todoList.push({
id: change.doc.id,
Todo: change.doc.data().Todo,
});
}
if (change.type === ‘removed’) {
//console.log(change.type);
for (let i = 0; i < todoList.length; i++) { if (todoList[i].id === change.doc.id) { todoList.splice(i, 1); } } } }); // console.log(‘待办事项列表:’, todoList); this.setState({ todos: todoList }); }); return unsubscribe; } else { console.log(‘未登录用户’); } }); } deleteTodo = (id) => {
// console.log(id);
auth.onAuthStateChanged((user) => {
if (user) {
const docRef = doc(db, ‘todos of’ + user.uid, id);
deleteDoc(docRef)
.then(() => {
console.log(‘文档删除成功!’);
})
.catch((error) => {
console.error(‘删除文档时出错: ‘, error);
});
} else {
console.log(‘未登录用户’);
}
});
};

editModal = (obj) => {
this.setState({
editTodoValue: obj
})
}

updateTodoHandler = (editTodo, id) => {
// console.log(editTodo, id);
const todoList = this.state.todos;
for(let i=0;i<todoList.length;i++){
if(todoList[i].id===id){
todoList.splice(i,1,{id,Todo: editTodo});
}
this.setState({
todos:todoList
})
}
}

render() {
返回 (

} />
} />
} />
} />

)
}
}

导出默认的App

源码/索引.css

索引.css
* {
边距: 0;
填充: 0;
}div.包装器 {
横向溢出: 隐藏;
纵向溢出: 自动;
}

/* 头部 */
.头部-盒子 {
宽度: 100%;
高度: 自动;
填充: 50px;
显示: 弹性;
对齐内容: 空隙分布;
对齐项目: 居中;
背景颜色: #0170ad;
}

@media(max-width:768px){
.头部-盒子{
弹性方向: 垂直;
对齐内容: 居中;
}
}

.头部-盒子 .左侧 {
弹性: 1;
显示: 弹性;
对齐内容: 弹性开始;
对齐项目: 居中;
}

@media(max-width: 768px){
.头部-盒子 .左侧{
宽度: 100%;
弹性方向: 垂直-反向;
对齐内容: 居中;
文本对齐: 中心;
下边距: 20px;
}
}

.头部-盒子 .左侧 .图像 {
宽度: 170px;
高度: 170px;
}

.头部-盒子 .左侧 .图像 图片 {
宽度: 100%;
高度: 100%;
}

.头部-盒子 .左侧 .内容 {
颜色: #fff;
}

.头部-盒子 .左侧 .内容 .大标题 {
字体大小: 42px;
}

.头部-盒子 .左侧 .内容 .小标题 {
字体大小: 24px;
}

.头部-盒子 .右侧 {
弹性: 1;
显示: 弹性;
弹性方向: 垂直;
对齐内容: 居中;
对齐项目: 居中;
}

@media(max-width:768px){
.头部-盒子 .右侧{
宽度: 100%;
文本对齐: 中心;
}
}

.头部-盒子 .右侧 .按钮, .头部-盒子 .右侧 .按钮:hover{
宽度: 100px;
下边距: 5px;
颜色: #fff;
文本装饰: 无;
}

.日期-区块{
颜色: #fff;
}

.日期-区块 span{
左边距: 4px;
}

/* 注册 */
@media(max-width:539px){
.注册{
宽度:100%
}
}

.错误-消息{
颜色: 红色;
宽度:100%;
字体大小:14px;
字体粗细: 600;
}

.欢迎-区块{
颜色: #fff;
字母间距: 0.09em;
}

/* 待办事项 */
.待办事项{
背景颜色: #e4e4e4;
字体粗细: 600;
字体大小: 16px;
边距: 10px 0px;
填充: 10px;
显示: 弹性;
对齐内容: 空隙分布;
对齐项目: 居中;
}

.待办事项 .操作-区块{
显示: 弹性;
对齐内容: 弹性开始;
对齐项目: 居中;
}

.待办事项 .操作-区块 div{
边距: 0px 10px;
光标: 指针;
}

.删除-按钮{
颜色: rgb(165, 2, 2);
光标: 指针;
}

.模态-容器{
宽度: 100%;
高度: 100vh;
位置: 固定;
顶部: 0;
左侧: 0;
背景颜色: rgba(0,0,0,0.7);
显示: 弹性;
对齐内容: 居中;
对齐项目: 居中;
}

.模态-容器 .模态{
显示: 块;
宽度: 70%;
高度: 70vh;
背景颜色: #fff;
边框半径: 20px;
边距: 60px 15%;
}

@media(max-width: 768px){
.模态-容器 .模态{
宽度: 100%;
高度: 100vh;
边距: 0;
边框半径: 0px;
}
}

.模态-容器 .模态 .头部{
宽度: 100%;
高度: 自动;
填充: 20px;
显示: 弹性;
对齐内容: 空隙分布;
对齐项目: 居中;
}

.模态-容器 .模态 .头部 .更新-文本{
字体大小: 24px;
字体粗细: 600;
宽度: 100%;
}

.模态-容器 .模态 .头部 .关闭-按钮{
颜色: rgb(165, 2, 2);
光标: 指针;
}

组件/ Header.js

Header.jsHeader.jsx
import React, { useEffect, useState } from ‘react’
import { Link } from ‘react-router-dom’

import todoIcon from ‘../images/TodoAppicon.png’
import ‘bootstrap/dist/css/bootstrap.css’
import ‘../index.css’

import {auth} from ‘../services/firebase.config’

export const Header = ({ currentUser }) => {

const [year, setYear] = useState(null);
const [date, setDate] = useState(null);
const [month, setMonth] = useState(null);
const [day, setDay] = useState(null);

useEffect(() => {
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentDateOfMonth = currentDate.getDate();
const currentMonth = currentDate.toLocaleString(‘ja-JP’, { month: ‘long’ });
const currentDay = currentDate.toLocaleDateString(‘js-JP’, { weekday: ‘long’ });

setYear(currentYear);
setDate(currentDateOfMonth);
setMonth(currentMonth);
setDay(currentDay);
}, [])

const handleLogout = () => {
auth.signOut().then(() => {
window.location.reload();
});
}

return (

todoIcon
还有任务要做?
做个列表吧!

{!currentUser && <>
注册
登录

{year}年
{month}
{date}日
{day}

</>}

{currentUser &&

欢迎

{currentUser}

 

{year}年
{month}
{date}日
{day}

}

)
}

组件 / Home.js

Home.jsHome.jsx
import React, { useState } from ‘react’

import ‘../index.css’

import { Header } from ‘./Header’
import { Todos } from ‘./Todos’
import { Modal } from ‘./Modal’

import { auth, db } from ‘../services/firebase.config’
import { collection, addDoc } from ‘firebase/firestore’

export const Home = ({ currentUser, todos,deleteTodo,editTodoValue,editModal,updateTodoHandler }) => {

const [todo, setTodo] = useState(”);
const [todoError, setTodoError] = useState(”);

const handleTodoSubmit = async (e) => {
e.preventDefault();
await auth.onAuthStateChanged(user => {
if (user) {
addDoc(collection(db, ‘用户’ + user.uid + ‘的待办事项’), {
待办事项: todo,
用户ID: user.uid
}).then(setTodo(”)).catch(err => setTodoError(err.message))
}
else {
console.log(“用户未登录”);
}
})
}

return (

{currentUser && <>
setTodo(e.target.value)}
value={todo}
/>

</>}

{!currentUser && <>

请注册您的账户或登录以使用应用程序

</>}

{todoError &&

}

{editTodoValue && }

)
}

组件 / IndividualTodo.js

IndividualTodo.jsIndividualTodo.jsx
import React from ‘react’

import {FiEdit} from ‘react-icons/fi’
import {FaTrashAlt} from ‘react-icons/fa’

export const IndividualTodo = ({ individualTodo,deleteTodo,editModal }) => {

const handleDelete=()=>{
deleteTodo(individualTodo.id);
}

const handleEditModal=()=>{
editModal(individualTodo);
}

return (

{individualTodo.Todo}

)
}

组件 / 登录.js

Login.jsLogin.jsx
import React, { useState } from ‘react’
import { Link, useNavigate } from ‘react-router-dom’

import { signInWithEmailAndPassword } from ‘firebase/auth’;
import { auth } from ‘../services/firebase.config’

export const Login = () => {
const [email, setEmail] = useState(”);
const [password, setPassword] = useState(”);

const [loginError, setLoginError] = useState(”);

const navigate = useNavigate();

const handleLogin = async (e) => {
e.preventDefault();
try {
await signInWithEmailAndPassword(auth, email, password);
setEmail(”);
setPassword(”);
setLoginError(”);
navigate(‘/’);
} catch (error) {
setLoginError(error.message);
}
};

return (

在这里登录

 


setEmail(e.target.value)}
value={email}
/>

 


setPassword(e.target.value)}
value={password}
/>

 

{loginError &&

{loginError}

}

还没有账户? 创建一个
这里

)
}

组件 / Modal.js

Modal.jsModal.jsx
import React, { useState } from ‘react’

import { FiXCircle } from ‘react-icons/fi’

import { doc, updateDoc } from “firebase/firestore”;
import { db, auth } from ‘../services/firebase.config’

export const Modal = ({ editTodoValue, editModal,updateTodoHandler }) => {

const [editTodo, setEditTodo] = useState(editTodoValue.Todo);

const handleClose = () => {
editModal(null)
}

const handleEditTodoSubmit = async (e) => {
e.preventDefault();
handleClose();
updateTodoHandler(editTodo, editTodoValue.id);
await auth.onAuthStateChanged(user => {
if (user) {
const todoRef = doc(db, ‘todos of’ + user.uid,editTodoValue.id);
updateDoc(todoRef, {
Todo: editTodo
})

}
else {
console.log(“user is not signed”);
}
})
}

return (

更新待办事项
setEditTodo(e.target.value)}
/> 

)
}

组件 / NotFound.js

未找到.jsNotFound.jsx
import React from ‘react’

export const NotFound = () => {
return (

未找到

)
}

组件 / 注册.js

签署.js签署.jsx
import React, { useState } from ‘react’;
import { Link } from ‘react-router-dom’;
import { useNavigate } from ‘react-router-dom’;

import { createUserWithEmailAndPassword } from ‘firebase/auth’;
import { doc, setDoc } from ‘firebase/firestore’;
import { auth, db } from ‘../services/firebase.config’;

export const Signup = () => {
const [userName, setUserName] = useState(”);
const [email, setEmail] = useState(”);
const [password, setPassword] = useState(”);
const [registerError, setRegisterError] = useState(”);
const navigate = useNavigate();

const handleRegister = async (e) => {
e.preventDefault();
try {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const { uid } = userCredential.user;
console.log(userName, email, password, uid);
await setDoc(doc(db, ‘users’, uid), {
userName: userName,
Email: email,
Password: password,
});
setUserName(”);
setEmail(”);
setPassword(”);
setRegisterError(”);
navigate(‘/login’);
} catch (error) {
setRegisterError(error.message);
}
};

return (

现在注册!

 


setUserName(e.target.value)}
value={userName}
/>

 


setEmail(e.target.value)}
value={email}
/>

 


setPassword(e.target.value)}
value={password}
/>

 

{registerError && (

{registerError}

)}

已经拥有账户? 在这里登录

);
};

组件/ Todos.js

Todos.jsTodos.jsx
import React from ‘react’
import { IndividualTodo } from ‘./IndividualTodo’

export const Todos = ({todos,deleteTodo,editModal}) => {
return todos.map((individualTodo)=>{
return (

)
})
}

服务 / firebase.config.js

firebase.config.jsfirebase.config.js
import { initializeApp } from ‘firebase/app’
import { getAuth } from ‘firebase/auth’
import { getFirestore } from ‘firebase/firestore’

const firebaseConfig = {
apiKey: “你的配置代码”,
authDomain: “你的配置代码”,
projectId: “你的配置代码”,
storageBucket: “你的配置代码”,
messagingSenderId: “你的配置代码”,
appId: “你的配置代码”,
measurementId: “你的配置代码”
};

const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
const db = getFirestore(app)

export { auth, db }

结束

通过制作Todo应用并添加各种功能,我能够理解React和Firestore数据库操作的各种创建方法。

想要制作想象中的事物,需要相应的先决知识,所以学习是不可或缺的。

我之前制作了一个学食售罄信息确认应用程序,名为“随时检查午餐”,但在学习React的过程中,我意识到与仅使用JavaScript相比,使用React可以更容易地按功能进行创建并进行管理。因此,我希望今后能使用React进行制作。

各位,在现代(2023/05/22)这个时代,有许多信息可以供您参考,请您尝试自行查询或咨询。

广告
将在 10 秒后关闭
bannerAds