一些适用的知识点
可执行命令
start:开发时启动sum的命令
build:开发完毕后打包的命令
classnames
npm install classnames
js库
js
静态类名和动态类名
className = {classNames('nav-item',{active:type===item.type})}
受控表单绑定

js
const [value,setValue] = useState('')
js
<input
value={value}
onChange = {(e)=>setValue(e.target.value)}
/>
useRef获取dom
在react组件中获取/操作DOM,需要使用useRef钩子函数,分为两步
-
使用useRef创建ref对象,并与JSX绑定
jsconst inputRef = useRef(null) <input type="text" ref={inputRef} />
-
在DOM可用时,通过inputRef.current拿到DOM对象
jsconsole.log(inputRef.current)
使用的 lodash库
- 生成唯一的随机数---uuid
- 以当前时间为标准,生成固定格式---dayjs
组件通信
父子通信
父传子

-
props可传递任意的数据:数字、字符串、数组、对象、函数、JSX
-
props是只读对象,子组件只能读取props中的数据,不能直接进行修改,
父组件的数据只能由父组件修改
-
props.children
当我们在使用子组件标签时,在标签内部写入的内容,React 会自动将其作为一个特殊的 prop,即 props.children,传递给子组件。子组件可以通过访问这个属性来获取并渲染父组件传递过来的内容。jsimport 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> 这两个元素,子组件需要按照数组的方式去处理和渲染这些元素。
js
function Parent() {
return (
<Son>
<span>this is span</span>
<p>this is a paragraph</p>
</Son>
);
}
子传父
子组件通过调用父组件
传递过来的回调函数,将数据传递给父组件。
js
// 父组件
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
js
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 的场景
js
// 创建 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 等。它可以让函数组件拥有类似类组件中生命周期钩子的能力。
js
import React, { useEffect } from'react';
function MyComponent() {
useEffect(() => {
// 在这里编写副作用代码,比如数据获取、订阅等
console.log('副作用操作执行');
return () => {
// 在这里进行清理工作,比如取消订阅、清除定时器等
console.log('清理操作执行');
};
}, []); // 第二个参数是依赖数组
return (
<div>
{/* 组件的JSX内容 */}
</div>
);
}
useEffect依赖项

