乐闻世界logo
搜索文章和话题

React面试题手册

React Native 性能优化有哪些常用技巧?如何避免常见的性能陷阱?

回答要点React Native 性能优化概览React Native 应用性能优化涉及多个层面,包括渲染优化、内存管理、网络优化和原生模块优化等。1. 渲染优化避免不必要的重渲染import React, { memo, useCallback, useMemo } from 'react';// 使用 React.memo 包裹纯展示组件const ListItem = memo(({ item, onPress }) => { return ( <TouchableOpacity onPress={onPress}> <Text>{item.title}</Text> </TouchableOpacity> );});function ParentComponent() { const [data, setData] = useState([]); // 使用 useCallback 缓存回调函数 const handlePress = useCallback((id) => { console.log('点击:', id); }, []); // 使用 useMemo 缓存计算结果 const sortedData = useMemo(() => { return data.sort((a, b) => b.score - a.score); }, [data]); return ( <FlatList data={sortedData} renderItem={({ item }) => ( <ListItem item={item} onPress={() => handlePress(item.id)} /> )} /> );}列表优化<FlatList data={largeDataSet} renderItem={renderItem} keyExtractor={item => item.id} // 性能优化属性 initialNumToRender={10} // 减少首屏渲染数量 maxToRenderPerBatch={10} // 控制每批渲染量 windowSize={5} // 可视区域外缓存的屏幕数 removeClippedSubviews={true} // 移除屏幕外视图 getItemLayout={getItemLayout} // 跳过布局计算 updateCellsBatchingPeriod={50} // 控制更新频率/>2. 图片优化图片加载优化import FastImage from 'react-native-fast-image';// 使用 react-native-fast-image 替代原生 Image<FastImage source={{ uri: 'https://example.com/image.jpg', priority: FastImage.priority.normal, cache: FastImage.cacheControl.immutable, }} style={{ width: 200, height: 200 }} resizeMode={FastImage.resizeMode.cover}/>图片尺寸优化使用适当尺寸的图片,避免加载过大图片实现图片懒加载使用 WebP 格式减少体积压缩图片质量(建议 80% 左右)3. 内存优化避免内存泄漏import { useEffect, useRef } from 'react';function MyComponent() { const isMounted = useRef(true); useEffect(() => { fetchData().then(data => { // 检查组件是否仍然挂载 if (isMounted.current) { setData(data); } }); return () => { isMounted.current = false; }; }, []);}及时清理资源useEffect(() => { const subscription = eventEmitter.addListener('event', handler); const timer = setInterval(callback, 1000); return () => { // 清理订阅和定时器 subscription.remove(); clearInterval(timer); };}, []);4. JS 引擎优化使用 Hermes 引擎// android/app/build.gradleproject.ext.react = [ enableHermes: true]// ios/Podfileuse_react_native!( :hermes_enabled => true)Hermes 优势:启动时间减少 50%内存占用减少 30%支持字节码预编译更好的崩溃报告5. 原生模块优化减少 Bridge 通信// ❌ 避免频繁的小数据传输for (let i = 0; i < 100; i++) { NativeModules.MyModule.process(i); // 100 次 Bridge 调用}// ✅ 批量传输数据NativeModules.MyModule.processBatch(Array.from({length: 100}, (_, i) => i));使用 TurboModules(新架构)按需加载原生模块减少启动时间支持同步调用6. 网络优化请求优化// 使用缓存策略const fetchWithCache = async (url) => { const cached = await AsyncStorage.getItem(url); if (cached) return JSON.parse(cached); const response = await fetch(url); const data = await response.json(); await AsyncStorage.setItem(url, JSON.stringify(data)); return data;};// 请求去重const pendingRequests = new Map();const dedupedFetch = (url) => { if (pendingRequests.has(url)) { return pendingRequests.get(url); } const promise = fetch(url).finally(() => { pendingRequests.delete(url); }); pendingRequests.set(url, promise); return promise;};7. 动画性能使用原生驱动动画import { Animated } from 'react-native';const fadeAnim = new Animated.Value(0);Animated.timing(fadeAnim, { toValue: 1, duration: 1000, useNativeDriver: true, // 使用原生驱动}).start();避免在动画中执行重操作// ❌ 避免在动画回调中执行复杂操作Animated.timing(value, { toValue: 1, duration: 1000,}).start(() => { // 避免在这里执行耗时操作 heavyComputation();});8. 常见性能陷阱及解决方案| 问题 | 原因 | 解决方案 ||------|------|---------|| 列表卡顿 | 渲染过多项目 | 使用 FlatList,启用虚拟化 || 内存泄漏 | 未清理订阅 | useEffect 返回清理函数 || 启动慢 | JS Bundle 过大 | 启用 Hermes,代码分割 || 图片加载慢 | 图片过大 | 使用 FastImage,压缩图片 || 重渲染频繁 | 状态更新不当 | 使用 memo, useCallback || 白屏时间长 | 同步任务阻塞 | 使用 InteractionManager |9. 性能监控import { InteractionManager } from 'react-native';// 延迟执行非关键任务InteractionManager.runAfterInteractions(() => { // 在动画和交互完成后执行 loadHeavyData();});// 使用 Performance APIconst measurePerformance = () => { const start = performance.now(); // 执行操作 const end = performance.now(); console.log(`操作耗时: ${end - start}ms`);};10. 性能优化检查清单[ ] 使用 FlatList 替代 ScrollView 展示长列表[ ] 组件使用 React.memo 优化[ ] 回调函数使用 useCallback 缓存[ ] 复杂计算使用 useMemo 缓存[ ] 启用 Hermes JavaScript 引擎[ ] 使用 FastImage 加载图片[ ] 动画使用 useNativeDriver[ ] 及时清理订阅和定时器[ ] 实现图片懒加载[ ] 使用 InteractionManager 延迟非关键任务
阅读 0·3月6日 21:27

