倉庫進(jìn)銷存管理軟件免費(fèi)版搜索引擎優(yōu)化分析
文章目錄
- 一、項(xiàng)目起航:項(xiàng)目初始化與配置
- 二、React 與 Hook 應(yīng)用:實(shí)現(xiàn)項(xiàng)目列表
- 三、TS 應(yīng)用:JS神助攻 - 強(qiáng)類型
- 四、JWT、用戶認(rèn)證與異步請求
- 五、CSS 其實(shí)很簡單 - 用 CSS-in-JS 添加樣式
- 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理
- 七、Hook,路由,與 URL 狀態(tài)管理
- 八、用戶選擇器與項(xiàng)目編輯功能
- 九、深入React 狀態(tài)管理與Redux機(jī)制
- 1&2
- 3&4
- 5~8
- 9&10
- 11.用redux-thunk管理登錄狀態(tài)
學(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)容會有差異,“坑”也會有所不同。。。
一、項(xiàng)目起航:項(xiàng)目初始化與配置
- 一、項(xiàng)目起航:項(xiàng)目初始化與配置
二、React 與 Hook 應(yīng)用:實(shí)現(xiàn)項(xiàng)目列表
- 二、React 與 Hook 應(yīng)用:實(shí)現(xiàn)項(xiàng)目列表
三、TS 應(yīng)用:JS神助攻 - 強(qiáng)類型
- 三、 TS 應(yīng)用:JS神助攻 - 強(qiáng)類型
四、JWT、用戶認(rèn)證與異步請求
- 四、 JWT、用戶認(rèn)證與異步請求(上)
- 四、 JWT、用戶認(rèn)證與異步請求(下)
五、CSS 其實(shí)很簡單 - 用 CSS-in-JS 添加樣式
- 五、CSS 其實(shí)很簡單 - 用 CSS-in-JS 添加樣式(上)
- 五、CSS 其實(shí)很簡單 - 用 CSS-in-JS 添加樣式(下)
六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理
- 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理(上)
- 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理(中)
- 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理(下)
七、Hook,路由,與 URL 狀態(tài)管理
- 七、Hook,路由,與 URL 狀態(tài)管理(上)
- 七、Hook,路由,與 URL 狀態(tài)管理(中)
- 七、Hook,路由,與 URL 狀態(tài)管理(下)
八、用戶選擇器與項(xiàng)目編輯功能
- 八、用戶選擇器與項(xiàng)目編輯功能(上)
- 八、用戶選擇器與項(xiàng)目編輯功能(下)
九、深入React 狀態(tài)管理與Redux機(jī)制
1&2
- 九、深入React 狀態(tài)管理與Redux機(jī)制(一)
3&4
- 九、深入React 狀態(tài)管理與Redux機(jī)制(二)
5~8
- 九、深入React 狀態(tài)管理與Redux機(jī)制(三)
9&10
- 九、深入React 狀態(tài)管理與Redux機(jī)制(四)
11.用redux-thunk管理登錄狀態(tài)
既然模態(tài)框使用 redux
來管理了,而 redux
與 context
是競爭關(guān)系,那可以嘗試將之前管理登錄狀態(tài)的 context
改為 redux
新建 src\store\auth.slice.ts
:
import { User } from "screens/ProjectList/components/SearchPanel";
import { createSlice } from "@reduxjs/toolkit";
import * as auth from 'auth-provider'
import { AuthForm, initUser as _initUser } from "context/auth-context";
import { AppDispatch, RootState } from "store";interface State {user: User | null
}const initialState: State = {user: null
}export const authSlice = createSlice({name: 'auth',initialState,reducers: {setUser(state, action) {state.user = action.payload}}
})const { setUser } = authSlice.actionsexport const selectUser = (state: RootState) => state.auth.userexport const login = (form: AuthForm) => (dispatch: AppDispatch) => auth.login(form).then(user => dispatch(setUser(user)))
export const register = (form: AuthForm) => (dispatch: AppDispatch) => auth.register(form).then(user => dispatch(setUser(user)))
export const logout = () => (dispatch: AppDispatch) => auth.logout().then(() => dispatch(setUser(null)))
export const initUser = () => (dispatch: AppDispatch) => _initUser().then(user => dispatch(setUser(user)))
需要提前將
src\context\auth-context.tsx
中的interface AuthForm
和initUser
(視頻中對應(yīng)bootstrapUser
) 導(dǎo)出
最后在 src\store\index.ts
中統(tǒng)一注冊:
...
import { authSlice } from "./auth.slice";// 集中狀態(tài)注冊
export const rootReducer = {projectList: projectListSlice.reducer,auth: authSlice.reducer
};
...
接下來使用 redux
中的 auth
相關(guān)功能替換原有 context
提供的 auth
的功能
重構(gòu)需要找代碼上游中集中的一個(gè)點(diǎn):useAuth
修改 src\context\auth-context.tsx
:
+ import { ReactNode, useCallback } from "react";
...
+ import * as authStore from 'store/auth.slice'
+ import { useDispatch, useSelector } from "react-redux";
+ import { AppDispatch } from "store";- interface AuthForm {
+ export interface AuthForm {username: string;password: string;
}- const initUser = async () => {
+ export const initUser = async () => {let user = null;const token = auth.getToken();if (token) {// 由于要自定義 token ,這里使用 http 而非 useHttpconst data = await http("me", { token });user = data.user;}return user
};- 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 {
- data: user,error,isLoading,isReady,isError,run,
- setData: setUser,} = useAsync<User | null>();
+ // const dispatch: (...args: unknown[]) => Promise<User> = useDispatch()
+ // const dispatch: AppDispatch = useDispatch()
+
+ // 這種寫法雖然消除了代碼中的報(bào)錯(cuò),但是控制臺會有報(bào)錯(cuò)
+ // useMount(async () => run(dispatch(await initUser())));
+ // useMount(() => run(dispatch(initUser())));
+ // 還原后登錄保持功能已經(jīng)是不好使的了useMount(() => run(initUser()));- const login = (form: AuthForm) => auth.login(form).then(setUser);
- const register = (form: AuthForm) => auth.register(form).then(setUser);
- const logout = () => auth.logout().then(() => setUser(null));if (isReady || isLoading) {return <FullPageLoading />;}if (isError) {return <FullPageErrorFallback error={error} />;}- return (
- <AuthContext.Provider
- children={children}
- value={{ user, login, register, logout }}
- />
- );
+ return <div>
+ { children }
+ </div>
};export const useAuth = () => {
- const context = React.useContext(AuthContext);
- if (!context) {
- throw new Error("useAuth 必須在 AuthProvider 中使用");
- }
- return context;
+ // 這種寫法有報(bào)錯(cuò)
+ // const dispatch: (...args: unknown[]) => Promise<User> = useDispatch()
+ const dispatch: AppDispatch = useDispatch()
+ const user = useSelector(authStore.selectUser)
+ const login = useCallback((form: AuthForm) => dispatch(authStore.login(form)), [dispatch])
+ const register = useCallback((form: AuthForm) => dispatch(authStore.register(form)), [dispatch])
+ const logout = useCallback(() => dispatch(authStore.logout()), [dispatch])
+ // const login = useCallback((form: AuthForm) => authStore.login(form), [])
+ // const register = useCallback((form: AuthForm) => authStore.register(form), [])
+ // const logout = useCallback(() => authStore.logout(), [])
+ // login({ username: '123', password: '123' }).then()
+ return { user, login, register, logout };
};
這種寫法會報(bào)錯(cuò) const dispatch: (...args: unknown[]) => Promise<User> = useDispatch()
:
不能將類型“Dispatch<AnyAction>”分配給類型“(...args: unknown[]) => Promise<User>”。參數(shù)“action”和“args” 的類型不兼容。不能將類型“unknown”分配給類型“AnyAction”
替換為 const dispatch: AppDispatch = useDispatch()
后正常
useMount(async () => run(dispatch(await initUser())))
中不加 async..await
的話會有如下報(bào)錯(cuò)提示:
沒有與此調(diào)用匹配的重載。第 1 個(gè)重載(共 3 個(gè)),“(thunkAction: ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>): Promise<User | null>”,出現(xiàn)以下錯(cuò)誤。類型“Promise<any>”的參數(shù)不能賦給類型“ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>”的參數(shù)。類型“Promise<any>”提供的內(nèi)容與簽名“(dispatch: ThunkDispatch<{ projectList: State; auth: State; }, undefined, AnyAction>, getState: () => { projectList: State; auth: State; }, extraArgument: undefined): Promise<...>”不匹配。第 2 個(gè)重載(共 3 個(gè)),“(action: AnyAction): AnyAction”,出現(xiàn)以下錯(cuò)誤。類型“Promise<any>”的參數(shù)不能賦給類型“AnyAction”的參數(shù)。類型 "Promise<any>" 中缺少屬性 "type",但類型 "AnyAction" 中需要該屬性。第 3 個(gè)重載(共 3 個(gè)),“(action: AnyAction | ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>): AnyAction | Promise<...>”,出現(xiàn)以下錯(cuò)誤。類型“Promise<any>”的參數(shù)不能賦給類型“AnyAction | ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>”的參數(shù)。不能將類型“Promise<any>”分配給類型“AnyAction”。ts(2769)
auth-context.tsx(41, 31): 是否忘記使用 "await"?
index.d.ts(19, 3): 在此處聲明了 "type"。
auth-context.tsx(41, 31): 是否忘記使用 "await"?
auth-context.tsx(41, 31): 是否忘記使用 "await"?
最終運(yùn)行結(jié)果:登錄,注冊,登出功能都正常,只有登錄保持不正常
部分引用筆記還在草稿階段,敬請期待。。。