學(xué)習(xí)內(nèi)容來(lái)源:React + React Hook + TS 最佳實(shí)踐-慕課網(wǎng)
相對(duì)原教程,我在學(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è)級(jí)項(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è)級(jí)項(xiàng)目(二)
三、TS 應(yīng)用:JS神助攻 - 強(qiáng)類型
- 【實(shí)戰(zhàn)】三、 TS 應(yīng)用:JS神助攻 - 強(qiáng)類型 —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(三)
四、JWT、用戶認(rèn)證與異步請(qǐng)求
- 【實(shí)戰(zhàn)】四、 JWT、用戶認(rèn)證與異步請(qǐng)求(上) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(四)
- 【實(shí)戰(zhàn)】四、 JWT、用戶認(rèn)證與異步請(qǐng)求(下) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(五)
五、CSS 其實(shí)很簡(jiǎn)單 - 用 CSS-in-JS 添加樣式
- 【實(shí)戰(zhàn)】 五、CSS 其實(shí)很簡(jiǎn)單 - 用 CSS-in-JS 添加樣式(上) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(六)
- 【實(shí)戰(zhàn)】 五、CSS 其實(shí)很簡(jiǎn)單 - 用 CSS-in-JS 添加樣式(下) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(七)
六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理
1~2
- 【實(shí)戰(zhàn)】 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理(上) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(八)
3.登錄注冊(cè)頁(yè)面 Loading 和 Error 狀態(tài)處理,與 Event Loop 詳解
列表頁(yè)的 異步狀態(tài) 弄完,接下來(lái)是登錄注冊(cè)頁(yè)了
修改 src\unauthenticated-app\index.tsx
(新增 error
狀態(tài)處理,將 error
j監(jiān)聽操作 交給 登錄注冊(cè)頁(yè)):
...
import { Card, Button, Divider, Typography } from "antd";
...
export const UnauthenticatedApp = () => {
...
const [error, setError] = useState<Error | null>(null);
return (
<Container>
...
<ShadowCard>
<Title>{isRegister ? "請(qǐng)注冊(cè)" : "請(qǐng)登錄"}</Title>
{ error ? <Typography.Text type="danger">{error.message}</Typography.Text> : null }
{isRegister ? <Register onError={setError}/> : <Login onError={setError}/>}
<Divider />
...
</ShadowCard>
</Container>
);
};
...
修改 src\unauthenticated-app\login.tsx
(傳入 onError
并在異步操作后 catch
中使用):
...
export const Login = ({onError}: { onError: (error: Error) => void }) => {
...
const handleSubmit = (values: { username: string; password: string }) => {
login(values).catch(e => onError(e))
};
...
};
...
同理修改 src\unauthenticated-app\register.tsx
:
...
export const Register = ({onError}: { onError: (error: Error) => void }) => {
...
const handleSubmit = (values: { username: string; password: string }) => {
register(values).catch(e => onError(e))
};
...
};
...
使用非預(yù)設(shè)用戶名密碼檢驗(yàn):沒反應(yīng)。。。但是控制臺(tái)打印出了剛輸入的用戶名和密碼。。。
通過登錄的調(diào)用鏈可以找到 導(dǎo)致這個(gè)問題的原因:src\auth-provider.ts
-
!res.ok
時(shí),返回了Promise.reject(data)
,而data
是請(qǐng)求入?yún)?,這顯然不是預(yù)想的效果(注冊(cè)同理),修改這部分為Promise.reject(await res.json())
修改后再次檢驗(yàn),成了!
Promise.catch
固然好用,但接下來(lái)?yè)Q個(gè)思路,使用 try..catch
并引出 Event Loop
。
先修改 src\unauthenticated-app\login.tsx
試試水:
...
export const Login = ({onError}: { onError: (error: Error) => void }) => {
...
const handleSubmit = (values: { username: string; password: string }) => {
try {
// login(values).catch(e => onError(e))
login(values);
} catch(e: Error | any) {
onError(e)
}
};
...
};
...
控制臺(tái)輸出正常,但是界面沒有效果。。。
問題出在 login 是異步操作,程序中會(huì)優(yōu)先執(zhí)行同步操作,然后才會(huì)異步操作,所以 onError 優(yōu)先執(zhí)行,并沒有拿到后端返回的報(bào)錯(cuò)信息
再次修改 src\unauthenticated-app\login.tsx
(使用 async await 處理異步操作):
...
export const Login = ({onError}: { onError: (error: Error) => void }) => {
...
const handleSubmit = async (values: { username: string; password: string }) => {
try {
// login(values).catch(e => onError(e))
await login(values);
} catch(e: Error | any) {
onError(e)
}
};
...
};
...
這樣便正常啦!
接下來(lái)給注冊(cè)頁(yè)新增確認(rèn)密碼功能
修改 src\unauthenticated-app\register.tsx
(新增確認(rèn)密碼的 Form.Item
和 相關(guān)處理邏輯):
...
export const Register = ({onError}: { onError: (error: Error) => void }) => {
const { register, user } = useAuth();
const handleSubmit = ({ cpassword, ...values }: { username: string, password: string, cpassword: string }) => {
if (cpassword === values.password) {
register(values).catch(e => onError(e));
} else {
onError(new Error('請(qǐng)確認(rèn)兩次的輸入密碼相同'))
return
}
};
return (
<Form onFinish={handleSubmit}>
<Form.Item
name="username"
rules={[{ required: true, message: "請(qǐng)輸入用戶名" }]}
>
<Input placeholder="用戶名" type="text" id="username" />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: "請(qǐng)輸入密碼" }]}
>
<Input placeholder="密碼" type="password" id="password" />
</Form.Item>
<Form.Item
name="cpassword"
rules={[{ required: true, message: "請(qǐng)確認(rèn)密碼" }]}
>
<Input placeholder="確認(rèn)密碼" type="password" id="cpassword" />
</Form.Item>
<Form.Item>
<LongButton htmlType="submit" type="primary">
注冊(cè)
</LongButton>
</Form.Item>
</Form>
);
};
再接著為 登錄注冊(cè)頁(yè) 添加異步狀態(tài) Loading
的處理:
...
import { useAsync } from "utils/use-async";
export const Login = ({onError}: { onError: (error: Error) => void }) => {
const { login, user } = useAuth();
const { run, isLoading } = useAsync()
const handleSubmit = async (values: { username: string; password: string }) => {
try {
// login(values).catch(e => onError(e))
await run(login(values))
} catch(e: Error | any) {
onError(e)
}
};
return (
<Form onFinish={handleSubmit}>
...
<Form.Item>
<LongButton loading={isLoading} htmlType="submit" type="primary">
登錄
</LongButton>
</Form.Item>
</Form>
);
};
...
檢驗(yàn)一下,沒有效果,但是控制臺(tái)拋出 400 錯(cuò)誤了,排查一下
-
try..catch
中的onError
沒接收到,唯一的變數(shù)就是這個(gè)run
了 - 查看一下,果然報(bào)錯(cuò)被
run
內(nèi)部消化了,沒有正常拋出(將catch
到的error throw
或是用Promise.reject
包裹返回都是可以的,建議使用后者)
修改 src\utils\use-async.ts
:
...
export const useAsync = <D>(initialState?: State<D>) => {
...
// run 來(lái)觸發(fā)異步請(qǐng)求
const run = (promise: Promise<D>) => {
...
return promise
.then(...)
.catch((error) => {
// catch 會(huì)消化異常,如果不主動(dòng)拋出,外面是接收不到異常的
setError(error);
// return error; // 原代碼
// throw error;
return Promise.reject(error);
});
};
...
};
檢驗(yàn)一下,正常 catch
并 展示報(bào)錯(cuò)信息
- try…catch only works for runtime errors (try…catch 只能處理有效代碼之中的異常)
- try…catch works synchronously(try…catch 只能處理同步代碼之中的異常)
問題是解決了,但這樣 try…catch 還是有些拖泥帶水的感覺,繼續(xù)優(yōu)化:
修改 src\utils\use-async.ts
(增加是否拋出異常的配置,來(lái)合理化邏輯):
...
const defaultConfig = {
throwOnError: false
}
export const useAsync = <D>(initialState?: State<D>, initialConfig?: typeof defaultConfig) => {
const config = {...defaultConfig, ...initialConfig}
...
// run 來(lái)觸發(fā)異步請(qǐng)求
const run = (promise: Promise<D>) => {
...
return promise
.then((data) => {
setData(data);
return data;
})
.catch((error) => {
// catch 會(huì)消化異常,如果不主動(dòng)拋出,外面是接收不到異常的
setError(error);
return config.throwOnError ? Promise.reject(error) : error;
});
};
...
};
修改 src\unauthenticated-app\login.tsx
(傳入 { throwOnError: true }
):
...
export const Login = ({onError}: { onError: (error: Error) => void }) => {
...
const { run, isLoading } = useAsync(undefined, { throwOnError: true })
...
};
...
同理修改 src\unauthenticated-app\register.tsx
:
...
export const Register = ({onError}: { onError: (error: Error) => void }) => {
...
const { run, isLoading } = useAsync(undefined, { throwOnError: true })
const handleSubmit = async ({ cpassword, ...values }: { username: string, password: string, cpassword: string }) => {
if (cpassword === values.password) {
try {
await run(register(values))
} catch (e: Error | any) {
onError(e)
}
} else {
onError(new Error('請(qǐng)確認(rèn)兩次的輸入密碼相同'))
return
}
};
return (
<Form onFinish={handleSubmit}>
...
<Form.Item>
<LongButton loading={isLoading} htmlType="submit" type="primary">
注冊(cè)
</LongButton>
</Form.Item>
</Form>
);
};
最后收尾,修改 src\unauthenticated-app\index.tsx
(切換登錄和注冊(cè)時(shí),error
清空):
...
export const UnauthenticatedApp = () => {
const [isRegister, setIsRegister] = useState(false);
const [error, setError] = useState<Error | null>(null);
return (
<Container>
...
<ShadowCard>
...
<Button type="link" onClick={() => { setIsRegister(!isRegister); setError(null) }}>
切換到{isRegister ? "已經(jīng)有賬號(hào)了?直接登錄" : "沒有賬號(hào)?注冊(cè)新賬號(hào)"}
</Button>
</ShadowCard>
</Container>
);
};
...
檢驗(yàn)效果,完美!
拓展學(xué)習(xí)(引用自:高薪之路—前端面試精選集-慕課專欄)
js 是單線程的,異步在 js 中是反直覺的存在
判斷打印順序:
console.log('script start')
setTimeout(function(){
console.log('setTimeout');
},0);
new Promise(function(resolve){
console.log('promise1');
resolve();
console.log('promise2');
}).then(function(){
console.log('promise then');
});
console log('script end');
打印順序:
script start
promise1
promise2
script end
promise then
setTimeout
因?yàn)镴avaScript中有2種任務(wù):
- 宏任務(wù)(macro-task):同步 script(整體代碼),setTimeout 回調(diào)函數(shù),setlnterval 回調(diào)函數(shù),l/O,Ul rendering;
- 微任務(wù)(micro-task):process.nextTick,Promise 回調(diào)函數(shù), Object.observe,MutationObserver
其執(zhí)行的順序是這樣的:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-541164.html
- 首先 JavaScript 引擎會(huì)執(zhí)行一個(gè)宏任務(wù),注意這個(gè)宏任務(wù)一般是指主干代碼本身,也就是目前的同步代碼;
- 執(zhí)行過程中如果遇到微任務(wù),就把它添加到微任務(wù)任務(wù)隊(duì)列中;
- 宏任務(wù)執(zhí)行完成后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的微任務(wù),直到微任務(wù)隊(duì)列被清空;
- 微任務(wù)執(zhí)行完成后,開始執(zhí)行下一個(gè)宏任務(wù);
- 如此循環(huán)往復(fù),直到宏任務(wù)和微任務(wù)被清空。
部分引用筆記還在草稿階段,敬請(qǐng)期待。。。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-541164.html
到了這里,關(guān)于【實(shí)戰(zhàn)】 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理(中) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(九)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!