常用hooks 使用
useState
是一个 React Hook,允许函数组件在内部管理状态。
tsx
import { useState } from "react"
const [num, setNum] = useState(0);
<button onClick={() => setNum(num => num + 1)}>修改</button>
useEffect
获取数据、订阅事件、手动操作DOM等。这些操作在函数式编程中被称为"副作用"(side effects),而useEffect正是处理这些副作用的函数 基本用法
-
无依赖数组,没有设置第二个参数,每次渲染后都执行
tsxuseEffect(() => { // 每次渲染后都会执行(包括首次) console.log('无依赖数组effect执行') }) -
空依赖数组,第二个参数为空数组,useEffect仅在挂载时执行
tsxuseEffect(() => { // 只在组件挂载时执行一次 console.log('空依赖数组effect执行') }, []); -
有依赖项,当依赖项变化时执行
tsxconst [count, setCount] = useState(0); useEffect(() => { console.log('有依赖项effect执行') }, [count]);
useEffect可以返回一个清理函数,这个函数会在组件卸载时执行 ,即当组件从 DOM 中移除时,React 会执行所有 effect 的清理函数;还会在依赖项变化时执行,即当 effect 的依赖项发生变化,React 会先执行上一次 effect 的清理函数,再运行新的 effect
tsx
const [time,setTime] = useState(0)
useEffect(()=>{
console.log('定时器开启')
const timer = setInterval(() => {
setTime(prevTime => prevTime+1)
}, 1000)
return () => {
console.log('定时器关闭')
clearInterval(timer)
}
},[])
useLayoutEffect
和useEffect使用差不多,主要是useLayoutEffect会阻塞
- useEffect:渲染完 → 浏览器画到屏幕上 → 再执行(异步,不阻塞渲染)
- useLayoutEffect:渲染完 → 改 DOM → 浏览器再画(同步,会阻塞渲染)
useCallback
允许你在多次渲染中缓存函数的 React Hook
子组件
tsx
import { memo } from 'react';
const Son = memo(function ({onChange}:any) {
console.info('input组件渲染了')
return (
<div>
<input type="text" onChange={e => onChange(e.target.value)} />
</div>
)
});
export default Son;
父组件
tsx
import { useState, useCallback } from "react"
import axios from "axios"
import Son from './Son';
function Home() {
const [count, setCount] = useState(0)
const [title, setTitle] = useState('react')
//缓存函数不会触发子组件重新渲染
const changeValue = useCallback((value: any) => { console.info(value) }, [])
//函数会触发子组件重新渲染
const changeValue2 = (value:string) => {
console.info(value)
}
return (
<>
<ShippingForm onChange={changeValue2} />
<button onClick={() => { setCount(count +1) }}>{count}</button>
<button onClick={() => { setTitle(title+count) }}>sayHello</button>
</>
)
}
export default Home
useMemo
每次重新渲染的时候能够缓存计算的结果。
工具
ts
export function createTodos() {
const todos = [];
for (let i = 0; i < 50; i++) {
todos.push({
id: i,
text: "Todo " + (i + 1),
completed: Math.random() > 0.5
});
}
return todos;
}
export function filterTodos(todos:any[], tab:string) {
console.log('[ARTIFICIALLY SLOW] Filtering ' + todos.length + ' todos for "' + tab + '" tab.');
let startTime = performance.now();
while (performance.now() - startTime < 500) {
// 在 500 毫秒内不执行任何操作以模拟极慢的代码
}
return todos.filter((todo: any) => {
console.log('todo',todo);
if (tab === 'all') {
return true;
} else if (tab === 'active') {
return !todo.completed;
} else if (tab === 'completed') {
return todo.completed;
}
});
}
子组件
tsx
import { useMemo } from 'react';
import { filterTodos } from '@/utils/index'
export default function TodoList({ todos, theme, tab }:any) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
return (
<div className={theme}>
<p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p>
<ul>
{visibleTodos.map(todo => (
<li key={todo.id}>
{todo.completed ?
<s>{todo.text}</s> :
todo.text
}
</li>
))}
</ul>
</div>
);
}
父组件
tsx
import { useState } from "react"
import { createTodos } from '@/utils/index';
import TodoList from './ToDoList';
const todos = createTodos();
function Home() {
const [tab, setTab] = useState('all');
const [isDark, setIsDark] = useState(false);
return (
<>
<button onClick={() => setTab('all')}>
All
</button>
<button onClick={() => setTab('active')}>
Active
</button>
<button onClick={() => setTab('completed')}>
Completed
</button>
<br />
<label>
<input
type="checkbox"
checked={isDark}
onChange={e => setIsDark(e.target.checked)}
/>
Dark mode
</label>
<hr />
<TodoList
todos={todos}
tab={tab}
theme={isDark ? 'dark' : 'light'}
/>
</>
)
}
export default Home
useReducer
响应式状态管理,它是React提供的用于管理复杂状态逻辑的Hook。
tsx
import { useReducer } from "react"
const initialState ={
count :0,
}
const reducer = (state:any ,action:any)=>{
switch(action.type){
case 'increment':
return {
...state,count:state.count + 1
};
case 'decrement':
return {
...state,count:state.count -1
};
case 'reset':
return{
...state,count:0
}
case 'addNum': // 命令:加指定数值(带参数)
return { ...state, count: state.count + action.payload };
default:
return state
}
}
function Home() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<h2>Count: {state.count}</h2>
<button onClick={() => dispatch({ type: 'increment' })}>加一</button>
<button onClick={() => dispatch({ type: 'decrement' })}>减一</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
<button onClick={() => dispatch({ type: 'addNum',payload:10 })}>加10</button>
</>
)
}
export default Home
useRef
获取dom,然后对dom进行操作
tsx
import { useRef } from "react"
const divRef = useRef<HTMLDivElement>(null)
useLayoutEffect(() => {
divRef.current!.style.background = 'red'
},[divRef])
<div ref={divRef} style={{height:'50px',background:'lightblue'}}>
内容
</div>
- createContext 与 useContext
useContext 是React提供的一个Hook,它允许你在组件中订阅React的Context(上下文)而不需要使用传统的Context.Consumer组件。这使得代码更加简洁和易于理解。
tsx
//主页面
import { useState, createContext } from "react"
const [num, setNum] = useState(0);
const HomeContext = createContext(num);
return (
<HomeContext.Provider value={num}>
<div>主页</div>
<button onClick={() => setNum(10)}>修改</button>
<a onClick={() => navigate('/login/33/jack')}>去 Login 页面</a>
<Son context={HomeContext}/>
</HomeContext.Provider>
)
基于useContext 获取
子组件
tsx
import { useContext } from 'react'
type Props = {
context:React.Context<number>
}
const Son = (props:Props) => {
const num = useContext(props.context)
return (
<>
<div>我是son组件{ num }</div>
</>
)
}
export default Son
基于Context.Consumer获取
子组件
tsx
type Props = {
context:React.Context<number>
}
const Son = (props:Props) => {
return (
<>
<div>我是son组件
<props.context.Consumer>
{
(value) => <span>{value}</span>
}
</props.context.Consumer>
</div>
</>
)
}
export default Son
API
memo
允许你的组件在 props 没有改变的情况下跳过重新渲染。重要:不要把子组件写入父组件函数中,不然缓存不生效。
tsx
//子组件
import { useLayoutEffect,useRef,memo } from "react"
const SetContent = memo(() => {
const divRef = useRef<HTMLDivElement>(null)
useLayoutEffect(() => {
if (divRef) {
divRef.current!.style.background = 'red'
}
}, [])
return (
<div ref={divRef} style={{ height: '50px', background: 'lightblue' }}>
内容
</div>
)
})
tsx
//父组件
<div>
<SetContent />
</div>
子组件封装
子组件中封装类似插槽功能
tsx
type Props = {
children?:React.ReactNode
}
const Son = (props:Props) => {
return (
<>
<div>我是son组件
{props.children}
</div>
</>
)
}
export default Son
tsx
//父组件
<Son>
<div>孙子</div>
</Son>
Redux 使用
sh
# 安装依赖
# @reduxjs/toolkit react-redux redux状态管理
# redux-persist redux数据持久化
npm i @reduxjs/toolkit react-redux redux-persist
store与持久化配置
- modules 配置
ts
/store/modules/counter.ts
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 10,
},
reducers: {
incremented: (state) => {
// Redux Toolkit 允许在 reducers 中编写 "mutating" 逻辑。
// 它实际上并没有改变 state,因为使用的是 Immer 库,检测到"草稿 state"的变化并产生一个全新的
// 基于这些更改的不可变的 state。
state.value += 1;
},
decremented: (state) => {
state.value -= 1;
},
},
})
export const { incremented, decremented } = counterSlice.actions;
const reducer = counterSlice.reducer
export default reducer;
- store中的配置
ts
/store/index.ts
import { configureStore,combineReducers } from "@reduxjs/toolkit";
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import counterReducer from "./modules/counter"
const reducer = combineReducers({
counter: counterReducer,
})
const persistConfig = {
key: 'root', // 存储的键名
storage, // 存储引擎
// whitelist: ['counter'], // 只持久化这些 reducer
//blacklist: ['temporary'], // 不持久化这些 reducer
}
const persistedReducer = persistReducer(persistConfig, reducer)
export const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware => getDefaultMiddleware({
//关闭redux序列化检测
serializableCheck: false,
})
});
//给state设置类型
export type RootState = ReturnType<typeof store.getState>
//给dispatch设置类型
export type AppDispatch = typeof store.dispatch
export type AppStore = typeof store
export const persistor = persistStore(store)
入口文件配置
tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import { PersistGate } from 'redux-persist/integration/react'
import { store, persistor } from "./store"
import { Provider } from "react-redux";
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
</StrictMode>,
)
组件中的使用
ts
import { useSelector,useDispatch } from "react-redux";
import { incremented, decremented } from "./store/modules/counter"
import type { RootState, AppDispatch } from './store'
function App() {
const { value } = useSelector.withTypes<RootState>()(state => state.counter);
const dispatch = useDispatch.withTypes<AppDispatch>()();
return (
<>
<button onClick={() => dispatch(incremented())}>+</button>
<span>{value}</span>
<button onClick={() => dispatch(decremented())}>-</button>
</>
)
}
异步使用
- modules中配置
ts
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
import type { AppDispatch } from "..";
const counterSlice = createSlice({
name: "counter",
initialState: {
channelList:[] as any[]
},
reducers: {
setChannles: (state, action) => {
state.channelList = action.payload
}
},
})
const reducer = counterSlice.reducer
//异步请求处理
const { setChannles } = counterSlice.actions
export const fetchChannelList = () => {
return async (dispatch:AppDispatch) => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
dispatch(setChannles(res.data.data.channels))
}
}
export default reducer;
- 组件中使用
tsx
import { useSelector,useDispatch } from "react-redux";
import type { RootState, AppDispatch } from '@/store'
const dispatch = useDispatch.withTypes<AppDispatch>()();
const { channelList } = useSelector.withTypes<RootState>()(state => state.counter)
useEffect(() => {
dispatch(fetchChannelList())
},[dispatch])
<ul>
{ channelList.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
路由 使用
sh
npm i react-router-dom
- 路由表与懒加载配置
tsx
import {
createBrowserRouter
} from 'react-router-dom'
import { lazy, Suspense } from 'react'
import Home from '@/pages/Home/index'
const Login = lazy(() => import('@/pages/Login/index'))
const routes = createBrowserRouter([
{
path: '/',
element: <Home />
},
{
path: '/login',
element: <Suspense fallback={<div>Loading...</div>}><Login /></Suspense>
}
])
export default routes
- 入口文件配置
结合了redux的使用
tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { PersistGate } from 'redux-persist/integration/react'
import { store, persistor } from "./store"
import { Provider } from "react-redux";
import routes from './routes'
import { RouterProvider } from 'react-router-dom'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RouterProvider router={routes} />
</PersistGate>
</Provider>
</StrictMode>,
)
-
路由导航
- 通过Link 标签
tsximport { Link } from "react-router-dom"; <Link to="/login">去 Login 页面</Link>- 通过useNavigate
tsximport { useNavigate } from "react-router-dom"; const navigate = useNavigate() <a onClick={() => navigate('/login')}>去 Login 页面</a>- 路由传参useSearchParams
tsx// 主页 import { useNavigate } from "react-router-dom"; <a onClick={() => navigate('/login?id=33')}>去 Login 页面</a> //登录页 import { useSearchParams } from "react-router-dom" const [params] = useSearchParams() const id = params.get('id')- 路由传参useParams
tsx//修改路由表 { path: '/login/:id', } //主页 import { useNavigate } from "react-router-dom"; <a onClick={() => navigate('/login/33')}>去 Login 页面</a> //登录页 import { useParams } from "react-router-dom" const params = useParams() const id = params.id -
嵌套路由
基于Outlet
tsx
import { Outlet } from "react-router"
const BaseContainer = () => {
return <Outlet />
}
export default BaseContainer
修改路由
tsx
import BaseContainer from '@/layout/base'
import { lazy, Suspense } from 'react'
const routes = createBrowserRouter([
{
path: '/',
element: <BaseContainer />,
children: [
{
//设置为默认二级路由,一级路由访问的时候,它也能得到渲染
index:true,
element: (
<Suspense fallback={loadingElement}>
<H5game />
</Suspense>
)
}
]
}
])
样式使用
- 通过style标签使用
tsx
<span style={{fontSize:'24px'}}>123</span>
- 通过className使用
tsx
<div className="card">card</div>
- css-module样式的文件内,通过import的方式引入该xxx.module.css文件
tsx
import style from './style.module.css'
<span className={style.counter}>123</span>
vie 项目中配置
配置别名
- 修改vite.config.ts
ts
import { resolve } from 'path'
resolve: {
alias: {
'@': resolve('./src')
}
},
- 修改tsconfig.json
json
"paths": {
"@/*": ["./src/*"]
},
修改代理
- 修改vite.config.ts
ts
import { defineConfig,loadEnv } from 'vite'
const VITE_API_URL: string = loadEnv(
mode,
process.cwd()
).VITE_API_URL
return defineConfig({
plugins: [
react()
],
server: {
host: true, // 监听所有可用的网络接口
port: 5173,
proxy: {
'/api': {
target: VITE_API_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
// https: true
},
})
修改打包配置
- 修改vite.config.ts
ts
import path, { resolve } from 'path'
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: process.env.NODE_ENV !== 'production',
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html')
},
output: {
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
const ext = path.extname(assetInfo.name)
if (ext === '.css') {
return 'css/[name]-[hash][extname]'
}
if (/\.(png|jpe?g|gif|svg|webp|avif)$/.test(ext)) {
return 'image/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
},
}
},
minify: 'terser',
terserOptions: {
compress: {
hoist_funs: true,
hoist_vars: true,
reduce_vars: true,
passes: 2,
drop_console: process.env.NODE_ENV === 'production',
drop_debugger: process.env.NODE_ENV === 'production'
}
}
}