現代 Web 開發實戰API 開發與前端整合班第01期 - 前端篇
===
- [大綱](/35WkTtFuRs-9pnqT3taIrA)
安裝環境
---
- [安裝 Node.js](/7wJdIsrbSnyYSeBtQAjPZQ)
- [使用 NVM (解決 Node.js 版本衝突)](/cvCwml8_TgmLJ6YAaDE_XQ)
- [VSCode 環境設定](/mFLqf-IsQ9Kb2ZK-AHzY1g)
- [[舊版] VSCode 環境設定](/owdA5uCnSgWUbeaGl56WmA)
第一週課程
---
<!-- [複習前端網頁的基礎知識,深入瞭解HTML、CSS和JavaScript的角色] -->
- [架構概念與環境建置,前後端分離概念解析與實務開發工作流說明](/eFS6M4egQwmP9h6AXi3cfg)
- [API 整合基礎,深入 Axios 應用與特點解析,實作 API 發送請求](/x5D8-qTxThiYyvj_pbY9_g)
第二週課程
---
- [React 是什麼?](/FbWE3qFVTH2sSA_wCsSw2A)
- [NPM 安裝以及 React Project 演練](/_Yena9U2RMW0APB9WYolXg)
- [資料流(Data Flow)——從入門到實務](/-W9pFyMRRQG2TNqeOJypeA)
- [Next.js 關鍵特性解析,專案與環境搭建及核心功能說明](/re12aPfySXeSDdpYygI9oQ)
- [TypeScript 基礎應用入門,並與 Next.js 進行整合開發](/2Qi1PLWrRb6tsfXE1_PK4g)
第三週課程
---
- [前端表單處理與認證,使用 React Hook Form 驗證欄位與處理錯誤資訊](/uI5_1Vz_TJi0MOMcIWsPpg)
- [了解狀態管理與快取,Tanstack Query 基礎學習與基本 API 應用](/Vw6LR-35SHqGhGZOr3nVpA)
第四週課程
---
- [API 進階整合應用,Axios 攔截器設定與處理認證及授權等](/1dpzy-rRRsuTTktye3-Apg)
第五週課程
---
- [整合專案開發,完成全端網站應用](/OFz4QwZaTXaYLSlZAX8eFA)
[前端課程教材](https://hackmd.io/@react-course/SJcB6W1rA/%2FZTg7Zsq8Qd2C487323xihw)
```jsx=
import { useEffect, useMemo, useState, useCallback } from 'react';
import TodoItem from './TodoItem';
const TodoList = () => {
const [todoList, setTodoList] = useState([]);
const [keyword, setKeyword] = useState('');
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/todos?_limit=50')
.then((r) => r.json())
.then((data) => setTodoList(data));
}, []);
const deleteTodo =
((id) => {
setTodoList((list) => list.filter((t) => t.id !== id));
},
[]);
return (
<div>
<h1>Todo list</h1>
<input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder='搜尋標題'
/>
<ul>
{todoList
.filter((t) => t.title.toLowerCase().includes(keyword))
.map((todo, index) => (
<TodoItem
key={todo.id}
index={index}
todo={todo}
deleteTodo={deleteTodo}
/>
))}
</ul>
</div>
);
};
export default TodoList;
```
```
// TodoItem.jsx
import './App.css';
import Greeting from './components/Greeting';
function App() {
return (
<>
<Greeting name='sss' />
</>
);
}
export default App;
```
```
// TodoItem.jsx
// 方式一:具名 Props 型別 + 傳統函式
type GreetingProps = { name: string; children?: React.ReactNode };
function Greeting({ name, children }: GreetingProps) {
return (
<h1>
Hello, {name}! {children}
</h1>
);
}
export default Greeting;
```
```
import { useState } from 'react';
function Form() {
const [formData, setFormData] = useState({
username: '',
email: '',
});
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target; // 從 event.target 中解構出 name 和 value
setFormData({ ...formData, [name]: value });
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // 防止表單提交後頁面刷新
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type='text'
name='username'
value={formData.username}
onChange={handleChange}
/>
<input
type='email'
name='email'
value={formData.email}
onChange={handleChange}
/>
<button type='submit'>Submit</button>
</form>
);
}
export default Form;
```
```
import './App.css';
import Form from './components/Form';
function App() {
return (
<>
<Form />
</>
);
}
export default App;
```
```
import React, { useState } from 'react';
const NativeForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [formError, setFormError] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
if (!email || !password) {
setFormError(true);
return;
} else {
setFormError(false);
}
const data = {
email: email,
password: password,
};
console.log(data);
};
return (
<form
onSubmit={handleSubmit}
style={{ display: 'flex', flexDirection: 'column' }}
>
<label htmlFor=''>帳號</label>
<input
type='text'
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{formError && !email && '帳號為必填欄位'}
<label htmlFor=''>密碼</label>
<input
type='text'
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{formError && !password && '密碼為必填欄位'}
<input type='checkbox' />
是否記錄登入狀態
<input type='submit' />
</form>
);
};
export default NativeForm;
```
```
import React from 'react';
import { useForm } from 'react-hook-form';
const formFields = [
{
label: '電子信箱',
name: 'email',
type: 'email',
placeholder: '請輸入信箱',
rules: { required: '帳號為必填欄位', maxLength: 80 },
},
{
label: '密碼',
name: 'password',
type: 'password',
rules: { required: '密碼為必填欄位' },
},
{
label: '使用者姓名',
name: 'userName',
type: 'text',
},
];
const LoopForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = (data: unknown) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{formFields.map((field) => (
<React.Fragment key={field.name}>
<label htmlFor={field.name}>{field.label}</label>
<input
id={field.name}
type={field.type}
placeholder={field.placeholder ?? '請輸入'}
{...register(field.name, field.rules)}
/>
{errors[field.name] && <>{errors[field.name]?.message}</>}
</React.Fragment>
))}
<button type='submit'>Submit</button>
</form>
);
};
export default LoopForm;
```
```
import React from 'react';
import { useForm } from 'react-hook-form';
const formFields = [
{
name: 'firstName',
label: '姓氏',
type: 'text',
rules: { required: true, maxLength: 80 },
},
{
name: 'lastName',
label: '名稱',
type: 'text',
rules: { required: true, maxLength: 80 },
},
{
name: 'title',
label: '尊稱',
type: 'select',
options: [
{
label: 'Mr',
value: 'Mr',
},
{
label: 'Mrs',
value: 'Mrs',
},
{
label: 'Miss',
value: 'Miss',
},
{
label: 'Dr',
value: 'Dr',
},
],
},
];
const LoopForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = (data: unknown) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{formFields.map((field) => (
<React.Fragment key={field.name}>
<label htmlFor={field.name}>{field.label}</label>
{field.type === 'select' ? (
<select {...register(field.name, { required: true })}>
{field.options?.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
) : (
<input
type={field.type}
placeholder={`請輸入${field.label}`}
{...register(field.name, field.rules)}
/>
)}
{errors[field.name] && <>{errors[field.name]?.message}</>}
</React.Fragment>
))}
<button type='submit'>Submit</button>
</form>
);
};
export default LoopForm;
```
---
### Field.tsx
```
const Field = ({ field, register }) => {
switch (field.type) {
case 'select':
return (
<select {...register(field.name, { required: true })}>
{field.options.map((option) => (
<option key={option.value}>{option.label}</option>
))}
</select>
);
case 'radio':
return (
<>
{field.options.map((option) => (
<label>
<input
{...register(field.name, { required: true })}
type='radio'
value={option.value}
/>
{option.value}
</label>
))}
</>
);
default:
return (
<input
type={field.type}
placeholder={`請輸入${field.label}`}
{...register(field.name, field.rules)}
/>
);
}
};
export default Field;
```
### Form.tsx
```
import React from 'react';
import { useForm } from 'react-hook-form';
import Field from './Field';
const formFields = [
{
name: 'firstName',
label: '姓氏',
type: 'text',
rules: { required: true, maxLength: 80 },
},
{
name: 'lastName',
label: '名稱',
type: 'text',
rules: { required: true, maxLength: 80 },
},
{
name: 'title',
label: '尊稱',
type: 'select',
options: [
{
label: 'Mr',
value: 'Mr',
},
{
label: 'Mrs',
value: 'Mrs',
},
{
label: 'Miss',
value: 'Miss',
},
{
label: 'Dr',
value: 'Dr',
},
],
},
{
name: 'yesNo',
label: '尊稱',
type: 'radio',
options: [
{
label: 'Yes',
value: 'Yes',
},
{
label: 'no',
value: 'no',
},
],
},
];
const LoopForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = (data: unknown) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{formFields.map((field) => (
<React.Fragment key={field.name}>
<label htmlFor={field.name}>{field.label}</label>
<Field field={field} register={register} />
{errors[field.name] && <>{errors[field.name]?.message}</>}
</React.Fragment>
))}
<button type='submit'>Submit</button>
</form>
);
};
export default LoopForm;
```
### App.tsx
```
import './App.css';
import Form from './components/Form';
const formFields = [
{
name: 'firstName',
label: '姓氏',
type: 'text',
rules: { required: true, maxLength: 80 },
},
{
name: 'lastName',
label: '名稱',
type: 'text',
rules: { required: true, maxLength: 80 },
},
{
name: 'title',
label: '尊稱',
type: 'select',
options: [
{
label: 'Mr',
value: 'Mr',
},
{
label: 'Mrs',
value: 'Mrs',
},
{
label: 'Miss',
value: 'Miss',
},
{
label: 'Dr',
value: 'Dr',
},
],
},
{
name: 'yesNo',
label: '尊稱',
type: 'radio',
options: [
{
label: 'Yes',
value: 'Yes',
},
{
label: 'no',
value: 'no',
},
],
},
];
function App() {
const onSubmit = (data) => console.log(data);
const onSubmit1 = (data) => console.log(data);
return (
<>
<Form formFields={formFields} onSubmit={onSubmit} />
<Form formFields={formFields} onSubmit={onSubmit1} />
</>
);
}
export default App;
```
### Form.tsx
```
import React from 'react';
import { useForm } from 'react-hook-form';
import Field from './Field';
const LoopForm = ({ onSubmit, formFields }) => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
{formFields.map((field) => (
<React.Fragment key={field.name}>
<label htmlFor={field.name}>{field.label}</label>
<Field field={field} register={register} />
{errors[field.name] && <>{errors[field.name]?.message}</>}
</React.Fragment>
))}
<button type='submit'>Submit</button>
</form>
);
};
export default LoopForm;
```
### App.tsx
```
import AddTodo from './components/AddTodo';
import TodoList from './components/TodoList';
const App = () => {
return (
<div>
<AddTodo />
<TodoList />
</div>
);
};
export default App;
```
### TodoList.tsx
```
import { useQuery } from '@tanstack/react-query';
function fetchTodoList() {
return fetch('https://jsonplaceholder.typicode.com/todos').then((response) =>
response.json(),
);
}
function TodoList() {
const { data, isLoading, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error loading data</p>;
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
export default TodoList;
```
### AddTodo.tsx
```
import { useMutation } from '@tanstack/react-query';
function addTodo(newTodo) {
return fetch('https://jsonplaceholder.typicode.com/todos', {
method: 'POST',
body: JSON.stringify(newTodo),
headers: {
'Content-Type': 'application/json',
},
}).then((response) => response.json());
}
function AddTodo() {
const mutation = useMutation({
mutationFn: addTodo,
onSuccess: () => {
console.log('work');
},
});
return (
<button
onClick={() => {
mutation.mutate({ title: 'New Todo', completed: false });
}}
>
Add Todo
</button>
);
}
export default AddTodo;
```
