当前位置: 首页 > news >正文

react基础知识(下)

一些适用的知识点

可执行命令

start:开发时启动sum的命令

build:开发完毕后打包的命令

classnames

npm install classnames

js库

静态类名和动态类名
className = {classNames('nav-item',{active:type===item.type})}

受控表单绑定

在这里插入图片描述

const [value,setValue] = useState('')
<inputvalue={value}onChange = {(e)=>setValue(e.target.value)}
/>

useRef获取dom

在react组件中获取/操作DOM,需要使用useRef钩子函数,分为两步

  1. 使用useRef创建ref对象,并与JSX绑定
    const inputRef = useRef(null)
    <input type="text" ref={inputRef} />
    
  2. 在DOM可用时,通过inputRef.current拿到DOM对象
    console.log(inputRef.current)
    

使用的 lodash库

  1. 生成唯一的随机数—uuid
  2. 以当前时间为标准,生成固定格式—dayjs

组件通信

父子通信

父传子

在这里插入图片描述

  1. props可传递任意的数据:数字、字符串、数组、对象、函数、JSX
  2. props是只读对象,子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改
  3. props.children
    当我们在使用子组件标签时,在标签内部写入的内容,React 会自动将其作为一个特殊的 prop,即 props.children,传递给子组件。子组件可以通过访问这个属性来获取并渲染父组件传递过来的内容。
    import React from'react';
    import Son from './Son';function Parent() {return (<Son><span>this is span</span></Son>);
    }// 子组件
    function Son({ children }) {return (<div><h2>子组件接收到的内容:</h2>{children}</div>);
    }export default Parent;
    

当传递多个元素时,props.children 是一个数组,包含了 <span> 和 <p> 这两个元素,子组件需要按照数组的方式去处理和渲染这些元素。

function Parent() {return (<Son><span>this is span</span><p>this is a paragraph</p></Son>);
}

子传父

子组件通过调用父组件传递过来的回调函数,将数据传递给父组件。

// 父组件
function Parent() {const handleChildData = (data) => {console.log('接收到子组件的数据:', data);};return (<Son sendDataToParent={handleChildData} />);
}// 子组件
function Son({ sendDataToParent }) {const childData = "子组件的数据";const handleClick = () => {sendDataToParent(childData);};return (<div><button onClick={handleClick}>发送数据给父组件</button></div>);
}

兄弟通信

通常需要借助它们共同的父组件来实现。兄弟组件 A 可以通过调用父组件的函数,将数据传递给父组件,然后父组件再将数据传递给兄弟组件 B

import React, { useState } from'react';// 兄弟组件A
function BrotherA({ sendDataToParent }) {const data = "这是来自BrotherA的数据";const handleSend = () => {sendDataToParent(data);};return (<div><button onClick={handleSend}>发送数据给兄弟组件</button></div>);
}// 兄弟组件B
function BrotherB({ receivedData }) {return (<div><p>接收到的兄弟组件数据: {receivedData}</p></div>);
}// 父组件
function Parent() {const [dataFromBrotherA, setDataFromBrotherA] = useState('');const handleReceiveFromA = (data) => {setDataFromBrotherA(data);};return (<div><h2>兄弟组件通信示例 - 通过父组件中转</h2><BrotherA sendDataToParent={handleReceiveFromA} /><BrotherB receivedData={dataFromBrotherA} /></div>);
}export default Parent;

跨层通信

使用 Context API:适用于需要在多层嵌套组件之间共享数据,而不需要逐层传递 props 的场景
在这里插入图片描述

// 创建 Context
const MyContext = React.createContext();// 提供者组件
function Provider({ children }) {const value = "共享的数据";return (<MyContext.Provider value={value}>{children}</MyContext.Provider>);
}// 消费者组件
function Consumer() {return (<MyContext.Consumer>{value => <p>{value}</p>}</MyContext.Consumer>);
}function App() {return (<Provider><Consumer />  这里使用的props.children插槽写法</Provider>);
}

也可以借助redux等状态管理工具,下面该状态管理工具的详细介绍

useEffect

