一、创建react项目
1. vite创建react项目
javascript
npm create vite@latest
2. 安装react-router-dom路由
javascript
npm i react-router-dom
3. 安装状态管理库
这里可以使用redux,但是个人觉得比较繁琐,于是使用的是一个类似于vue3中的pinia的一个库valtio
javascript
npm i valtio
4. 安装ui库
这里使用Ant Design,但是这个库有点小问题,那就是样式中的px不能被postcss-pxtorem插件转换成rem
javascript
npm install antd --save
5. 安装axios
javascript
npm i axios
二、react-router-dom使用
1. 路由文件
javascript
// 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页面中实现全局路由守卫,这个页面是/对应的页面
javascript
// 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做项目的入口
javascript
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使用
javascript
// index.tsx
import { proxy } from "valtio";
export const gasListState:IGasListState= proxy({
gasList: [],
});
export function setGasList(list:IGasList[]) {
gasListState.gasList = list;
}
使用方法
javascript
import { useSnapshot } from "valtio";
import { gasListState } from "@/store/gasList";
let gastate = useSnapshot(gasListState); //通过useSnapshot方法包裹gasListState
四、hook使用
1. useCallback
useCallback是一个允许你在多次渲染中缓存函数的 React Hook
javascript
//使用方式
const cachedFn = useCallback(fn, dependencies)
它会返回传入的fn,如果dependencies依赖项没有发生变化的时候,组件多次渲染也是返回缓存的fn,也就是说,cachedFn的值是同一个值,注意它只是返回fn,并不是调用fn,需要手动调用cachedFn()
javascript
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中的计算属性,不多说
javascript
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,主要用于组件通信,避免组件层级太深,一层一层往下传递的尴尬
javascript
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(),用于暴露子组件指定的函数给父组件使用
javascript
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 的某些部分
javascript
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有一点相似
javascript
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 仍将在重新渲染过程中保持响应性。例如用户点击一个选项卡并且这个选项卡的内容需要大量时间渲染,但改变了主意并点击另一个选项卡,他们可以在不等待第一个重新渲染完成的情况下完成操作
javascript
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>允许在子组件完成加载前展示后备方案
javascript
<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)。
javascript
const SomeContext = createContext(defaultValue)
defaultValue:当读取上下文的组件上方的树中没有匹配的上下文时,希望该上下文具有的默认值。倘若没有任何有意义的默认值,可指定其为 null。该默认值是用于作为"最后的手段"的后备方案。它是静态的,永远不会随时间改变。SomeContext.Provider让你为被它包裹的组件提供上下文的值
javascript
function App() {
const ThemeContext= createContext("默认值")
const [theme, setTheme] = useState('light');
// ......
return (
<ThemeContext.Provider value={theme}> // value是要传递下去的值
<Page />
</ThemeContext.Provider>
);
}
2.forwardRef
forwardRef允许组件使用 ref 将 DOM 节点暴露给父组件
javascript
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能够让你在组件第一次被渲染之前延迟加载组件的代码
javascript
import { lazy } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
4. memo
memo允许你的组件在 props 没有改变的情况下跳过重新渲染
javascript
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
Component:要进行记忆化的组件。memo 不会修改该组件,而是返回一个新的、记忆化的组件。它接受任何有效的 React 组件,包括函数组件和forwardRef组件- 可选参数
arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回true。否则返回false。通常情况下,你不需要指定此函数。默认情况下,React 将使用Object.is比较每个 prop
5. startTransition
startTransition可以让你在不阻塞 UI 的情况下更新 state
javascript
startTransition(scope)
startTransition与useTransition非常相似,但它不提供isPending标志来跟踪一个 Transition 是否正在进行。你可以在useTransition不可用时调用startTransition。例如,在组件外部(如从数据库中)使用 startTransition。