React 常见使用笔记

常用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正是处理这些副作用的函数 基本用法

  1. 无依赖数组,没有设置第二个参数,每次渲染后都执行

    tsx 复制代码
      useEffect(() => {
          // 每次渲染后都会执行(包括首次)
        console.log('无依赖数组effect执行')
      })
  2. 空依赖数组,第二个参数为空数组,useEffect仅在挂载时执行

    tsx 复制代码
      useEffect(() => {
        // 只在组件挂载时执行一次
        console.log('空依赖数组effect执行')
      }, []);
  3. 有依赖项,当依赖项变化时执行

    tsx 复制代码
      const [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 标签
    tsx 复制代码
    import { Link } from "react-router-dom";
    <Link to="/login">去 Login 页面</Link>
    • 通过useNavigate
    tsx 复制代码
    import { 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'
      }
    }
  }
相关推荐
鹏多多4 小时前
React项目使用useMemo优化性能指南和应用场景
前端·javascript·react.js
mapbar_front13 小时前
React 中 useCallback 的基本使用和原理解析
react.js
前端架构师-老李18 小时前
React中useContext的基本使用和原理解析
前端·javascript·react.js
技术钱19 小时前
react antdesign实现表格嵌套表单
前端·react.js·前端框架
小p19 小时前
react学习4:CSS Modules 样式
前端·react.js
小p20 小时前
react学习3: 闭包陷阱
前端·react.js
FogLetter21 小时前
React Fiber 机制:让渲染变得“有礼貌”的魔法
前端·react.js
刺客_Andy21 小时前
React 第五十一节 Router中useOutletContext的使用详解及注意事项
前端·javascript·react.js
你说啥名字好呢1 天前
【React Fiber的重要属性】
javascript·react.js·ecmascript