如何在 React Query 中实现乐观更新,它有什么优缺点?

乐观更新是 React Query 中的一个强大特性,它允许应用在服务器响应之前就更新 UI,提供更流畅的用户体验。实现乐观更新的方法在 React Query 中,使用 useMutation 钩子实现乐观更新的基本步骤如下:配置 useMutation: const mutation = useMutation(updateTodo, { // 乐观更新函数 onMutate: async (updatedTodo) => { // 1. 取消相关查询的重复请求 await queryClient.cancelQueries('todos'); // 2. 获取当前缓存数据作为回滚状态 const previousTodos = queryClient.getQueryData('todos'); // 3. 乐观更新缓存 queryClient.setQueryData('todos', (oldTodos) => oldTodos.map((todo) => todo.id === updatedTodo.id ? updatedTodo : todo ) ); // 4. 返回回滚函数 return { previousTodos }; }, // 错误处理,回滚数据 onError: (err, updatedTodo, context) => { queryClient.setQueryData('todos', context.previousTodos); }, // 无论成功失败,都重新获取最新数据 onSettled: () => { queryClient.invalidateQueries('todos'); }, });调用 mutation: mutation.mutate({ id: 1, title: 'Updated Todo' });乐观更新的优点更好的用户体验:用户操作后立即看到反馈,无需等待服务器响应减少感知延迟:即使网络较慢,UI 也能快速响应更接近原生应用体验:操作响应速度快,感觉更流畅简化状态管理:无需手动管理加载状态和临时 UI 状态乐观更新的缺点实现复杂度增加:需要编写更多代码来处理乐观更新和回滚逻辑可能出现数据不一致:如果服务器请求失败,用户可能已经看到了更新后的 UI,然后又回滚需要考虑并发操作:如果多个乐观更新同时发生,可能会导致冲突调试难度增加:问题可能出现在乐观更新逻辑、回滚逻辑或服务器响应处理中最佳实践适用于简单操作:乐观更新最适合简单的 CRUD 操作,如更新任务状态、编辑文本等提供视觉反馈:可以在乐观更新后显示一个临时状态指示器,如加载动画合理处理错误:确保在服务器失败时正确回滚数据,并向用户显示错误信息考虑网络状况:在网络不稳定的环境下,可能需要调整乐观更新策略乐观更新是一把双刃剑,正确使用可以显著提升用户体验,但需要谨慎处理各种边界情况。
阅读 0·3月6日 21:24

在实际项目中,如何组织和管理 React Query 的查询,有哪些项目结构和命名约定的最佳实践?

在实际项目中,合理组织和管理 React Query 的查询对于代码的可维护性和可扩展性至关重要:项目结构组织查询函数分离将数据获取逻辑从组件中分离出来创建专门的 API 或服务层示例结构: src/ ├── api/ │ ├── index.js │ ├── users.js │ └── posts.js ├── components/ └── pages/自定义钩子封装创建自定义钩子封装常用查询逻辑提供统一的接口和配置示例: // src/hooks/useUsers.js import { useQuery } from 'react-query'; import { fetchUsers } from '../api/users'; export const useUsers = (options = {}) => { return useQuery('users', fetchUsers, { staleTime: 5 * 60 * 1000, ...options, }); };查询键管理创建统一的查询键常量或工具函数确保查询键的一致性和可维护性示例: javascript // src/utils/queryKeys.js export const queryKeys = { users: 'users', user: (id) => ['user', id], posts: 'posts', userPosts: (userId) => ['posts', 'user', userId], };命名约定查询键命名使用描述性的名称对于动态参数,使用数组形式遵循一致的命名模式(如 [resource, id, action])自定义钩子命名使用 use 前缀名称应反映查询的用途示例:useUsers, useUserPosts, useCreateUserAPI 函数命名使用清晰的动词+名词结构示例:fetchUsers, createUser, updatePost最佳实践全局配置在应用入口配置 QueryClient设置合理的默认选项示例: // src/App.js import { QueryClient, QueryClientProvider } from 'react-query'; const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 30000, cacheTime: 60000, retry: 2, }, }, }); function App() { return ( <QueryClientProvider client={queryClient}> {/* 应用组件 */} </QueryClientProvider> ); }查询分组和层次结构使用层次化的查询键便于批量操作和失效示例: // 层次化查询键 const userQueryKey = ['users', userId]; const userPostsQueryKey = ['users', userId, 'posts']; // 批量失效 queryClient.invalidateQueries(['users', userId]);代码分割和懒加载对于大型应用,考虑代码分割按需加载查询逻辑测试策略模拟 QueryClient 和查询响应测试组件在不同查询状态下的表现示例: // 使用 React Testing Library import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; test('renders data when query succeeds', async () => { const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: Infinity, }, }, }); // 预填充缓存 queryClient.setQueryData('todos', [{ id: 1, title: 'Test Todo' }]); render( <QueryClientProvider client={queryClient}> <TodoList /> </QueryClientProvider> ); expect(await screen.findByText('Test Todo')).toBeInTheDocument(); });文档和注释为复杂查询添加注释记录查询的用途、缓存策略和依赖关系通过遵循这些最佳实践,可以创建更加结构化、可维护和可扩展的 React Query 代码库,提高开发效率和代码质量。
阅读 0·3月5日 23:34

Qwik 和 React 有什么区别?

Qwik 和 React 在架构上有几个关键区别,主要体现在加载策略、状态管理和性能优化方面:1. 加载策略Qwik:采用"按需加载"策略,所有 JavaScript 代码默认都是延迟加载的只有当用户与页面交互时,才会加载和执行相关的代码不需要下载整个应用程序包,而是按需加载单个函数或组件React:通常需要下载整个应用程序包(或多个 chunk)使用代码分割(Code Splitting)来实现懒加载,但需要手动配置即使使用 SSR,仍需要下载 hydration 代码2. 水合(Hydration)Qwik:不需要传统的水合过程通过恢复性(Resumability)直接从 HTML 中恢复状态和功能事件监听器通过 HTML 属性直接附加,无需 JavaScript 执行React:必须进行水合过程,重新执行 JavaScript 来附加事件监听器水合过程需要执行大量 JavaScript,影响首屏性能使用 React 18 的 Selective Hydration 可以部分优化,但仍不如 Qwik3. 状态管理Qwik:使用 useSignal 和 useStore 进行状态管理状态变化会自动触发细粒度的更新状态序列化到 HTML 中,可以在客户端直接恢复React:使用 useState、useReducer、Context API 等进行状态管理状态变化会触发组件重新渲染需要额外的状态管理库(如 Redux、Zustand)来管理复杂状态4. 性能优化Qwik:编译器自动优化代码分割和加载零 JavaScript 启动成本自动实现细粒度的更新,避免不必要的重新渲染React:需要手动优化性能(如 useMemo、useCallback)使用 React.memo 来避免不必要的重新渲染需要开发者有深入的性能优化知识5. 开发体验Qwik:语法与 React 相似,学习曲线较平缓编译器处理大部分优化工作不需要关心代码分割和加载策略React:生态系统成熟,有丰富的第三方库社区支持强大,文档完善需要开发者手动处理性能优化6. 适用场景Qwik:适合需要极致性能的应用内容密集型网站需要良好 SEO 的应用大型企业级应用React:适合各种规模的应用团队已经熟悉 React 生态需要丰富的第三方库支持快速原型开发总结:Qwik 通过其独特的恢复性架构,在性能方面优于 React,特别是在首屏加载和交互响应方面。但 React 的生态系统和社区支持更加成熟,适合更广泛的应用场景。
阅读 0·2月21日 15:37

如何在 React 中使用 MobX 的 observer?

在 MobX 中,observer 是一个高阶组件(HOC),用于将 React 组件转换为响应式组件。当组件使用的数据发生变化时,组件会自动重新渲染。observer 的基本用法1. 类组件中使用 observerimport React from 'react';import { observer } from 'mobx-react';import { observable } from 'mobx';class Store { @observable count = 0;}const store = new Store();@observerclass Counter extends React.Component { render() { return ( <div> <p>Count: {store.count}</p> <button onClick={() => store.count++}>Increment</button> </div> ); }}2. 函数组件中使用 observerimport React from 'react';import { observer } from 'mobx-react-lite';import { observable } from 'mobx';class Store { @observable count = 0;}const store = new Store();const Counter = observer(() => { return ( <div> <p>Count: {store.count}</p> <button onClick={() => store.count++}>Increment</button> </div> );});observer 的工作原理1. 组件挂载时MobX 创建一个 reaction 来追踪组件 render 函数中访问的所有 observable建立组件与 observable 之间的依赖关系2. 状态变化时当 observable 被修改时,MobX 检测到依赖变化将组件标记为需要重新渲染在下一个事件循环中,触发组件的重新渲染3. 组件卸载时自动清理 reaction 和依赖关系避免内存泄漏observer 的优化特性1. 细粒度更新observer 只会重新渲染真正需要更新的组件:@observerclass Parent extends React.Component { render() { return ( <div> <ChildA /> <ChildB /> </div> ); }}@observerclass ChildA extends React.Component { render() { // 只依赖 store.count return <div>Count: {store.count}</div>; }}@observerclass ChildB extends React.Component { render() { // 只依赖 store.name return <div>Name: {store.name}</div>; }}当 store.count 变化时,只有 ChildA 会重新渲染,ChildB 不会。2. shouldComponentUpdate 优化observer 会自动实现 shouldComponentUpdate,避免不必要的渲染:只有当组件依赖的 observable 真正变化时才重新渲染即使父组件重新渲染,子组件也可能不会重新渲染3. 批量更新多个状态变化会被批量处理,只触发一次重新渲染:runInAction(() => { store.count++; store.name = 'New Name';});observer 的最佳实践1. 只在需要的地方使用 observer不是所有组件都需要 observer,只在需要响应状态变化的组件上使用:// 不需要 observerconst Header = () => <h1>My App</h1>;// 需要 observerconst Counter = observer(() => { return <div>Count: {store.count}</div>;});2. 避免在 render 中创建新对象在 render 中创建新对象会导致不必要的重新渲染:// 不好的做法const BadComponent = observer(() => { const style = { color: 'red' }; // 每次渲染都创建新对象 return <div style={style}>{store.count}</div>;});// 好的做法const style = { color: 'red' }; // 在组件外部定义const GoodComponent = observer(() => { return <div style={style}>{store.count}</div>;});3. 使用 computed 优化计算在组件外部使用 computed 来优化计算逻辑:// 不好的做法const BadComponent = observer(() => { const fullName = `${store.firstName} ${store.lastName}`; return <div>{fullName}</div>;});// 好的做法class Store { @observable firstName = 'John'; @observable lastName = 'Doe'; @computed get fullName() { return `${this.firstName} ${this.lastName}`; }}const GoodComponent = observer(() => { return <div>{store.fullName}</div>;});4. 使用 React.memo 配合 observer对于纯展示组件,可以结合 React.memo 使用:const PureComponent = React.memo(observer(() => { return <div>{store.count}</div>;}));常见问题1. 组件不更新确保:组件被 observer 包装访问的是 observable 而不是普通对象状态修改在 action 中进行2. 过度渲染如果组件过度渲染,检查:是否在 render 中创建了新对象是否使用了 computed 来优化计算是否可以拆分组件以减少依赖3. 内存泄漏确保:组件卸载时 observer 会自动清理手动创建的 reaction 需要手动清理总结observer 是 MobX 与 React 集成的核心,它通过细粒度的依赖追踪实现了高效的响应式更新。正确使用 observer 和遵循最佳实践,可以构建高性能的 React 应用。
阅读 0·2月19日 17:58

React 如何做性能优化?有哪些常见手段?

React 在性能优化方面提供了多种策略和工具,以确保用户界面高效、平滑且响应迅速。以下是一些常用的性能优化手段:1. 使用 shouldComponentUpdate 和 React.PureComponent在类组件中,通过实现 shouldComponentUpdate 方法可以控制组件是否需要更新。当组件的状态或属性改变时,此方法会被调用,并根据返回的布尔值决定是否进行渲染。javascriptshouldComponentUpdate(nextProps, nextState) { // 只有当特定的属性或状态改变时才更新组件 return nextProps.id !== this.props.id || nextState.count !== this.state.count;}对于那些拥有不可变的属性和状态的组件,可以使用 React.PureComponent,它通过浅比较 props 和 state 来减少不必要的渲染。2. 使用 Hooks(如 React.memo 和 useMemo)对于函数组件,React.memo 是一个高阶组件,它仅在组件的 props 发生变化时才会重新渲染组件。javascriptconst MyComponent = React.memo(function MyComponent(props) { /* 只有props改变时,组件才会重新渲染 */});useMemo 和 useCallback 钩子可以用来缓存复杂计算的结果和回调函数,避免在每次渲染时都重新计算和创建新的函数实例。javascriptconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);const memoizedCallback = useCallback(() => { // 一个依赖特定props的回调函数}, [props]);3. 避免不必要的 DOM 更新当操作 DOM 时,应尽量减少更新次数和范围。可以使用虚拟列表(比如 react-window 或 react-virtualized)来仅渲染可视区域的元素,从而提高长列表的性能。4. 懒加载组件和路由使用 React.lazy 可以实现组件级别的代码拆分,这样可以将不同的组件打包成单独的代码块,并在需要时才加载它们。同时,结合 React Router 的 Suspense 组件,可以实现路由级别的懒加载,仅当路由被访问时才加载对应的组件。javascriptconst OtherComponent = React.lazy(() => import('./OtherComponent'));function MyComponent() { return ( React.Suspense fallback={div>Loading.../div>}> OtherComponent /> /React.Suspense> );}5. 使用 Web Workers对于复杂或计算密集型任务,可以使用 Web Workers 在后台线程中执行,避免阻塞主线程导致用户界面卡顿。6. 优化条件渲染避免不必要的渲染,例如,可以将条件渲染逻辑移到可能更改状态的事件处理函数中,而不是在渲染方法中进行。7. 状态升级将子组件的本地状态提升到父组件中,这样可以减少不必要的子组件渲染,因为状态的变化会集中处理。8. 使用不可变数据结构使用不可变数据可以更容易地检测到状态和属性的变化,这使得组件的更新检查更高效。库如 Immutable.js 可以用来帮助创建不可变数据。9. 使用生产版本的 React开发中通常使用的是开发版本的 React,它包含了许多有用的警告和错误信息。但在生产中,应该使用经过压缩和优化的生产版本,它删除了这些额外的警告和检查,以减少库的大小并提升性能。10. 分析和监控使用性能分析工具,如 React DevTools 中的 Profiler,可以帮助识别渲染性能瓶颈。它可以记录组件的渲染时间,并帮助你找到可以优化的部分。11. 避免内联对象和数组的传递对于那些接收对象或数组作为 props 的组件,应避免在渲染方法中直接创建新的内联对象或数组,因为这会导致 props 始终不相等,从而触发不必要的渲染。<MyComponent items={[1, 2, 3]} /> // 每次渲染都会创建一个新的数组,不推荐这样做// 更好的做法是在组件外部定义这个数组const items = [1, 2, 3];<MyComponent items={items} />12. 使用键(keys)来优化列表渲染当渲染列表时,应该为每个列表项指定一个唯一的 key。这有助于 React 确定哪些项已更改、添加或删除,从而提高列表渲染的效率。data.map((item) => <ListItem key={item.id} {...item} />)13. 使用 Context 时的优化当使用 React Context API 时,应该注意其可能对性能的影响。Context 的变动会导致所有消费该 Context 的组件重新渲染。为了避免不必要的渲染,可以分割 Context 或是使用 useMemo 跟 useCallback 来传递稳定的上下文值。14. 避免过度渲染和过度传递 props审视组件间的 props 传递,确保不会传递额外的 props。如果一个组件不需要某个 prop,那么就不应该传递它,因为这可能会导致不必要的组件渲染。15. 服务器端渲染 (SSR)服务器端渲染可以加快首次页面加载时间,并提升搜索引擎优化(SEO)。通过在服务器上生成 HTML,可以减少客户端的工作量,从而提升性能。实施示例假设我们有一个用户列表组件,其中包含大量用户数据。我们可以应用以下优化:使用 React.memo 封装用户列表项组件,仅在 props 变化时重新渲染。通过 useMemo 缓存用户列表计算,避免在每次渲染时重新计算。使用虚拟列表库,如 react-window,仅渲染可视区域内的用户,以优化长列表性能。如果用户列表是通过路由导航到达的,可以使用 React.lazy 和 Suspense 实现路由懒加载。通过 React DevTools 的 Profiler 分析用户列表的渲染性能,找出任何潜在的性能瓶颈进行优化。通过应用这些优化技巧,我们可以显著提高大型 React 应用的性能和用户体验。
阅读 330·2024年8月5日 12:52

React hook 使用需要注意哪些?

在使用 React Hooks 时需要遵循一些最佳实践和注意事项,以确保代码的可维护性与功能的正确性。以下是一些关键点:1. 遵守Hooks规则不要在循环、条件或嵌套函数中调用HooksHooks 应该始终在组件的顶层被调用,这样可以保证 Hooks 在每次渲染时都以相同的顺序被调用,这对于 React 的内部状态追踪机制非常重要。只在React函数中调用Hooks应该仅在React的函数组件或自定义 Hooks 中调用 Hooks。不要在普通的 JavaScript 函数中调用。2. 使用 useState时的注意事项初始化状态对于复杂的状态逻辑,可以通过传递一个函数给 useState 来惰性初始化,这样可以避免在每次渲染时重新创建初始状态。const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState;});状态更新函数的身份稳定setState 函数是身份稳定的,这意味着你可以在其他 Hooks 中安全地引用它,而不用担心它会在重新渲染时改变。3. 使用 useEffect时的注意事项清理副作用在 useEffect 中创建的订阅、定时器、监听事件等副作用,应该在返回的清理函数中进行清除,以避免内存泄漏。useEffect(() => { const subscription = props.source.subscribe(); return () => { // 清理订阅 subscription.unsubscribe(); };}, [props.source]);依赖列表的完整性确保依赖列表包含了所有外部作用域中被 useEffect 使用到的值,这样才能正确响应这些值的变化。如果忽略了依赖,可能会导致旧的闭包中的值被捕获,从而引发错误。useEffect(() => { function doSomething() { console.log(someProp); } doSomething();}, [someProp]); // 确保所有使用到的变量都被包含在依赖列表中4. 避免在 useEffect中进行不必要的操作节流和防抖如果 useEffect 中的操作非常昂贵,考虑使用节流(throttling)或防抖(debouncing)技术来减少操作的频率。5. 自定义Hooks代码复用当你发现需要在不同组件之间复用状态逻辑时,可以将其抽离成自定义 Hooks。这有助于减少代码冗余并增强逻辑的可维护性。例如,使用自定义 useForm Hook 来处理表单:function useForm(initialValues) { const [values, setValues] = useState(initialValues); const handleChange = (event) => { setValues({ ...values, [event.target.name]: event.target.value, }); }; return [values, handleChange];}6. 性能优化useMemo 和 useCallback在有必要的情况下,使用 useMemo 和 useCallback 来避免不必要的渲染或计算。useMemo 可以用来缓存复杂计算的结果,useCallback 可以用来缓存函数,这在将函数传递给子组件时特别有用,可以避免不必要的子组件重渲染。
阅读 154·2024年8月5日 12:50

什么是 React 的受控组件和非受控组件?

在React中,受控组件(Controlled Components)和非受控组件(Uncontrolled Components)都是处理表单输入的方式,但它们处理数据的方式不同。受控组件(Controlled Components)受控组件是React的一种模式,在这种模式下,表单数据是由React组件的状态管理的。这意味着每次字段的值发生变化时,我们都会通过一个事件处理函数(通常是 onChange)来更新组件的状态。然后,组件的状态被用作输入字段的值,确保组件的状态是数据的唯一来源。示例:假设我们有一个受控的 <input>元素:class ControlledComponent extends React.Component { constructor(props) { super(props); this.state = {value: ''}; } handleChange = (event) => { this.setState({value: event.target.value}); } render() { return ( <form> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> </form> ); }}在上面的例子中,<input>的值始终由 this.state.value决定,而且每当用户输入时,handleChange函数都会被调用,更新状态,因此界面显示的内容总是和状态同步。非受控组件(Uncontrolled Components)非受控组件是另一种模式,在这种模式下,表单数据是由DOM本身处理的,而不是由React状态管理。这就像传统的HTML表单工作方式。在非受控组件中,我们通常使用 ref来从DOM节点获取表单数据,而不是为每个状态变化编写事件处理函数。示例:下面是一个非受控组件的例子:class UncontrolledComponent extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } handleSubmit = (event) => { alert('A name was submitted: ' + this.inputRef.current.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" ref={this.inputRef} /> </label> <button type="submit">Submit</button> </form> ); }}在上面的例子中,<input>不是通过状态来控制其值,而是通过 ref来访问DOM节点获取其值。 总结受控组件允许你更好地控制表单的行为,因为组件的状态充当了数据的真实来源。非受控组件舍弃了对表单状态的即时控制,使得组件的代码更简洁,但可能会更难管理表单的状态,尤其是在复杂的表单交互时。在实际的开发实践中,受控组件通常是首选方法,因为它们更加符合React的数据流概念,使得状态的管理更加清晰和可预测。然而,对于一些简单的表单或者集成第三方DOM库时,非受控组件也可能是一个不错的选择。
阅读 147·2024年8月5日 12:48

React 如何使用异步组件以及异步组件的使用场景

React 的异步组件(通常被称为懒加载组件)主要是通过动态 import() 语法和 React 的 React.lazy 函数来实现的。它们用于在需要时才加载组件,可以显著提高应用程序的性能,尤其是当应用程序很大并且有许多不同的页面和组件时。接下来,我会详细介绍如何使用异步组件以及它们的使用场景。 如何使用异步组件使用 React 异步组件的基本步骤如下:使用 React.lazy 函数分别导入组件。这个函数允许你定义一个动态导入的组件。该函数接受一个函数,这个函数必须调用一个 import(),它返回一个 Promise,该 Promise 解析为一个有 default 导出的模块。 const AsyncComponent = React.lazy(() => import('./AsyncComponent'));将 React.lazy 返回的组件与 React.Suspense 组件结合使用。Suspense 组件允许你指定加载指示器(例如:加载中的旋转器),在等待异步组件加载时显示给用户。 import React, { Suspense } from 'react'; // 异步导入组件 const AsyncComponent = React.lazy(() => import('./AsyncComponent')); function App() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <AsyncComponent /> </Suspense> </div> ); }使用场景性能优化: 对于大型应用程序,将不同的页面或功能分割成独立的代码块,然后只在用户需要时才加载,可以减少应用程序的初始负载时间。条件渲染组件: 当一个组件只在某些条件下才需要时,例如特定的用户角色或权限,可以使用异步组件按需加载,从而节省资源。路由懒加载: 在使用如 React Router 这样的库进行路由管理时,可以结合 React.lazy 和 Suspense 来实现路由级别的懒加载。 import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import React, { Suspense } from 'react'; const Home = React.lazy(() => import('./Home')); const About = React.lazy(() => import('./About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> </Switch> </Suspense> </Router> );组件库懒加载: 如果你的应用程序使用了庞大的第三方组件库,而只有少数组件被频繁使用,可以选择仅懒加载那些较少使用的组件,以减少初始包的大小。使用异步组件的主要目标是提升用户体验,减少页面加载时间,并且按需加载资源,避免浪费客户端的计算和带宽资源。React 的懒加载功能是实现上述目标的重要手段之一。
阅读 271·2024年8月5日 12:48

React Router 是如何配置组件的懒加载?

React Router 可以通过配合 React 的 React.lazy() 和 Suspense 组件来配置组件的懒加载。以下是使用 React Router 实现懒加载的基本步骤:使用 React.lazy 实现动态导入: React.lazy() 是一个允许你动态加载组件的函数。它可以让你定义一个动态导入的组件,并且这个组件会在首次渲染时自动加载。 const LazyComponent = React.lazy(() => import('./LazyComponent'));使用 Suspense 组件包裹路由: 在你的应用中,你需要使用 Suspense 组件来包裹懒加载的路由。Suspense 可以指定一个加载指示器(比如一个 spinner),它会在懒加载组件加载完成之前显示。 import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import React, { Suspense } from 'react'; const LazyComponent = React.lazy(() => import('./LazyComponent')); function App() { return ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route path="/lazy" component={LazyComponent} /> {/* 其他路由 */} </Switch> </Suspense> </Router> ); }为懒加载组件创建独立的 chunk: 当你使用 create-react-app 或其他构建工具时,它会为每个用 React.lazy() 引入的组件自动创建一个独立的 JavaScript chunk 文件。这意味着这些代码只会在用户需要时才会被加载。举个例子,假设你有一个很大的组件 BigComponent,你不希望它在应用首次加载时就加载进来,而是希望当用户真正访问到该组件对应的路由时再加载,你可以这样设置:import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';import React, { Suspense } from 'react';const BigComponent = React.lazy(() => import('./BigComponent'));function App() { return ( <Router> <Suspense fallback={<div>Loading Big Component...</div>}> <Switch> <Route path="/big-component" component={BigComponent} /> {/* 其他路由 */} </Switch> </Suspense> </Router> );}在上述例子中,当用户访问 /big-component 路径时,BigComponent 会被动态加载。用户会看到 "Loading Big Component…" 的文本,直到 BigComponent 加载完成并准备好渲染。这样可以减少应用的初始加载时间,并且按需加载资源,提高性能。
阅读 331·2024年8月5日 12:48