清除副作用
js
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
函数,并立即调用它。这样可以实现异步操作
js
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 () => {
// 组件卸载时的清理逻辑
};
}, []); // 依赖数组为空,仅在挂载时执行一次
- 使用立即执行的异步函数表达式
js
useEffect(() => {
(async () => {
try {
const data = await fetchData();
setData(data);
} catch (error) {
console.error(error);
}
})(); // 立即执行异步函数
return () => { /* 清理逻辑 */ };
}, []);
- 使用 Promise 链式调用
js
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(json => setData(json))
.catch(error => console.error(error));
return () => { /* 清理逻辑 */ };
}, []);
处理异步操作中的清理需求
当异步操作需要在组件卸载前取消时(如取消未完成的请求),可以使用 AbortController:
js
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?
- 解决代码重复问题
当多个组件需要实现相同的逻辑(如获取用户位置、处理表单验证)时,自定义 Hook 可以避免代码冗余。 - 分离关注点
将复杂逻辑从组件中提取出来,使组件更专注于 UI 渲染,提高代码可读性和可维护性。 - 状态逻辑复用
与普通函数不同,自定义 Hook 可以使用 React 的状态和副作用管理,保留状态的独立性。
封装自定义函数
js
// 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
};
}
使用自定义函数
js
import { useCounter } from './useCounter';
function CounterComponent() {
// 使用自定义Hook
const { 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

原始代码
js
使用 CRA 快速创建 React 项目
npx create - react - app react - redux
安装配套工具
npm i @reduxjs/toolkit react - redux
启动项目
npm run start
js
<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
- dispatch 是触发状态变更的唯一入口
- 在 Redux 中,状态(state)是只读的,不能直接修改(如 state.count++ 是被禁止的)。
- 必须通过 dispatch 发送一个 action ,才能触发 reducer 计算新状态。
类比:就像你想让手机拍照,必须按下快门键(dispatch),而不是直接修改相册文件(直接改状态)。没有快门键(dispatch),你无法告诉手机 "我要拍照"(触发动作)。
- dispatch 是连接视图层和状态层的桥梁
- 视图层(如按钮点击事件)无法直接访问或修改 reducer 和 store,必须通过 dispatch 发送 action 来 "告知" 状态层需要做什么。
javascript
// 没有 dispatch,点击事件无法触发状态变更
inBtn.addEventListener('click', () => {
// ❌ 非法操作:直接修改 state(Redux 不允许)
store.state.count += 1;
});
- 合法操作:通过 dispatch 发送 action,由 reducer 处理状态变更:
javascript
inBtn.addEventListener('click', () => {
store.dispatch({ type: 'INCREMENT' }); // ✅ 正确方式
});
- dispatch 是数据流可追踪的基础
- 所有状态变更都必须经过 dispatch,因此:
- 可以通过 Redux DevTools 追踪每一次 dispatch 的 action,查看状态变化的历史记录(时间旅行调试)。
- 可以通过中间件(如 Redux Thunk)在 dispatch 过程中拦截、处理异步逻辑(如网络请求)。
- 如果没有 dispatch,状态变更可能来自任意地方(如视图层直接修改状态),导致数据流混乱,无法追踪和调试。
使用配套工具
js
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 文件,内容如下:
js
import { createSlice } from '@reduxjs/toolkit';
// 创建一个 slice,它包含了状态、reducer 和 action creators
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 }, // 初始状态,计数器初始值为 0
reducers: {
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;
- 在 store 目录下创建 index.js 文件,用于组合模块并导出 store:
js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './modules/counterStore';
const store = configureStore({
reducer: {
counter: counterReducer, // 将 counterReducer 挂载到 store 的 counter 键下
},
});
export default store;
- 在 React 组件中使用 Redux
js
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>
<input
type="number"
placeholder="Increment by amount"
onChange={(e) => {
const amount = parseInt(e.target.value, 10);
if (!isNaN(amount)) {
dispatch(incrementByAmount(amount));
}
}}
/>
</div>
);
}
export default App;
还有第二种写法
js
const {count} = useSelector((state) => state.counter);
action传参
- 定义 reducer
js
// 引入 createSlice 用于创建 Redux slice
import { createSlice } from '@reduxjs/toolkit';
// 创建计数器 slice
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
// 传入参数增加计数器值的 action
incrementByAmount: (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 会被自动解析为传入的参数。例如:
- 在组件中使用并传参
js
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>
<input
type="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
- 定义 reducer 和相关 action
javascript
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 可以作为参数传递
- Redux-Thunk 中间件的作用
- 上述异步函数(称为 Thunk 函数)能接收 dispatch 作为参数,依赖于 Redux-Thunk 中间件的机制:
当组件调用 dispatch(thunkFunction) 时,Redux-Thunk 会拦截这个 thunkFunction,并主动将 dispatch 和 getState(store 的状态)作为参数传入。
- 因此,Thunk 函数的参数 dispatch 实际上是 store 提供的真实 dispatch 函数,可以直接用于触发 action。
-
函数参数的本质
从 JavaScript 函数的角度看:
- dispatch 是一个函数引用,可以像普通变量一样作为参数传递给其他函数。
- 异步函数接收 dispatch 作为参数,本质上是接收了一个 "触发状态更新的能力",与函数接收其他回调函数(如 onSuccess)的逻辑一致。
-
在组件中调用异步 action
js
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
js
npm i react-router-dom
核心组件
常用钩子

路由跳转
- 使用 组件 *:这是最常用的方式,类似于 HTML 中的
<a>
标签,但在 React Router 中使用它进行导航时不会重新加载整个页面,而是在单页应用内进行跳转。例如:
js
import { Link } from'react-router-dom';
function Navbar() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
);
}
- 编程式导航:通过 useNavigate 钩子函数来实现路由跳转
js
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 钩子获取参数值:
js
// 路由配置
<Route path="/user/:id" element={<UserDetail />} />
// UserDetail 组件
import { useParams } from'react-router-dom';
function UserDetail() {
const { id } = useParams();
return <div>用户 ID:{id}</div>;
}
- 跳转时可以这样写:
js
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 钩子来获取查询参数:
js
import { useSearchParams } from'react-router-dom';
function SearchResults() {
const [searchParams] = useSearchParams();
const keyword = searchParams.get('keyword');
return <div>搜索关键词:{keyword}</div>;
}
- 跳转时:
js
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>
);
}
获取当前路径
js
import { useLocation } from 'react-router-dom'
const About = () => {
// 使用 hook
const location = useLocation();
const { from, pathname } = location
return <div>这里是卡拉云的网站,你当前在 {pathname},你是从 {from} 跳转过来的</div>
}
location和useSearchParams区别
点击链接查看和 Kimi 的对话 https://kimi.moonshot.cn/share/d0dkul3ua3d2tdsjh830
404页面
我们只要在最后加入 path 为*
的一个路径,意为匹配所有路径,即可
js
function App() {
return <BrowserRouter>
<Routes>
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
}
// 用来作为 404 页面的组件
const NotFound = () => {
return <div>你来到了没有知识的荒原</div>
}
默认二级路由

子路由路径不以斜杠开头 :这样可以确保子路由路径是相对于父路由的路径来解析的,避免路由匹配出现问题。
导航路径 :在 Link
组件中,确保 to
属性的路径是正确的绝对路径。
js
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 Layout
import { 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 组件 );若不满足条件,可能会将用户重定向到登录页或提示无权限访问。示例代码如下:
js
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 组件 )进行错误处理。示例:
js
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在组建渲染的过程中缓存一个值
js
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 = 100
const list = useMemo(() => {
return [1, 2, 3]
}, [])
}
useMemo 接收两个参数,第一个参数是一个回调函数
,用于返回要缓存的值;第二个参数是一个依赖数组,这里依赖数组为空数组 [],表示只要组件重新渲染,useMemo 都会计算并返回缓存值(因为没有依赖任何会变化的值 )。如果依赖数组中有其他变量,只有当这些变量发生变化时,useMemo 才会重新计算缓存值。
useCallback
useState 更适合管理那些需要频繁更新并且每次更新都需要反映在 UI 上的状态。
useMemo 更适合用于性能优化,避免不必要的计算和渲染。
-
在组件多次重新渲染的时候缓存函数**
-
使用useCallback包裹函数之后,函数可以保证APP重新渲染的时候保持引用稳定
-
useMemo
完全可以用来缓存函数 ,但它的主要设计目的是缓存任意类型的值(包括函数)。而useCallback
实际上是useMemo
的语法糖,专门为缓存函数设计,让代码更简洁直观。
React.forwardRef
useRef
js
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暴露子组件中的方法
js
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 则在保证封装性的前提下,有选择地暴露功能给父组件 。