Web Frontend Learning
Contents
最近使用 tauri 开发一个小工具,学了一些前端知识,也能用antd开发一个有几页的工具了。 下面的叙述未必正确,毕竟我不是一个前端工程师。
工具选择
- React (antd)
- Typescript
- bun + vite
一些简单的规则
- interface 和 type 都可以用来定义接口类型,一般用 type
- ?.[0] 可以用来访问可能不存在的index,和?.member类似
- !号可以用来assert一个变量不为空,例如 user!
- React.FC已不再推荐
- 组件一般使用 const Xxx = () => {} 的形式
- useXxx 一般直接使用 function useXxx() {} 的形式
react或库的使用
axios
import axios from 'axios'
export const API = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL
});
API.interceptors.request.use((cfg) => {
const t = localStorage.getItem('token');
if (t && cfg.headers) {
cfg.headers.Authorization = `Bear ${t}`;
}
return cfg
})
要想 import.meta.env.VITE_API_BASE_URL 编辑时提示,应修改 vite-env.d.ts,添加以下内容:
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
自定义hook
自定义hook就是一个函数,是为了封装逻辑,复用复杂的逻辑。注意每个使用了这个hook的组件,都会存储一份hook内部的状态。 对于需要全局共享的状态,可以使用context。
export function useXXX() {}
自定义context
import {createContext, ReactNode, useContext, useEffect, useState} from 'react';
interface AuthContextValue {
user: User | null;
loading: boolean,
login: (username: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export const AuthProvider = ({children}: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const login = async (username: string, password: string) => {
const {data} = await API.post<AuthResult>('/api/v1/auth', {username, password, auto_login: true})
localStorage.setItem('token', data.token);
try {
const {data} = await API.get<AuthUser>('/api/v1/user')
setUser(data)
} catch (e) {
localStorage.removeItem('token');
throw e
}
};
const logout = () => {
setUser(null);
localStorage.removeItem('token');
};
const check = async () => {
try {
const {data} = await API.get<AuthUser>('/api/v1/user')
setUser(data)
} catch (e) {
console.error(e)
setUser(null)
localStorage.removeItem('token');
} finally {
setLoading(false)
}
}
useEffect(() => {
const t = localStorage.getItem('token');
if (t) {
void check();
} else {
setLoading(false);
}
}, [])
const value: AuthContextValue = {
user,
loading,
login,
logout,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth 必须在 AuthProvider 内使用');
}
return context;
};
export default AuthContext;
import {ReactNode} from "react";
import { Navigate } from 'react-router-dom';
import { useAuth } from './AuthContext';
interface ProtectedRouteProps {
children: ReactNode;
}
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
const { user, loading } = useAuth();
if (loading) return <div>Loading auth...</div>;
return user ? children : <Navigate to="/login" replace />;
}
export default ProtectedRoute;
然后就可以这样包装一下组件,使得登录后才能访问。
const HomePage = () => {
return (
<ProtectedRoute>
<AuthenticatedHomePage/>
</ProtectedRoute>
);
};
vite-plugin-pages
在src/pages文件夹下创建文件,就会自动生成路由,然后可以在函数中这样写来实现跳转
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
navigate('/xxx');
也可以使用 Navigate 组件来实现跳转,如
import { Navigate } from 'react-router-dom';
return <Navigate to="/xxx" replace />;
其中 replace 表示是否替换当前路由,可以防止浏览器的回退按钮,返回到上一个页面。
vite-plugin-pages 需要按官方文档配置一下,才能正确生成路由。
import { StrictMode, Suspense } from 'react'
import { createRoot } from 'react-dom/client'
import {
BrowserRouter,
useRoutes,
} from 'react-router-dom'
import routes from '~react-pages'
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
{useRoutes(routes)}
</Suspense>
)
}
const app = createRoot(document.getElementById('root')!)
app.render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
)
// vite-env.d.ts
/// <reference types="vite-plugin-pages/client-react" />
注意:一定要修改 vite-env.d.ts,否则就会 tsc 会报错!! vite-plugin-pages 没法自动生成带路由守卫的路由,但可以在生成之后,修改生成的 routes 对象。
zustand
对于全局,并且频繁改变的状态,使用zustand性能更高,对于不怎么改变的配置、主题等,可使用useContext
xstate
状态机是一个好东西,这个类似于 rust 的 smlang::statemachine,写起来略有一点不爽,但思想基本是一样的。
setup中提供配置,包括
- context和event的类型
- delays常数的定义
- guards的定义,guard可以访问context和对应的event
- actions定义,action可以访问context和对应的event
- actors的类型定义
- 等
createMachine主要提供
- 初始状态
- context的初始值
- 状态转换表
组件中使用 useMachine,返回的第3值是actorRef(即状态机自己的ref),可以使用useSelector将 context中的某个成员包装成reative的变量