一、创建react项目
1. vite创建react项目
npm create vite@latest
2. 安装react-router-dom路由
npm i react-router-dom
3. 安装状态管理库
这里可以使用redux
,但是个人觉得比较繁琐,于是使用的是一个类似于vue3
中的pinia
的一个库valtio
npm i valtio
4. 安装ui库
这里使用Ant Design
,但是这个库有点小问题,那就是样式中的px
不能被postcss-pxtorem
插件转换成rem
npm install antd --save
5. 安装axios
npm i axios
二、react-router-dom使用
1. 路由文件
// router.js
import { createHashRouter, Navigate } from "react-router-dom";
import Home from "@/views/Home";
import HomePage from "@/views/home/HomePage";
import ThreeDimensional from "@/views/home/ThreeDimensional";
import AuthRoute from "@/views/AuthRoute";
import Login from "@/views/Login";
const router = createHashRouter([
{
path: "/",
element: <AuthRoute />,
children: [
{
path: "home",
element: <Home />,
children: [
{
path: "homePage",
element: <HomePage />,
},
{
path: "ThreeDimensional",
element: <ThreeDimensional />,
},
{
path: "",
element: <Navigate to="homePage"></Navigate>,
},
],
},
{
path: "login",
element: <Login />,
},
{
path: "",
element: <Navigate to="home"></Navigate>,
},
],
},
]);
export default router;
在AuthRoute
页面中实现全局路由守卫,这个页面是/
对应的页面
// AuthRoute.tsx
import { useEffect } from 'react';
import { Outlet, useLocation,useNavigate} from 'react-router-dom';
const AuthRoute = () => {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
// 路由拦截处理逻辑
let token = localStorage.getItem('token');
if(!token){ //无token进入登录页
navigate("/login");
}else{
if(location.pathname === "/login"){ //有token不能跳转登录页,直接去首页
navigate("/")
}
}
}, [location.pathname]);
return <Outlet />;
};
export default AuthRoute;
App.tsx
做项目的入口
import { RouterProvider } from "react-router-dom";
import router from "./router";
function App() {
return (
<>
<RouterProvider router={router}></RouterProvider>
</>
);
}
export default App;
遗留问题:由于目前还不是很熟悉react这一套,有以下问题需要后期解决
- 如何实现类似于
vue-router
里面的meta
属性来给每个路由自定义参数 - 在路由跳转后,如何获取之前的路由地址
- 如何实现可暂停的路由跳转功能,类似于
vue-router
中的路由守卫中间件,执行next
方法才会跳转页面
三、valtio使用
// index.tsx
import { proxy } from "valtio";
export const gasListState:IGasListState= proxy({
gasList: [],
});
export function setGasList(list:IGasList[]) {
gasListState.gasList = list;
}
使用方法
import { useSnapshot } from "valtio";
import { gasListState } from "@/store/gasList";
let gastate = useSnapshot(gasListState); //通过useSnapshot方法包裹gasListState
四、hook使用
1. useCallback
useCallback
是一个允许你在多次渲染中缓存函数的 React Hook
//使用方式
const cachedFn = useCallback(fn, dependencies)
它会返回传入的fn
,如果dependencies
依赖项没有发生变化的时候,组件多次渲染也是返回缓存的fn,也就是说,cachedFn
的值是同一个值,注意它只是返回fn
,并不是调用fn
,需要手动调用cachedFn()
import { useState, useCallback, useEffect, useMemo } from "react";
export default function UseCallbackExample() {
console.log("我渲染了");
const [num, setNum] = useState(1);
// 使用 useCallback 来创建一个优化过的函数
const getDoubleNum = useCallback(() => {
console.log("获取双倍的 num");
return 2 * num;
}, []);
// const getDoubleNum = () => {
// console.log('获取双倍的 num');
// return 2 * num;
// };
// 模拟外部处理逻辑
useEffect(() => {
console.log("外部处理逻辑");
}, [getDoubleNum]);
return (
<div>
<p>当前数字: {num}</p>
<button onClick={() => setNum(num + 1)}>增加数字</button>
</div>
);
}
上面的代码,点击按钮会发现并没有打印useEffect
里面的“外部处理逻辑”,因为getDoubleNum
已经被缓存了,组件每次渲染getDoubleNum
都是同一个值,如果使用被注释的普通函数,就会发现每次都打印了“外部处理逻辑”
2. useMemo
vue中的计算属性,不多说
import { useState,useMemo } from "react";
export default function UseCallbackExample() {
const [num, setNum] = useState(1);
const [count, setCount] = useState(1);
const expensiveCalculation = useMemo(() => {
console.log("执行昂贵的计算...");
return count * 1000000;
}, [count]);
return (
<div>
<p>当前计算值: {expensiveCalculation}</p>
<button onClick={() => setNum(num + 1)}>增加数字</button>
</div>
);
}
3. useContext
useContext
是一个 React Hook,可以让你读取和订阅组件中的context
,主要用于组件通信,避免组件层级太深,一层一层往下传递的尴尬
import React, { createContext, useContext, useState } from "react";
// 创建一个 Context
const ThemeContext = createContext({});
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme, abc: 1 }}> //上面创建的context, value是要传递的值
{children}
</ThemeContext.Provider>
);
}
function Button() {
// 使用 useContext 获取 Context 中的数据
const { theme, setTheme } = useContext(ThemeContext); //获取祖先元素传递的值
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Switch to {theme === "light" ? "Dark" : "Light"} Theme
</button>
);
}
function App() {
return (
<>
<ThemeProvider>
<Button />
</ThemeProvider>
</>
);
}
export default App;
4. useImperativeHandle
类似于vue3中的defineExpose()
,用于暴露子组件指定的函数给父组件使用
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
// 子组件
const ChildComponent = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
if (inputRef.current) {
inputRef.current.focus();
}
},
}));
return (
<div>
<input ref={inputRef} type="text" placeholder="Focus me!" />
</div>
);
});
// 父组件
function ParentComponent() {
const childRef = useRef(null);
const handleButtonClick = () => {
if (childRef.current) {
childRef.current.focus(); // 调用子组件的方法
}
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleButtonClick}>Focus the input</button>
</div>
);
}
export default ParentComponent;
5. useRef
类似于vue3中的ref
,可以用来绑定数据和dom节点,通过.current
来获取绑定的值,和useState
的区别是,useRef
更新值不会触发更新,它会存储上一次的值,就是重新渲染也不会重置
6. useDeferredValue
useDeferredValue
是一个 React Hook,可以让你延迟更新 UI 的某些部分
import React, { useState, useDeferredValue, useEffect } from 'react';
export default function SearchInput() {
const [searchText, setSearchText] = useState('');
const deferredSearchText = useDeferredValue(searchText);
useEffect(() => {
console.log('Updating search text:', deferredSearchText);
}, [deferredSearchText]);
return (
<div>
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
<p>Current search text: {deferredSearchText}</p>
</div>
);
}
看起来和防抖挺像的,但是二者是有区别的
与防抖或节流不同,useDeferredValue
不需要选择任何固定延迟时间。如果用户的设备很快(比如性能强劲的笔记本电脑),延迟的重渲染几乎会立即发生并且不会被察觉。如果用户的设备较慢,那么列表会相应地“滞后”于输入,滞后的程度与设备的速度有关。
此外,与防抖或节流不同,useDeferredValue
执行的延迟重新渲染默认是可中断的。这意味着,如果 React 正在重新渲染一个大型列表,但用户进行了另一次键盘输入,React 会放弃该重新渲染,先处理键盘输入,然后再次开始在后台渲染。相比之下,防抖和节流仍会产生不顺畅的体验,因为它们是阻塞的:它们仅仅是将渲染阻塞键盘输入的时刻推迟了。
如果你要优化的工作不是在渲染期间发生的,那么防抖和节流仍然非常有用。例如,它们可以让你减少网络请求的次数。你也可以同时使用这些技术。
所以useDeferredValue
更适用于延迟更新页面渲染
7. useReducer
状态管理仓库,用法和vuex有一点相似
import { useReducer, useEffect, useDeferredValue } from 'react';
// 定义 action 类型
const SET_SEARCH_TEXT = 'SET_SEARCH_TEXT';
// 定义 reducer 函数
function reducer(state, action) {
console.log(state,action,"state");
switch (action.type) {
case SET_SEARCH_TEXT:
return { ...state, searchText: action.payload };
default:
return state;
}
}
// 定义初始状态
const initialState = { searchText: '123' };
function SearchInput() {
const [state, dispatch] = useReducer(reducer, initialState);
const deferredSearchText = useDeferredValue(state.searchText);
useEffect(() => {
console.log('Updating search text:', deferredSearchText);
}, [deferredSearchText]);
return (
<div>
<input
type="text"
value={state.searchText}
onChange={(e) => dispatch({ type: SET_SEARCH_TEXT, payload: e.target.value })}
/>
<p>Current search text: {deferredSearchText}</p>
</div>
);
}
export default SearchInput;
搭配useContext
函数,能做到全局状态管理
8. useTransition
useTransition
是一个帮助你在不阻塞 UI 的情况下更新状态的 React Hook。
通过 transition,UI 仍将在重新渲染过程中保持响应性。例如用户点击一个选项卡并且这个选项卡的内容需要大量时间渲染,但改变了主意并点击另一个选项卡,他们可以在不等待第一个重新渲染完成的情况下完成操作
import React, { useState, useTransition } from "react";
export default function Example() {
const [count, setCount] = useState(0);
// 启用过渡
const [isPending, startTransition] = useTransition();
// 使用 startTransition 来开始一个过渡
function handleClick() {
startTransition(() => {
setCount(count + 1);
});
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>
Click me {isPending ? "(pending)" : ""}
</button>
</div>
);
}
五、内置组件使用
1. Suspense
<Suspense>
允许在子组件完成加载前展示后备方案
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
children
:真正的 UI 渲染内容。如果 children 在渲染中被挂起,Suspense 边界将会渲染fallback
。fallback
:真正的 UI 未渲染完成时代替其渲染的备用 UI,它可以是任何有效的 React 节点。后备方案通常是一个轻量的占位符,例如表示加载中的图标或者骨架屏。当children
被挂起时,Suspense 将自动切换至渲染fallback
;当数据准备好时,又会自动切换至渲染children
。如果fallback
在渲染中被挂起,那么将自动激活最近的 Suspense 边界
六、 API使用
1. createContext
使用createContext
创建组件能够提供与读取的 上下文(context)
。
const SomeContext = createContext(defaultValue)
defaultValue
:当读取上下文的组件上方的树中没有匹配的上下文时,希望该上下文具有的默认值。倘若没有任何有意义的默认值,可指定其为 null。该默认值是用于作为“最后的手段”的后备方案。它是静态的,永远不会随时间改变。SomeContext.Provider
让你为被它包裹的组件提供上下文的值
function App() {
const ThemeContext= createContext("默认值")
const [theme, setTheme] = useState('light');
// ……
return (
<ThemeContext.Provider value={theme}> // value是要传递下去的值
<Page />
</ThemeContext.Provider>
);
}
2.forwardRef
forwardRef
允许组件使用 ref 将 DOM 节点暴露给父组件
import { forwardRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
return <input type="text" ref={ref} {...props} />;
});
// 使用 forwardRef 定义的组件
function App() {
const inputRef = React.useRef(null);
// 在父组件中使用 ref
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>
Focus Input
</button>
</div>
);
}
3. lazy
lazy
能够让你在组件第一次被渲染之前延迟加载组件的代码
import { lazy } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
4. memo
memo
允许你的组件在 props 没有改变的情况下跳过重新渲染
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
Component
:要进行记忆化的组件。memo 不会修改该组件,而是返回一个新的、记忆化的组件。它接受任何有效的 React 组件,包括函数组件和forwardRef
组件- 可选参数
arePropsEqua
l:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回true
。否则返回false
。通常情况下,你不需要指定此函数。默认情况下,React 将使用Object.is
比较每个 prop
5. startTransition
startTransition
可以让你在不阻塞 UI 的情况下更新 state
startTransition(scope)
startTransition
与useTransition
非常相似,但它不提供isPending
标志来跟踪一个 Transition 是否正在进行。你可以在useTransition
不可用时调用startTransition
。例如,在组件外部(如从数据库中)使用 startTransition
。