React Basic 1 ContactList
https://stackblitz.com/edit/react-j2anynkk?file=README.md
- Make a GET request to "https://jsonplaceholder.typicode.com/users"
- Display all users as contacts and the associated properties (name, email, website) in a table
- A user should be able to add a new contact by filling out the form and clicking on the "Add" button
- If any of the fields are empty, users should not be able to add a new contact, and the application should alert with the message "Fields cannot be empty!"
https://jsonplaceholder.typicode.com/users
:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
{
"id": 3,
"name": "Clementine Bauch",
"username": "Samantha",
"email": "Nathan@yesenia.net",
"address": {
"street": "Douglas Extension",
"suite": "Suite 847",
"city": "McKenziehaven",
"zipcode": "59590-4157",
"geo": {
"lat": "-68.6102",
"lng": "-47.0653"
}
},
"phone": "1-463-123-4447",
"website": "ramiro.info",
"company": {
"name": "Romaguera-Jacobson",
"catchPhrase": "Face to face bifurcated interface",
"bs": "e-enable strategic applications"
}
},
{
"id": 4,
"name": "Patricia Lebsack",
"username": "Karianne",
"email": "Julianne.OConner@kory.org",
"address": {
"street": "Hoeger Mall",
"suite": "Apt. 692",
"city": "South Elvis",
"zipcode": "53919-4257",
"geo": {
"lat": "29.4572",
"lng": "-164.2990"
}
},
"phone": "493-170-9623 x156",
"website": "kale.biz",
"company": {
"name": "Robel-Corkery",
"catchPhrase": "Multi-tiered zero tolerance productivity",
"bs": "transition cutting-edge web services"
}
},
{
"id": 5,
"name": "Chelsey Dietrich",
"username": "Kamren",
"email": "Lucio_Hettinger@annie.ca",
"address": {
"street": "Skiles Walks",
"suite": "Suite 351",
"city": "Roscoeview",
"zipcode": "33263",
"geo": {
"lat": "-31.8129",
"lng": "62.5342"
}
},
"phone": "(254)954-1289",
"website": "demarco.info",
"company": {
"name": "Keebler LLC",
"catchPhrase": "User-centric fault-tolerant solution",
"bs": "revolutionize end-to-end systems"
}
},
{
"id": 6,
"name": "Mrs. Dennis Schulist",
"username": "Leopoldo_Corkery",
"email": "Karley_Dach@jasper.info",
"address": {
"street": "Norberto Crossing",
"suite": "Apt. 950",
"city": "South Christy",
"zipcode": "23505-1337",
"geo": {
"lat": "-71.4197",
"lng": "71.7478"
}
},
"phone": "1-477-935-8478 x6430",
"website": "ola.org",
"company": {
"name": "Considine-Lockman",
"catchPhrase": "Synchronised bottom-line interface",
"bs": "e-enable innovative applications"
}
},
{
"id": 7,
"name": "Kurtis Weissnat",
"username": "Elwyn.Skiles",
"email": "Telly.Hoeger@billy.biz",
"address": {
"street": "Rex Trail",
"suite": "Suite 280",
"city": "Howemouth",
"zipcode": "58804-1099",
"geo": {
"lat": "24.8918",
"lng": "21.8984"
}
},
"phone": "210.067.6132",
"website": "elvis.io",
"company": {
"name": "Johns Group",
"catchPhrase": "Configurable multimedia task-force",
"bs": "generate enterprise e-tailers"
}
},
{
"id": 8,
"name": "Nicholas Runolfsdottir V",
"username": "Maxime_Nienow",
"email": "Sherwood@rosamond.me",
"address": {
"street": "Ellsworth Summit",
"suite": "Suite 729",
"city": "Aliyaview",
"zipcode": "45169",
"geo": {
"lat": "-14.3990",
"lng": "-120.7677"
}
},
"phone": "586.493.6943 x140",
"website": "jacynthe.com",
"company": {
"name": "Abernathy Group",
"catchPhrase": "Implemented secondary concept",
"bs": "e-enable extensible e-tailers"
}
},
{
"id": 9,
"name": "Glenna Reichert",
"username": "Delphine",
"email": "Chaim_McDermott@dana.io",
"address": {
"street": "Dayna Park",
"suite": "Suite 449",
"city": "Bartholomebury",
"zipcode": "76495-3109",
"geo": {
"lat": "24.6463",
"lng": "-168.8889"
}
},
"phone": "(775)976-6794 x41206",
"website": "conrad.com",
"company": {
"name": "Yost and Sons",
"catchPhrase": "Switchable contextually-based project",
"bs": "aggregate real-time technologies"
}
},
{
"id": 10,
"name": "Clementina DuBuque",
"username": "Moriah.Stanton",
"email": "Rey.Padberg@karina.biz",
"address": {
"street": "Kattie Turnpike",
"suite": "Suite 198",
"city": "Lebsackbury",
"zipcode": "31428-2261",
"geo": {
"lat": "-38.2386",
"lng": "57.2232"
}
},
"phone": "024-648-3804",
"website": "ambrose.net",
"company": {
"name": "Hoeger LLC",
"catchPhrase": "Centralized empowering task-force",
"bs": "target end-to-end models"
}
}
]
/src/components/Form.js
import React from 'react';
export default function Form() {
return (
<form className="form">
<h1>Contact Form</h1>
<input
className="input"
placeholder="Name"/>
<input
className="input"
placeholder="Email"/>
<input
className="input"
placeholder="Website"/>
<button className="btn" type="submit">Add</button>
</form>
)
}
1 Make a GET
- Make a GET request to "https://jsonplaceholder.typicode.com/users"
App.js
import React, { useState, useEffect } from 'react';
// useState 是 React 内置的 Hook,需要先从 react 中导入,否则 JavaScript 不知道 useState 是什么
import './style.css';
// 引入样式文件,使页面具有良好的 UI 设计
import Form from './components/Form.js';
// 引入 Form 组件,该组件用于输入新的联系人信息
// 定义 App 组件,并将其作为默认导出
export default function App() {
const [users, setUsers] = useState([]);
// users:存储用户列表的状态变量(初始为空数组 []
// setUsers:用于更新 users 的函数
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json()) // 解析服务器返回的 JSON 数据。
.then((data) => setUsers(data)) // 将获取到的 data 设置到 users 状态变量中,以便在 UI 上展示
.catch((error) => console.error('Errot fetching user:', error));
// 如果 fetch 失败(如网络错误),则打印错误信息
// return () => {
// console.log('unMount');
// };
}, []);
// useEffect(() => { ... }, []);
// useEffect 作用是在组件加载时执行 fetch 请求,获取用户数据
// 由于 [] 为空数组,所以这个 useEffect 只会执行一次(相当于 componentDidMount)
// fetch('https://jsonplaceholder.typicode.com/users')
// 向 jsonplaceholder.typicode.com 这个假 API 发送 GET 请求,以获取用户列表
return (
<div className="container">
<h1>Contacts</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Website</th>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>email</td>
<td>website</td>
</tr>
</tbody>
</table>
<Form />
</div>
);
}
2 Display all users
- Display all users as contacts and the associated properties (name, email, website) in a table
import React, { useState, useEffect } from 'react';
// useState 是 React 内置的 Hook,需要先从 react 中导入,否则 JavaScript 不知道 useState 是什么
import './style.css';
// 引入样式文件,使页面具有良好的 UI 设计
import Form from './components/Form.js';
// 引入 Form 组件,该组件用于输入新的联系人信息
// 定义 App 组件,并将其作为默认导出
export default function App() {
const [users, setUsers] = useState([]);
// users:存储用户列表的状态变量(初始为空数组 []
// setUsers:用于更新 users 的函数
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json()) // 解析服务器返回的 JSON 数据。
.then((data) => setUsers(data)) // 将获取到的 data 设置到 users 状态变量中,以便在 UI 上展示
.catch((error) => console.error('Errot fetching user:', error));
// 如果 fetch 失败(如网络错误),则打印错误信息
// return () => {
// console.log('unMount');
// };
}, []);
// useEffect(() => { ... }, []);
// useEffect 作用是在组件加载时执行 fetch 请求,获取用户数据
// 由于 [] 为空数组,所以这个 useEffect 只会执行一次(相当于 componentDidMount)
// fetch('https://jsonplaceholder.typicode.com/users')
// 向 jsonplaceholder.typicode.com 这个假 API 发送 GET 请求,以获取用户列表
// return 语句返回 App 组件的 UI 结构
return (
// 容器 div,用于包裹整个组件
<div className="container">
<h1>Contacts</h1>
{/*
标题 “Contacts”
*/}
<table>
{/* <table>: HTML 表格,用于显示联系人信息 */}
<thead>
{/* <thead>:表头部分,定义列名(Name、Email、Website) */}
<tr>
<th>Name</th>
<th>Email</th>
<th>Website</th>
</tr>
</thead>
<tbody>
{/* <tbody>:表格主体 */}
{/* users.map((user) => (...))
遍历 users 数组,为每个 user 生成一个 <tr> 行 */}
{users.map((user) => (
<tr>
{/* <tr key={user.id}> */}
{/* key={user.id}:使用 user.id 作为 React 的唯一标识符,防止重复*/}
<td>{user.name}</td>
{/* 显示用户的 name(名字)*/}
<td>{user.email}</td>
{/* 显示用户的 email(邮箱)*/}
<td>{user.website}</td>
{/* 显示用户的 website(网站)*/}
</tr>
))}
</tbody>
</table>
<Form />
</div>
);
}
3 Add a new contact
- A user should be able to add a new contact by filling out the form and clicking on the "Add" button
App.js
:
import React, { useState, useEffect } from 'react';
// useState 是 React 内置的 Hook,需要先从 react 中导入,否则 JavaScript 不知道 useState 是什么
import './style.css';
// 引入样式文件,使页面具有良好的 UI 设计
import Form from './components/Form.js';
// 引入 Form 组件,该组件用于输入新的联系人信息
// 定义 App 组件,并将其作为默认导出
export default function App() {
const [users, setUsers] = useState([]);
// users:存储用户列表的状态变量(初始为空数组 []
// setUsers:用于更新 users 的函数
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json()) // 解析服务器返回的 JSON 数据。
.then((data) => setUsers(data)) // 将获取到的 data 设置到 users 状态变量中,以便在 UI 上展示
.catch((error) => console.error('Errot fetching user:', error));
// 如果 fetch 失败(如网络错误),则打印错误信息
// return () => {
// console.log('unMount');
// };
}, []);
// useEffect(() => { ... }, []);
// useEffect 作用是在组件加载时执行 fetch 请求,获取用户数据
// 由于 [] 为空数组,所以这个 useEffect 只会执行一次(相当于 componentDidMount)
// fetch('https://jsonplaceholder.typicode.com/users')
// 向 jsonplaceholder.typicode.com 这个假 API 发送 GET 请求,以获取用户列表
// return 语句返回 App 组件的 UI 结构
// 定义 addContact 函数,用于添加新联系人
// const addContact → 定义一个常量变量 addContact,它是一个函数
// (newContact) => {} → 箭头函数,它的参数是 newContact,表示新联系人数据
const addContact = (newContact) => {
setUsers([...users, newContact]);
// 使用 setUsers 更新用户列表
// ...users:展开现有的 users 数组
// newContact:添加新的联系人对象到 users 末尾
};
// const 是 JavaScript 中用于声明 常量(constant)的关键字。它表示 声明的变量不能重新赋值,但如果变量是一个对象或数组,我们仍然可以修改它的内容
// () => {} 是 箭头函数(Arrow Function) 的语法。它是 JavaScript ES6+ 提供的一种更简洁的函数写法,相当于:
// function functionName() {
// // 代码
// }
// 用箭头函数表示:
// const functionName = () => {
// // 代码
// };
return (
// 容器 div,用于包裹整个组件
<div className="container">
<h1>Contacts</h1>
{/*
标题 “Contacts”
*/}
<table>
{/* <table>: HTML 表格,用于显示联系人信息 */}
<thead>
{/* <thead>:表头部分,定义列名(Name、Email、Website) */}
<tr>
<th>Name</th>
<th>Email</th>
<th>Website</th>
</tr>
</thead>
<tbody>
{/* <tbody>:表格主体 */}
{/* users.map((user) => (...))
遍历 users 数组,为每个 user 生成一个 <tr> 行 */}
{users.map((user) => (
<tr>
{/* <tr key={user.id}> */}
{/* key={user.id}:使用 user.id 作为 React 的唯一标识符,防止重复*/}
<td>{user.name}</td>
{/* 显示用户的 name(名字)*/}
<td>{user.email}</td>
{/* 显示用户的 email(邮箱)*/}
<td>{user.website}</td>
{/* 显示用户的 website(网站)*/}
</tr>
))}
</tbody>
</table>
<Form onAddContact={addContact} />
{/* <Form onAddContact={addContact} />
Form 组件,用于新增联系人,并将 addContact 作为 prop 传递 */}
</div>
);
}
/src/components/Form.js
:
import React, { useState } from 'react';
export default function Form({ onAddContact }) {
// 定义 Form 组件,并接收 onAddContact 作为 prop,用于添加联系人
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [website, setWebsite] = useState('');
// name、email、website 分别管理输入框的状态,初始值为空字符串 ''
// Handle form submission
// handleSubmit(e):监听表单提交事件 onSubmit
const handleSubmit = (e) => {
e.preventDefault(); // 阻止默认提交行为,避免页面刷新
/*
在 HTML 表单中,如果你点击提交按钮(<button type="submit">)或者按下 Enter 键,默认行为是将表单数据提交给服务器,并且 浏览器会刷新页面。
在某些情况下,我们希望 拦截表单提交事件,然后用 JavaScript 处理数据(比如使用 AJAX 发送请求,而不是让页面刷新)。这时,就可以用 e.preventDefault(); 来 阻止默认提交行为
此处要是不阻止 就会刷新, 新添加的就会不见了.
*/
const newContact = { name, email, website, id: Date.now() };
// 创建 newContact 对象,包含 name、email、website,并用 Date.now() 生成唯一 id
onAddContact(newContact);
// 调用 onAddContact,将新联系人添加到 App.js 的 users 状态中。
// Clear the form
setName('');
setEmail('');
setWebsite('');
};
return (
<form className="form" onSubmit={handleSubmit}>
{/* <form> 绑定 onSubmit={handleSubmit},点击提交时执行 handleSubmit 逻辑 */}
<h1>Contact Form</h1>
<input
className="input"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
{/* value={name} 绑定 name 变量,onChange={(e) => setName(e.target.value)} 更新状态 */}
<input
className="input"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="input"
placeholder="Website"
value={website}
onChange={(e) => setWebsite(e.target.value)}
/>
<button className="btn" type="submit">
Add
</button>
</form>
);
}
4 Fields cannot be empty
- If any of the fields are empty, users should not be able to add a new contact, and the application should alert with the message "Fields cannot be empty!"
/src/components/Form.js
:
import React, { useState } from 'react';
export default function Form({ onAddContact }) {
// 定义 Form 组件,并接收 onAddContact 作为 prop,用于添加联系人
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [website, setWebsite] = useState('');
// name、email、website 分别管理输入框的状态,初始值为空字符串 ''
// Handle form submission
// handleSubmit(e):监听表单提交事件 onSubmit
const handleSubmit = (e) => {
e.preventDefault(); // 阻止默认提交行为,避免页面刷新
/*
在 HTML 表单中,如果你点击提交按钮(<button type="submit">)或者按下 Enter 键,默认行为是将表单数据提交给服务器,并且 浏览器会刷新页面。
在某些情况下,我们希望 拦截表单提交事件,然后用 JavaScript 处理数据(比如使用 AJAX 发送请求,而不是让页面刷新)。这时,就可以用 e.preventDefault(); 来 阻止默认提交行为
此处要是不阻止 就会刷新, 新添加的就会不见了.
*/
// Validate that fields are not empty
if (!name || !email || !website) {
alert('Fields cannot be empty!');
return;
}
const newContact = { name, email, website, id: Date.now() };
// 创建 newContact 对象,包含 name、email、website,并用 Date.now() 生成唯一 id
onAddContact(newContact);
// 调用 onAddContact,将新联系人添加到 App.js 的 users 状态中。
// Clear the form
setName('');
setEmail('');
setWebsite('');
};
return (
<form className="form" onSubmit={handleSubmit}>
{/* <form> 绑定 onSubmit={handleSubmit},点击提交时执行 handleSubmit 逻辑 */}
<h1>Contact Form</h1>
<input
className="input"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
{/* value={name} 绑定 name 变量,onChange={(e) => setName(e.target.value)} 更新状态 */}
<input
className="input"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="input"
placeholder="Website"
value={website}
onChange={(e) => setWebsite(e.target.value)}
/>
<button className="btn" type="submit">
Add
</button>
</form>
);
}