useEffect 是一个非常重要的 Hook,用于处理副作用操作,比如数据获取、订阅、手动更改 DOM 等。它可以让函数组件拥有类似类组件中生命周期钩子的能力。

import React, { useEffect } from'react';function MyComponent() {useEffect(() => {// 在这里编写副作用代码,比如数据获取、订阅等console.log('副作用操作执行');return () => {// 在这里进行清理工作,比如取消订阅、清除定时器等console.log('清理操作执行');};}, []); // 第二个参数是依赖数组return (<div>{/* 组件的JSX内容 */}</div>);
}

useEffect依赖项

在这里插入图片描述

清除副作用

import React, { useEffect, useState } from'react';function Timer() {const [seconds, setSeconds] = useState(0);useEffect(() => {const intervalId = setInterval(() => {setSeconds(prevSeconds => prevSeconds + 1);}, 1000);return () => {clearInterval(intervalId); // 组件卸载时清除定时器};}, []);return (<div><p>已过去的秒数: {seconds}</p></div>);
}

useEffect的返回函数会在组件卸载时执行,或者在下一次useEffect运行之前执行(如果useEffect有依赖项的话)。这个返回函数通常用于清理在useEffect中设置的任何副作用,如定时器、事件监听器或订阅。

为什么 async 不能写在 useEffect 的回调函数前面?

1.回调函数设计与返回值用途
  • React:useEffect 的回调函数设计比较特殊,它允许返回一个清理函数,用于在组件卸载或者依赖项变化时清除副作用,比如取消订阅、清除定时器等。由于 async 函数会隐式返回一个 Promise,这就和 useEffect 期望的返回值(清理函数或者 undefined )产生了冲突,所以不能直接用 async 修饰 useEffect 的回调函数。
  • Vue:onMounted 等钩子函数并没有这样对返回值的特殊要求,开发者可以根据需要自由编写异步代码,不会影响 Vue 对组件生命周期的管理。
2. 异步处理和状态管理的方式
  • React:React 自身并没有内置的像 Vue 那样强大且统一的响应式系统,在处理异步操作后的状态更新时,需要开发者手动去管理状态的更新和组件的重新渲染,并且要特别注意避免因为异步操作导致的一些问题,比如竞态条件、闭包陷阱等,这就使得在 useEffect 中对异步操作的处理需要遵循特定的规则。
  • Vue:Vue 的响应式系统会自动追踪依赖并在数据变化时更新视图,在 onMounted 中使用 async 进行异步数据获取并更新响应式数据,Vue 能够很方便地处理视图的更新,不需要开发者像在 React 中那样进行复杂的手动处理。

如何在 useEffect 中使用异步操作?

虽然 useEffect 的回调函数不能直接声明为 async,但你可以在回调函数内部定义一个 async 函数,并立即调用它。这样可以实现异步操作

useEffect(() => {// 定义异步函数const fetchData = async () => {try {const response = await fetch('https://api.example.com/data');const json = await response.json();setData(json);} catch (error) {console.error('Error fetching data:', error);}};fetchData(); // 立即调用异步函数// 返回清理函数(可选)return () => {// 组件卸载时的清理逻辑};
}, []); // 依赖数组为空,仅在挂载时执行一次
  • 使用立即执行的异步函数表达式
useEffect(() => {(async () => {try {const data = await fetchData();setData(data);} catch (error) {console.error(error);}})(); // 立即执行异步函数return () => { /* 清理逻辑 */ };
}, []);
  • 使用 Promise 链式调用
useEffect(() => {fetch('https://api.example.com/data').then(response => response.json()).then(json => setData(json)).catch(error => console.error(error));return () => { /* 清理逻辑 */ };
}, []);

处理异步操作中的清理需求

当异步操作需要在组件卸载前取消时(如取消未完成的请求),可以使用 AbortController:

useEffect(() => {const controller = new AbortController();const fetchData = async () => {try {const response = await fetch('https://api.example.com/data', {signal: controller.signal // 关联 AbortController});const json = await response.json();setData(json);} catch (error) {if (error.name === 'AbortError') {console.log('请求被取消');} else {console.error('Error fetching data:', error);}}};fetchData();// 组件卸载时取消请求return () => {controller.abort(); // 取消未完成的请求};
}, []);

自定义hook

自定义 Hook(Custom Hook) 是一种复用有状态逻辑的方式,它允许你将可复用的逻辑提取到独立的函数中,同时保留状态管理和副作用的能力。

什么是自定义 Hook

自定义hook是以use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用

  • 复用逻辑:将有状态的逻辑(如数据获取、表单验证、事件监听)封装成可复用的函数。
  • 保留状态:每次调用 Hook 时,其内部状态都是独立的。
  • 遵循 Hook 规则:只能在函数组件或其他 Hook 中调用,且不要在循环 / 条件语句中调用。

为什么需要自定义 Hook?

  1. 解决代码重复问题
    当多个组件需要实现相同的逻辑(如获取用户位置、处理表单验证)时,自定义 Hook 可以避免代码冗余。
  2. 分离关注点
    将复杂逻辑从组件中提取出来,使组件更专注于 UI 渲染,提高代码可读性和可维护性。
  3. 状态逻辑复用
    与普通函数不同,自定义 Hook 可以使用 React 的状态和副作用管理,保留状态的独立性。

封装自定义函数

// useCounter.js
import { useState, useCallback } from'react';export function useCounter(initialValue = 0) {// 使用 useState 管理状态const [count, setCount] = useState(initialValue);// 使用 useCallback 优化回调函数const increment = useCallback(() => {setCount(prev => prev + 1);}, []);const decrement = useCallback(() => {setCount(prev => prev - 1);}, []);const reset = useCallback(() => {setCount(initialValue);}, [initialValue]);// 返回状态和操作函数return {count,increment,decrement,reset};
}

使用自定义函数

import { useCounter } from './useCounter';function CounterComponent() {// 使用自定义Hookconst { count, increment, decrement, reset } = useCounter(0);return (<div><p>Count: {count}</p><button onClick={increment}>+</button><button onClick={decrement}>-</button><button onClick={reset}>Reset</button></div>);
}

Hook 规则

  • 只在顶层调用 Hook:不要在循环、条件语句或嵌套函数中调用 Hook,确保 Hook 调用顺序一致。
  • 只在 React 函数中调用 Hook:不要在普通 JavaScript 函数中调用 Hook。
  • 每个组件调用 Hook 时,状态是独立的。如果需要共享状态,可以使用 Context 或状态管理库

Redux

在这里插入图片描述

原始代码

使用 CRA 快速创建 React 项目
npx create - react - app react - redux
安装配套工具
npm i @reduxjs/toolkit react - redux
启动项目
npm run start
<script>
// 1. 定义reducer函数
// 作用: 根据不同的action对象, 返回不同的新的state
// state: 管理的数据初始状态
// action: 对象 type 标记当前想要做什么样的修改
function reducer (state = { count: 0 }, action) {// 数据不可变: 基于原始状态生成一个新的状态if (action.type === 'INCREMENT') {return { count: state.count + 1 }}if (action.type === 'DECREMENT') {return { count: state.count - 1 }}return state
}// 2. 使用reducer函数生成store实例
const store = Redux.createStore(reducer)// 3. 通过store实例的subscribe订阅数据变化
// 回调函数可以在每次state发生变化的时候自动执行
store.subscribe(() => {console.log('state变化了')
})// 4. 通过store实例的dispatch函数提交action更改状态
const inBtn = document.getElementById('increment')
inBtn.addEventListener('click', () => {// 增store.dispatch({type: 'INCREMENT'})
})const dBtn = document.getElementById('decrement')
dBtn.addEventListener('click', () => {// 减store.dispatch({type: 'DECREMENT'})
})
</script>

为什么要使用dispatch

  1. dispatch 是触发状态变更的唯一入口
  • 在 Redux 中,状态(state)是只读的,不能直接修改(如 state.count++ 是被禁止的)。
  • 必须通过 dispatch 发送一个 action,才能触发 reducer 计算新状态。
    类比:就像你想让手机拍照,必须按下快门键(dispatch),而不是直接修改相册文件(直接改状态)。没有快门键(dispatch),你无法告诉手机 “我要拍照”(触发动作)。
  1. dispatch 是连接视图层和状态层的桥梁
  • 视图层(如按钮点击事件)无法直接访问或修改 reducer 和 store,必须通过 dispatch 发送 action 来 “告知” 状态层需要做什么。
// 没有 dispatch,点击事件无法触发状态变更
inBtn.addEventListener('click', () => {// ❌ 非法操作:直接修改 state(Redux 不允许)store.state.count += 1; 
});
  • 合法操作:通过 dispatch 发送 action,由 reducer 处理状态变更:
inBtn.addEventListener('click', () => {store.dispatch({ type: 'INCREMENT' }); // ✅ 正确方式
});
  1. dispatch 是数据流可追踪的基础
  • 所有状态变更都必须经过 dispatch,因此:
    • 可以通过 Redux DevTools 追踪每一次 dispatch 的 action,查看状态变化的历史记录(时间旅行调试)。
    • 可以通过中间件(如 Redux Thunk)在 dispatch 过程中拦截、处理异步逻辑(如网络请求)。
  • 如果没有 dispatch,状态变更可能来自任意地方(如视图层直接修改状态),导致数据流混乱,无法追踪和调试。

使用配套工具

npx create-react-app react-redux-demo
cd react-redux-demo
npm i @reduxjs/toolkit react-redux

在这里插入图片描述
在这里插入图片描述1.创建 store 目录及相关文件
在项目根目录下创建 store 目录,然后在 store 目录下创建 modules 目录,接着在 store/modules 目录下创建 counterStore.js 文件,内容如下:

import { createSlice } from '@reduxjs/toolkit';// 创建一个 slice,它包含了状态、reducer 和 action creators
const counterSlice = createSlice({name: 'counter',initialState: { value: 0 }, // 初始状态,计数器初始值为 0reducers: {increment: (state) => {state.value++; // 使用 @reduxjs/toolkit 可以直接修改状态,内部会处理不可变更新},decrement: (state) => {state.value--;},incrementByAmount: (state, action) => {state.value += action.payload;},},
});// 导出 action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出 reducer
export default counterSlice.reducer;
  1. 在 store 目录下创建 index.js 文件,用于组合模块并导出 store:
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './modules/counterStore';const store = configureStore({reducer: {counter: counterReducer, // 将 counterReducer 挂载到 store 的 counter 键下},
});export default store;
  1. 在 React 组件中使用 Redux
import React from'react';
import { useSelector, useDispatch } from'react-redux';
import { increment, decrement, incrementByAmount } from './store/modules/counterStore';
import store from './store';function App() {// 使用 useSelector 从 store 中获取状态const count = useSelector((state) => state.counter.value);const dispatch = useDispatch(); // 获取 dispatch 函数return (<div className="App"><h1>Counter: {count}</h1><button onClick={() => dispatch(increment())}>Increment</button><button onClick={() => dispatch(decrement())}>Decrement</button><inputtype="number"placeholder="Increment by amount"onChange={(e) => {const amount = parseInt(e.target.value, 10);if (!isNaN(amount)) {dispatch(incrementByAmount(amount));}}}/></div>);
}export default App;

还有第二种写法

 const {count} = useSelector((state) => state.counter);

action传参

  1. 定义 reducer
// 引入 createSlice 用于创建 Redux slice
import { createSlice } from '@reduxjs/toolkit';// 创建计数器 slice
const counterSlice = createSlice({name: 'counter',initialState: { value: 0 },reducers: {// 传入参数增加计数器值的 actionincrementByAmount: (state, action) => {// 从 action.payload 获取传递的参数值state.value += action.payload; }}
});// 导出 action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出 reducer
export default counterSlice.reducer;

这里 incrementByAmount 是一个接收参数的 action 处理函数,它从 action.payload 读取外部传入的值来更新计数器状态。
当使用 @reduxjs/toolkit 的 createSlice 定义 reducers 时,action.payload 会被自动解析为传入的参数。例如:

  1. 在组件中使用并传参
import React from'react';
import { useSelector, useDispatch } from'react-redux';
// 导入前面定义的 action creators
import { increment, decrement, incrementByAmount } from './path/to/counterSlice';function App() {const count = useSelector((state) => state.counter.value);const dispatch = useDispatch();return (<div><inputtype="number"placeholder="Increment by amount"onChange={(e) => {const amount = parseInt(e.target.value, 10);if (!isNaN(amount)) {// 调用 incrementByAmount 并传递输入的数值作为参数dispatch(incrementByAmount(amount)); }}}/></div>);
}export default App;

用户在输入框输入数字,点击相关按钮时,incrementByAmount action 被触发,输入的数字作为 action.payload 传递给 reducer ,从而实现按指定数值增加计数器的功能。

异步action

  1. 定义 reducer 和相关 action
import { createSlice } from '@reduxjs/toolkit';
// 假设使用 axios 进行网络请求,先引入
import axios from 'axios';const userSlice = createSlice({name: 'users',initialState: {list: [],isLoading: false,error: null},reducers: {}
});// 定义异步 action creator
const fetchUsers = () => {return async (dispatch) => {dispatch(setLoading());try {// 模拟网络请求获取用户列表数据const response = await axios.get('/api/users'); dispatch(setUsers(response.data));} catch (error) {dispatch(setError(error.message));}};
};export { fetchUsers };
export default userSlice.reducer;

为什么异步 action 是放在外面的

  • 代码组织与可维护性:把异步 action 相关逻辑集中定义,与同步 action 和 reducer 放在一起,使整个状态管理逻辑代码结构清晰。比如在一个管理用户相关状态的文件里,同步的用户状态更新(如设置用户信息 )和异步的获取用户列表操作都能集中呈现,后续维护和查找功能逻辑更方便。

  • 便于复用与测试:集中定义的异步 action 可以在多个组件中方便地复用。同时,在进行单元测试时,能更方便地针对异步 action 的逻辑进行测试,不需要在各个使用它的组件中分散测试相关逻辑。

为什么 dispatch 可以作为参数传递

  1. Redux-Thunk 中间件的作用
  • 上述异步函数(称为 Thunk 函数)能接收 dispatch 作为参数,依赖于 Redux-Thunk 中间件的机制:

当组件调用 dispatch(thunkFunction) 时,Redux-Thunk 会拦截这个 thunkFunction,并主动将 dispatch 和 getState(store 的状态)作为参数传入。

  • 因此,Thunk 函数的参数 dispatch 实际上是 store 提供的真实 dispatch 函数,可以直接用于触发 action。
  1. 函数参数的本质
    从 JavaScript 函数的角度看:

    • dispatch 是一个函数引用,可以像普通变量一样作为参数传递给其他函数。
    • 异步函数接收 dispatch 作为参数,本质上是接收了一个 “触发状态更新的能力”,与函数接收其他回调函数(如 onSuccess)的逻辑一致。
  2. 在组件中调用异步 action

import React from'react';
import { useSelector, useDispatch } from'react-redux';
// 导入异步 action
import { fetchUsers } from './path/to/userSlice';function UserList() {React.useEffect(() => {// 组件挂载时触发获取用户列表的异步操作dispatch(fetchUsers()); }, [dispatch]);return (<div></div>);
}export default UserList;

在 UserList 组件中,通过 useEffect 在组件挂载时调用 dispatch(fetchUsers()) 触发异步操作。根据不同的状态(加载中、成功、失败 ),组件会显示相应的内容,实现了异步获取数据并更新 UI 的功能。

为什么依赖项是dispatch

  • 由于 React 函数组件重新渲染会重建作用域,如果存在一些特殊情况(比如在某些复杂的高阶组件封装、或者不规范的状态管理逻辑下 ),可能导致 useDispatch 每次返回的 dispatch 函数引用发生变化。

  • 这个 useEffect 内部的逻辑依赖于 dispatch 函数,如果 dispatch 函数的引用发生了变化(即它本身变了 ),就需要重新执行 useEffect 里的代码”。这样即使在一些极端或意外情况下 dispatch 函数引用改变了,也能保证 useEffect 内触发异步操作(dispatch(fetchUsers()) )的逻辑能正常运行,避免因使用了旧的 dispatch 函数引用而导致异步操作无法正确触发的问题。 虽然在常规、规范的代码中 dispatch 函数引用通常是稳定不变的,但遵循这样的处理方式是一种更严谨、符合最佳实践的做法。

Router

npm i react-router-dom

核心组件

在这里插入图片描述
在这里插入图片描述

常用钩子

在这里插入图片描述

路由跳转

  • 使用 组件*:这是最常用的方式,类似于 HTML 中的 <a> 标签,但在 React Router 中使用它进行导航时不会重新加载整个页面,而是在单页应用内进行跳转。例如:
import { Link } from'react-router-dom';
function Navbar() {return (<nav><Link to="/">Home</Link><Link to="/about">About</Link></nav>);
}
  • 编程式导航:通过 useNavigate 钩子函数来实现路由跳转
import { useNavigate } from'react-router-dom';
function Login() {const navigate = useNavigate();const handleSubmit = () => {// 假设登录成功navigate('/dashboard');};return (<form onSubmit={handleSubmit}>{/* 登录表单内容 */}</form>);
}

跳转传参

  • params 传参:通过在路由路径中定义参数来传递数据。例如,定义一个用户详情页路由 /user/:id ,其中 :id 就是参数。在组件中可以通过 useParams 钩子获取参数值:
// 路由配置
<Route path="/user/:id" element={<UserDetail />} />// UserDetail 组件
import { useParams } from'react-router-dom';
function UserDetail() {const { id } = useParams();return <div>用户 ID{id}</div>;
}
  • 跳转时可以这样写:
import { Link } from'react-router-dom';
function UserList({ users }) {return (<ul>{users.map((user) => (<li key={user.id}><Link to={`/user/${user.id}`}>{user.name}</Link></li>))}</ul>);
}
  • 查询参数传参:通过在 URL 中添加查询字符串来传递参数,如 /search?keyword=react 。可以使用 useSearchParams 钩子来获取查询参数:
import { useSearchParams } from'react-router-dom';
function SearchResults() {const [searchParams] = useSearchParams();const keyword = searchParams.get('keyword');return <div>搜索关键词:{keyword}</div>;
}
  • 跳转时:
import { useNavigate } from'react-router-dom';
function SearchForm() {const navigate = useNavigate();const handleSubmit = (e) => {e.preventDefault();const keyword = e.target.elements.keyword.value;navigate(`/search?keyword=${keyword}`);};return (<form onSubmit={handleSubmit}><input type="text" name="keyword" /><button type="submit">搜索</button></form>);
}

获取当前路径

  import { useLocation } from 'react-router-dom'const About = () => {// 使用 hookconst location = useLocation();const { from, pathname } = locationreturn <div>这里是卡拉云的网站,你当前在 {pathname},你是从 {from} 跳转过来的</div>
}

location和useSearchParams区别

点击链接查看和 Kimi 的对话 https://kimi.moonshot.cn/share/d0dkul3ua3d2tdsjh830

404页面

我们只要在最后加入 path 为* 的一个路径,意为匹配所有路径,即可

function App() {return <BrowserRouter><Routes><Route path="*" element={<NotFound />} /></Routes></BrowserRouter>
}// 用来作为 404 页面的组件
const NotFound = () => {return <div>你来到了没有知识的荒原</div>
}

默认二级路由

在这里插入图片描述

子路由路径不以斜杠开头:这样可以确保子路由路径是相对于父路由的路径来解析的,避免路由匹配出现问题。
导航路径:在 Link 组件中,确保 to 属性的路径是正确的绝对路径。

import { Link, Outlet } from "react-router-dom"
const Layout = ()=>{return (<div>layout<Link to='son1'><button>去son1</button></Link><br></br>{/* <Link to='/son2'><button>去son2</button></Link> */}<Link to='/layout'><button>去son2</button></Link><Outlet></Outlet></div>)
}
export default Layoutimport { createBrowserRouter } from "react-router-dom";
import Login from "../page/Login";
import Home from "../page/Home";
import Son1 from "../page/son1";
import Son2 from "../page/son2";
import Layout from "../page/Layout";
export const router = createBrowserRouter([{path:'/login',element:<Login></Login>},{path:'/home/:id/:name',element:<Home></Home>},{path:'/layout',element:<Layout></Layout>,children:[{path:'son1',element:<Son1></Son1>},// {path:'son2',element:<Son2></Son2>}{index:true,element:<Son2></Son2>}]   },
])

路由懒加载

在这里插入图片描述
在这里插入图片描述

AuthRoute

AuthRoute 大概率是开发者自定义的一个组件 。通常其作用是进行权限验证相关操作,用于控制哪些用户能够访问特定路由对应的页面。比如:

  • 实现方式:它内部可能会检查用户的登录状态(例如查看是否存在有效的用户令牌 )、用户角色(比如区分普通用户和管理员 )等信息。如果用户满足访问条件(已登录且具有相应权限 ),则允许渲染其包裹的子组件(这里是 Layout 组件 );若不满足条件,可能会将用户重定向到登录页或提示无权限访问。示例代码如下:
import React from'react';
import { Navigate } from'react-router-dom';// 假设从 Redux 或其他状态管理获取用户登录状态
import { useSelector } from'react-redux'; const AuthRoute = ({ children }) => {const isLoggedIn = useSelector(state => state.user.isLoggedIn);if (!isLoggedIn) {return <Navigate to="/login" />;}return children;
};export default AuthRoute;

Suspense

  • Suspense 是 React 内置组件 ,主要用于处理异步加载组件时的过渡状态。在 React Router 场景下,常配合动态导入组件(实现代码分割 )使用,作用和特点如下:

  • 作用:当组件(如 Home、Article、Publish 组件 )是通过动态导入(如 const Home = React.lazy(() => import(‘./Home’)) )这种异步方式获取时,在组件实际加载完成之前,Suspense 可以展示一个 fallback 内容(这里设置为 ‘加载中’ ),向用户提示页面正在加载,提升用户体验。

  • 使用方法:需要包裹异步加载的组件,并通过 fallback 属性指定加载过程中显示的内容。当被包裹组件成功加载后,Suspense 会渲染该组件;若加载失败,可配合错误边界(ErrorBoundary 组件 )进行错误处理。示例:

import React, { Suspense } from'react';// 动态导入组件
const Home = React.lazy(() => import('./Home')); function App() {return (<Suspense fallback={<div>加载中...</div>}><Home /></Suspense>);
}export default App;

常用的HOOK函数(高阶组件优化)

useMemo

在这里插入图片描述
在这里插入图片描述

React.memo

在这里插入图片描述
在这里插入图片描述memo进行缓存,只有props发生变化的时候才会重新渲染

React.memo-props比较机制

数组是对象,属于引用类型(reference type)。
当你使用 useState Hook 来创建一个数组时,这个数组的引用在组件的整个生命周期中是不变的。
这意味着,只要数组的内容没有被显式地修改,数组的内存地址(引用)就不会改变。

机制

在这里插入图片描述
当传递的是对象时,比较的是新值和旧值的引用是否相同

因为每当对象发生变化时,父组件会重新执行,父组件重新执行会引发对象重新执行,进而导致对象的引用不相同,导致子组件重新渲染

可通过使用useMemo在组建渲染的过程中缓存一个值

import {memo, useMemo, useState} from'react'const MemoSon = memo (function Son ({ list}) {
console.log (' 子组件重新渲染了 ')
return <div>this is Son {list}</div>
})function App() {const [count, setCount] = useState(0)// const num = 100const list = useMemo(() => {return [1, 2, 3]}, [])
}

useMemo 接收两个参数,第一个参数是一个回调函数,用于返回要缓存的值;第二个参数是一个依赖数组,这里依赖数组为空数组 [],表示只要组件重新渲染,useMemo 都会计算并返回缓存值(因为没有依赖任何会变化的值 )。如果依赖数组中有其他变量,只有当这些变量发生变化时,useMemo 才会重新计算缓存值。

useCallback

useState 更适合管理那些需要频繁更新并且每次更新都需要反映在 UI 上的状态。
useMemo 更适合用于性能优化,避免不必要的计算和渲染。
  • 在组件多次重新渲染的时候缓存函数**

  • 使用useCallback包裹函数之后,函数可以保证APP重新渲染的时候保持引用稳定

  • useMemo 完全可以用来缓存函数,但它的主要设计目的是缓存任意类型的值(包括函数)。而 useCallback 实际上是 useMemo 的语法糖,专门为缓存函数设计,让代码更简洁直观。

  • https://yuanbao.tencent.com/bot/app/share/chat/Ddkl3In8Me63

React.forwardRef

useRef

import { useEffect, useRef} from "react";function App() {const IptRef = useRef(null);useEffect(()=>{console.log("IptRef", IptRef,IptRef.current);})console.log("IptRef", IptRef,IptRef.current);return (<div><input type="text" ref={IptRef} /></div>);
}export default App;
  • 运行结果
    在这里插入图片描述
    在这里插入图片描述https://yuanbao.tencent.com/bot/app/share/chat/Je6uERI6fy4

  • 使用ref暴露DOM节点给父组件

  • 在这里插入图片描述

useInperativeHandle

useImperativeHandle 是一个 React Hook,它用于在子组件中自定义通过 ref 暴露给父组件的实例方法或属性 。通过 useImperativeHandle,可以有选择性地将子组件的某些功能或数据暴露给父组件,而不是直接将整个子组件实例或 DOM 元素暴露出去,从而更好地控制子组件对外的接口。

通过ref暴露子组件中的方法
在这里插入图片描述

const Input = forwardRef((props, ref) => {const inputRef = useRef(null)// 实现聚焦逻辑函数const focusHandler = () => {inputRef.current.focus()}// 暴露函数给父组件调用useImperativeHandle(ref, () => {return {focusHandler}})return <input type="text" ref={inputRef} />
})// 父组件
function App() {const sonRef = useRef(null)const focusHandler = () => {console.log(sonRef.current)sonRef.current.focusHandler()}return (<><Son ref={sonRef} /><button onClick={focusHandler}>focus</button></>)
}

forwardRef 和 useImperativeHandle的区别

  • 目的不同:forwardRef 主要是为了实现 ref 的传递,让父组件能够直接引用子组件内部的元素或实例;useImperativeHandle 则是用于在子组件中定制通过 ref 暴露给父组件的内容。
  • 使用位置不同:forwardRef 是一个高阶函数,用于包裹组件定义;useImperativeHandle 是一个 Hook,只能在函数组件内部使用。
  • 对组件封装性的影响不同:forwardRef 一定程度上打破了组件的封装,让父组件能直接访问子组件内部;useImperativeHandle 则在保证封装性的前提下,有选择地暴露功能给父组件 。

相关文章:

  • React 生命周期与 Hook 理解解析
  • Docker基础 -- Ubuntu 22.04 AArch64 交叉编译 Docker 镜像构建指南
  • [CSS3]rem移动适配
  • 防火墙的SD-WAN功能
  • jeecg-boot vue点击左侧菜单跳转无菜单栏的全屏页面
  • 5月26日星期一今日早报简报微语报早读
  • 数据结构-查找(1)
  • 机器学习多分类逻辑回归和二分类神经网络实践
  • 如何最简单、通俗地理解Pytorch?神经网络中的“梯度”是怎么自动求出来的?PyTorch的动态计算图是如何实现即时执行的?
  • 3d tiles高级样式设计与条件渲染
  • [面试精选] 0053. 最大子数组和
  • 小土堆pytorch--优化器
  • uniapp-商城-71-shop(4-商品列表,详情页中添加商品到购物车的处理)
  • 2025年燃气从业人员考试题库及答案
  • Java高频面试之并发编程-21
  • Composer 常规操作说明与问题处理
  • 遥控系统实时响应方案科普:事件触发(0/1) vs. 心跳轮询
  • Vue条件渲染
  • 【Webtrees 用户手册】第 2 章 - 访客须知
  • 特征分解:线性代数在AI大模型中的核心工具
  • 网站建设开发流程按钮/seo关键词排名如何
  • 惠安网站建设/千万别手贱在百度上搜这些词
  • 专业网站优化seo/怎么快速推广app
  • 机械公司网站建设/网络广告
  • 怎么给网站做快照/app运营推广策划方案
  • 易语言怎么用网站做背景音乐/sem是什么意思呢