學(xué)習(xí)內(nèi)容來源:React + React Hook + TS 最佳實(shí)踐-慕課網(wǎng)
相對原教程,我在學(xué)習(xí)開始時(shí)(2023.03)采用的是當(dāng)前最新版本:
項(xiàng) | 版本 |
---|---|
react & react-dom | ^18.2.0 |
react-router & react-router-dom | ^6.11.2 |
antd | ^4.24.8 |
@commitlint/cli & @commitlint/config-conventional | ^17.4.4 |
eslint-config-prettier | ^8.6.0 |
husky | ^8.0.3 |
lint-staged | ^13.1.2 |
prettier | 2.8.4 |
json-server | 0.17.2 |
craco-less | ^2.0.0 |
@craco/craco | ^7.1.0 |
qs | ^6.11.0 |
dayjs | ^1.11.7 |
react-helmet | ^6.1.0 |
@types/react-helmet | ^6.1.6 |
react-query | ^6.1.0 |
@welldone-software/why-did-you-render | ^7.0.1 |
@emotion/react & @emotion/styled | ^11.10.6 |
具體配置、操作和內(nèi)容會(huì)有差異,“坑”也會(huì)有所不同。。。
一、項(xiàng)目起航:項(xiàng)目初始化與配置
- 【實(shí)戰(zhàn)】 一、項(xiàng)目起航:項(xiàng)目初始化與配置 —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級項(xiàng)目(一)
二、React 與 Hook 應(yīng)用:實(shí)現(xiàn)項(xiàng)目列表
- 【實(shí)戰(zhàn)】 二、React 與 Hook 應(yīng)用:實(shí)現(xiàn)項(xiàng)目列表 —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級項(xiàng)目(二)
三、TS 應(yīng)用:JS神助攻 - 強(qiáng)類型
- 【實(shí)戰(zhàn)】 三、TS 應(yīng)用:JS神助攻 - 強(qiáng)類型 —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級項(xiàng)目(三)
四、JWT、用戶認(rèn)證與異步請求
1.login
- 新建文件:
src\screens\login\index.tsx
:
import { FormEvent } from "react";
const apiUrl = process.env.REACT_APP_API_URL;
export const Login = () => {
const login = (param: { username: string; password: string }) => {
fetch(`${apiUrl}/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(param),
}).then(async (res) => {
if (res.ok) {
}
});
};
// HTMLFormElement extends Element (子類型繼承性兼容所有父類型)(鴨子類型:duck typing: 面向接口編程 而非 面向?qū)ο缶幊?
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const username = (event.currentTarget.elements[0] as HTMLFormElement).value;
const password = (event.currentTarget.elements[1] as HTMLFormElement).value;
login({ username, password });
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="username">用戶名</label>
<input type="text" id="username" />
</div>
<div>
<label htmlFor="password">密碼</label>
<input type="password" id="password" />
</div>
<button type="submit">登錄</button>
</form>
);
};
- 在
src\App.tsx
中引入:
import "./App.css";
import { Login } from "screens/login";
function App() {
return (
<div className="App">
<Login />
</div>
);
}
export default App;
目前頁面點(diǎn)擊登錄 404,下一步配置 json-server
中間件,使其可以模擬 非 restful 接口
2.middleware of json-server
- 新建文件:
__json_server_mock__\middleware.js
:
module.exports = (req, res, next) => {
if (req.method === "POST" && req.path === "/login") {
if (req.body.username === "user" && req.body.password === "123") {
return res.status(200).json({
user: {
token: "token123",
},
});
} else {
return res.status(400).json({ message: "用戶名或者密碼錯(cuò)誤" });
}
}
next();
};
- 配置
package.json
中json-server
的script
:
"json-server": "json-server __json_server_mock__/db.json -w -p 3001 --middlewares ./__json_server_mock__/middleware.js"
- 配置完后重新啟動(dòng)
json-server
,輸入中間件預(yù)置用戶名和密碼即可正常訪問(200),否則(400:bad request)
3.jira-dev-tool(imooc-jira-tool)
jira-dev-tool - npm
安裝
- 首先確認(rèn) git 工作區(qū) clean,安裝 jira-dev-tool(imooc-jira-tool)
npx imooc-jira-tool
- 引入到
src\index.tsx
import { loadDevTools } from "jira-dev-tool";
loadDevTools(() => {
ReactDOM.render(
<React.StrictMode>
<AppProviders>
<App />
</AppProviders>
</React.StrictMode>,
document.getElementById("root")
);
});
這一步后頁面會(huì)多出一個(gè)“齒輪”,點(diǎn)擊即可使用 jira-dev-tool
問題
安裝 jira-dev-tool(imooc-jira-tool)后啟動(dòng)項(xiàng)目聯(lián)調(diào)可能會(huì)出現(xiàn)的問題
問題一
- 報(bào)錯(cuò):
request (TypeError: Failed to fetch). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.
- 解決:
npx msw init ./public/ --save
問題二
由于 jira-dev-tool 已經(jīng)兩年沒有更新了,且依賴 react@“^16.0.0”, 若要繼續(xù)使用,在 npm i 時(shí)會(huì)有如下報(bào)錯(cuò):
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: jira@0.1.0
npm ERR! Found: react@18.2.0
npm ERR! node_modules/react
npm ERR! react@"^18.2.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.0.0" from jira-dev-tool@1.7.61
npm ERR! node_modules/jira-dev-tool
npm ERR! jira-dev-tool@"^1.7.61" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR!
npm ERR! For a full report see:
npm ERR! C:\...\npm-cache\_logs\2023-03-08T09_11_24_998Z-eresolve-report.txt
npm ERR! A complete log of this run can be found in:
npm ERR! C:\...\npm-cache\_logs\2023-03-08T09_11_24_998Z-debug-0.log
解決方案一:
- 刪掉文件 yarn.lock,以及package.json 中的
"jira-dev-tool": "^1.7.61",
部分,jira-dev-tool 手動(dòng)安裝
解決方案二(推薦)
- 使用
yarn
代替npm i
使用
- 開發(fā)者工具用 MSW 以 Service Worker 為原理實(shí)現(xiàn)了"分布式后端"
- 后端邏輯處理后,以localStorage為數(shù)據(jù)庫進(jìn)行增刪改查操作
- 瀏覽器上安裝了一個(gè)獨(dú)立的后端服務(wù)和數(shù)據(jù)庫,再也不受任何中心化服務(wù)的影響 點(diǎn)擊’清空數(shù)據(jù)庫’便可以重置后端服務(wù)
- 可以精準(zhǔn)地控制 HTTP請求的時(shí)間、失敗概率、失敗規(guī)則
- Service Worker + localStorage雖然本質(zhì)上與傳統(tǒng)后端服務(wù)并不同,但絲毫不會(huì)影響前端開發(fā)
其他具體操作可見文檔以及接下來的操作:jira-dev-tool - npm
- Service Worker API - Web API 接口參考 | MDN
安裝好后進(jìn)入/login
,請求login接口,可以看到狀態(tài)碼后帶有(from service worker)字樣即成功連接:
開發(fā)工具控制臺第一個(gè)tab頁設(shè)置請求最短時(shí)間、請求失敗比例:
開發(fā)工具控制臺將/login
添加到異步請求失敗設(shè)置中,狀態(tài)碼 400 變?yōu)?500,提示:“請求失敗,請檢查 jira-dev-tool 的設(shè)置”:
4.JWT原理與auth-provider實(shí)現(xiàn)
注冊一個(gè)新用戶
- 修改:
src\screens\login\index.tsx
:- 調(diào)用接口
login
改為register
; - 按鈕 登錄 改為 注冊
- 調(diào)用接口
注冊一個(gè)新用戶 jira(密碼:jira),接口返回:
{
"user": {
"id": 2087569429,
"name": "jira",
"token": "MjA4NzU2OTQyOQ=="
}
}
token 即是 JWT(JSON Web Tokens) 的產(chǎn)物
- JSON Web Tokens - jwt.io
auth-provider
修改 src\screens\ProjectList\components\SearchPanel.tsx
,為 User
新增 token
:
export interface User {
id: string;
name: string;
email: string;
title: string;
organization: string;
token: string;
}
...
新建 src\auth-provider.ts
:
模擬第三方服務(wù)
// 在真實(shí)環(huán)境中,如果使用了 firebase 這種第三方 auth 服務(wù)的話,本文件不需要開發(fā)者開發(fā)
import { User } from "screens/ProjectList/components/SearchPanel"
const localStorageKey = '__auth_provider_token__'
const apiUrl = process.env.REACT_APP_API_URL;
export const getToken = () => window.localStorage.getItem(localStorageKey)
export const handleUserResponse = ({user} : { user: User }) => {
window.localStorage.setItem(localStorageKey, user.token || '')
return user
}
export const login = (data: { username: string, password: string }) => {
return fetch(`${apiUrl}/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then(async (res) => {
if (res.ok) {
return handleUserResponse(await res.json())
} else {
return Promise.reject(data)
}
});
}
export const register = (data: { username: string, password: string }) => {
return fetch(`${apiUrl}/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then(async (res) => {
if (res.ok) {
return handleUserResponse(await res.json())
} else {
return Promise.reject(data)
}
});
}
export const logout = async () => window.localStorage.removeItem(localStorageKey)
細(xì)節(jié)點(diǎn):
- 函數(shù)定義時(shí),值前添加 async 使其返回一個(gè) Promise 對象
- 回調(diào)函數(shù)入?yún)⒑突卣{(diào)函數(shù)內(nèi)有且只有一個(gè)函數(shù)調(diào)用且它的入?yún)⑴c回調(diào)函數(shù)入?yún)⒁恢?,該回調(diào)函數(shù)可直接簡寫為其內(nèi)部的函數(shù)調(diào)用且不帶參(這是函數(shù)式編程-PointFree的一種應(yīng)用):
- const login = (form: AuthForm) => auth.login(form).then(user => setUser(user))
- const login = (form: AuthForm) => auth.login(form).then(setUser)
【筆記】函數(shù)式編程——PointFree
5.useContext(user,login,register,logout)
新建 src\context\auth-context.tsx
:
import React, { ReactNode, useState } from "react"
import * as auth from 'auth-provider'
import { User } from "screens/ProjectList/components/SearchPanel"
interface AuthForm {
username: string,
password: string
}
const AuthContext = React.createContext<{
user: User | null,
login: (form : AuthForm) => Promise<void>,
register: (form : AuthForm) => Promise<void>,
logout: () => Promise<void>,
} | undefined>(undefined)
AuthContext.displayName = 'AuthContext'
export const AuthProvider = ({children}:{children: ReactNode}) => {
// 這里要考慮到初始值的類型與后續(xù)值類型,取并組成一個(gè)泛型
const [user, setUser] = useState<User | null>(null)
const login = (form: AuthForm) => auth.login(form).then(user => setUser(user))
const register = (form: AuthForm) => auth.register(form).then(user => setUser(user))
const logout = () => auth.logout().then(() => setUser(null))
return <AuthContext.Provider children={children} value={{ user, login, register, logout }}/>
}
export const useAuth = () => {
const context = React.useContext(AuthContext)
if (!context) {
throw new Error('useAuth 必須在 AuthProvider 中使用')
}
return context
}
新建 src\context\index.tsx
:
import { ReactNode } from "react";
import { AuthProvider } from "./auth-context";
export const AppProvider = ({children}:{children: ReactNode}) => {
return <AuthProvider>
{children}
</AuthProvider>
}
在項(xiàng)目中使用 AppProvider
,修改 src\index.tsx
:
import { AppProvider } from "context";
...
loadDevTools(() => {
root.render(
// <React.StrictMode>
<AppProvider>
<App />
</AppProvider>
// </React.StrictMode>
);
});
...
修改 src\screens\login\index.tsx
,調(diào)用 useAuth
中的 login
,并使用之前注冊的賬號 jira(jira)
驗(yàn)證:文章來源:http://www.zghlxwxcb.cn/news/detail-498416.html
import { useAuth } from "context/auth-context";
import { FormEvent } from "react";
export const Login = () => {
const {login, user} = useAuth()
// HTMLFormElement extends Element (子類型繼承性兼容所有父類型)(鴨子類型:duck typing: 面向接口編程 而非 面向?qū)ο缶幊?
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {...};
return (
<form onSubmit={handleSubmit}>
<div>
{
user ? <div>
登錄成功,用戶名{user?.name}
</div> : null
}
</div>
<div>
<label htmlFor="username">用戶名</label>
<input type="text" id="username" />
</div>
<div>
<label htmlFor="password">密碼</label>
<input type="password" id="password" />
</div>
<button type="submit">登錄</button>
</form>
);
};
部分引用筆記還在草稿階段,敬請期待。。。文章來源地址http://www.zghlxwxcb.cn/news/detail-498416.html
到了這里,關(guān)于【實(shí)戰(zhàn)】 JWT、用戶認(rèn)證與異步請求(1) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級項(xiàng)目(四)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!