現代 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; ``` ![無標題-2025-10-25-0933](https://hackmd.io/_uploads/Bk0uA3YAlg.png)
{"description":"現代 Web 開發實戰API 開發與前端整合班第01期","title":"大綱","contributors":"[{\"id\":\"a3caf6ca-a906-4b69-8f87-53a22980ce37\",\"add\":14365,\"del\":14897,\"latestUpdatedAt\":1761959629581}]"}
Expand menu