三、 React 进阶主题


文档摘要

三、 React 进阶主题 三、React 进阶主题:深入组件优化、状态管理与高级Hooks 组件优化 React组件的性能至关重要。不必要的渲染会导致应用响应缓慢,影响用户体验。以下是一些常用的组件优化策略: 1.1. 是一个高阶组件,用于记忆组件的渲染结果。它会浅比较组件的props,如果props没有发生变化,则跳过渲染。这可以有效地减少不必要的渲染。 代码详解: 接受一个组件作为参数,并返回一个记忆化的组件。 每次 渲染时, 会浅比较传入的 prop。 如果 prop没有发生变化, 会直接返回之前渲染的结果,跳过 的渲染。 注意: 默认进行浅比较,对于复杂对象,可能需要自定义比较函数。 1.2. 是一个Hook,用于记忆一个计算结果。它接收一个创建函数和一个依赖数组作为参数。

三、 React 进阶主题

三、React 进阶主题:深入组件优化、状态管理与高级Hooks

1. 组件优化

React组件的性能至关重要。不必要的渲染会导致应用响应缓慢,影响用户体验。以下是一些常用的组件优化策略:

1.1. React.memo

React.memo是一个高阶组件,用于记忆组件的渲染结果。它会浅比较组件的props,如果props没有发生变化,则跳过渲染。这可以有效地减少不必要的渲染。

import React from 'react'; const MyComponent = React.memo(({ data }) => { console.log('MyComponent rendered!'); return ( <div>{data.name}</div> ); }); export default MyComponent;

代码详解:

  • React.memo接受一个组件作为参数,并返回一个记忆化的组件。

  • 每次MyComponent渲染时,React.memo会浅比较传入的data prop。

  • 如果data prop没有发生变化,React.memo会直接返回之前渲染的结果,跳过MyComponent的渲染。

  • 注意: React.memo默认进行浅比较,对于复杂对象,可能需要自定义比较函数。

1.2. useMemo

useMemo是一个Hook,用于记忆一个计算结果。它接收一个创建函数和一个依赖数组作为参数。只有当依赖数组中的值发生变化时,才会重新执行创建函数,并返回新的计算结果。

import React, { useState, useMemo } from 'react'; function MyComponent({ list }) { const [filterText, setFilterText] = useState(''); const filteredList = useMemo(() => { console.log('Filtering list...'); return list.filter(item => item.name.includes(filterText)); }, [list, filterText]); return ( <div> <input type="text" value={filterText} onChange={e => setFilterText(e.target.value)} /> <ul> {filteredList.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </div> ); } export default MyComponent;

代码详解:

  • useMemo接收一个创建函数,该函数返回需要记忆的值,这里是filteredList

  • useMemo接收一个依赖数组[list, filterText]

  • 只有当listfilterText发生变化时,才会重新执行创建函数,重新过滤列表。

  • 如果没有使用useMemo,每次filterText改变,都会重新过滤整个列表,即使list没有改变,这会浪费计算资源。

1.3. useCallback

useCallback是一个Hook,用于记忆一个函数。它接收一个函数和一个依赖数组作为参数。只有当依赖数组中的值发生变化时,才会重新创建函数。这可以避免将不必要的函数传递给子组件,导致子组件不必要的渲染。

import React, { useCallback } from 'react'; function MyComponent({ onClick }) { console.log('MyComponent rendered!'); return ( <button onClick={onClick}>Click me</button> ); } const MemoizedComponent = React.memo(MyComponent); function ParentComponent() { const handleClick = useCallback(() => { console.log('Button clicked!'); }, []); return ( <MemoizedComponent onClick={handleClick} /> ); } export default ParentComponent;

代码详解:

  • useCallback接收一个函数和一个依赖数组[]

  • 由于依赖数组为空,handleClick函数只会被创建一次。

  • MemoizedComponent使用React.memo进行记忆化,并且接收onClick作为props。

  • 如果没有使用useCallback,每次ParentComponent渲染,都会创建一个新的handleClick函数,导致MemoizedComponent每次都会重新渲染,即使handleClick的功能没有改变。

1.4. 虚拟化(Virtualization)

当渲染大量数据列表时,虚拟化技术可以显著提高性能。虚拟化只渲染屏幕可见的部分,而不是整个列表。这可以减少DOM节点的数量,提高渲染速度。

常用的虚拟化库包括:

  • react-window

  • react-virtualized

示例 (使用 react-window):

import React from 'react'; import { FixedSizeList } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); function MyListComponent({ itemCount }) { return ( <FixedSizeList height={150} width={300} itemSize={30} itemCount={itemCount} > {Row} </FixedSizeList> ); } export default MyListComponent;

代码详解:

  • FixedSizeList组件来自react-window库。

  • heightwidth定义了列表容器的大小。

  • itemSize定义了每个列表项的高度。

  • itemCount定义了列表项的总数。

  • Row组件负责渲染每个列表项,style prop包含了该列表项的位置和大小信息。

1.5. 代码分割 (Code Splitting)

将应用拆分成更小的chunk,只在需要时加载。这可以减少初始加载时间,提高应用的响应速度。React提供了React.lazySuspense来实现代码分割。

import React, { Suspense, lazy } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> ); } export default App;

代码详解:

  • React.lazy接收一个函数,该函数返回一个Promise,Promise resolve为一个React组件。

  • Suspense组件用于包裹lazy加载的组件。

  • fallback prop指定了在组件加载完成之前显示的占位内容。

  • MyComponent首次渲染时,React.lazy会异步加载./MyComponent模块。

  • 在加载完成之前,Suspense会显示fallback内容。

2. 状态管理

React提供了多种状态管理方案,选择合适的方案对于构建复杂应用至关重要。

2.1. Context API

Context API是React内置的状态管理方案,适用于管理应用级别的数据,例如主题、用户认证信息等。

import React, { createContext, useState, useContext } from 'react'; const ThemeContext = createContext(); function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(theme === 'light' ? 'dark' : 'light'); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); } function useTheme() { return useContext(ThemeContext); } function MyComponent() { const { theme, toggleTheme } = useTheme(); return ( <div style={{ backgroundColor: theme === 'light' ? 'white' : 'black', color: theme === 'light' ? 'black' : 'white' }}> <h1>Theme: {theme}</h1> <button onClick={toggleTheme}>Toggle Theme</button> </div> ); } export { ThemeProvider, useTheme, MyComponent };

代码详解:

  • createContext创建一个Context对象。

  • ThemeProvider组件使用ThemeContext.Provider提供Context值,该值包含themetoggleTheme

  • useTheme Hook使用useContext Hook消费Context值。

  • MyComponent组件使用useTheme Hook获取themetoggleTheme,并根据theme改变背景颜色和文字颜色。

2.2. Redux

Redux是一个流行的JavaScript状态容器,适用于管理复杂应用的状态。它遵循单一数据源、不可变性和纯函数的原则。

核心概念:

  • Store: 存储应用的状态。

  • Actions: 描述发生了什么。

  • Reducers: 根据Actions更新状态。

  • Dispatch: 触发Actions。

  • Selectors: 从Store中获取数据。

示例:

// actions.js export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; export const increment = () => ({ type: INCREMENT }); export const decrement = () => ({ type: DECREMENT }); // reducer.js const initialState = { count: 0, }; function counterReducer(state = initialState, action) { switch (action.type) { case INCREMENT: return { ...state, count: state.count + 1 }; case DECREMENT: return { ...state, count: state.count - 1 }; default: return state; } } export default counterReducer; // store.js import { createStore } from 'redux'; import counterReducer from './reducer'; const store = createStore(counterReducer); export default store; // component.js import React from 'react'; import { connect } from 'react-redux'; import { increment, decrement } from './actions'; function Counter({ count, increment, decrement }) { return ( <div> <h1>Count: {count}</h1> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); } const mapStateToProps = state => ({ count: state.count, }); const mapDispatchToProps = { increment, decrement, }; export default connect(mapStateToProps, mapDispatchToProps)(Counter);

代码详解:

  • actions.js定义了Action类型和Action创建函数。

  • reducer.js定义了Reducer函数,根据Action类型更新状态。

  • store.js创建Redux Store,并传入Reducer函数。

  • component.js使用connect函数连接React组件和Redux Store。

  • mapStateToProps函数将Store中的状态映射到组件的props。

  • mapDispatchToProps函数将Action创建函数映射到组件的props。

2.3. Zustand

Zustand是一个小型、快速且可扩展的状态管理库。它提供了一个简单的API,可以轻松地创建和管理状态。

import create from 'zustand'; const useStore = create(set => ({ count: 0, increment: () => set(state => ({ count: state.count + 1 })), decrement: () => set(state => ({ count: state.count - 1 })), })); function Counter() { const { count, increment, decrement } = useStore(); return ( <div> <h1>Count: {count}</h1> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); } export default Counter;

代码详解:

  • create函数创建一个Store。

  • set函数用于更新状态。

  • useStore Hook用于消费Store中的状态和Action。

2.4. Jotai

Jotai是一个基于原子(atoms)的状态管理库。它提供了一种简单而灵活的方式来管理状态。

import { atom, useAtom } from 'jotai'; const countAtom = atom(0); function Counter() { const [count, setCount] = useAtom(countAtom); return ( <div> <h1>Count: {count}</h1> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={() => setCount(count - 1)}>Decrement</button> </div> ); } export default Counter;

代码详解:

  • atom函数创建一个原子,原子是状态的最小单位。

  • useAtom Hook用于消费原子中的状态和更新函数。

3. 高级 Hooks

React 16.8引入了Hooks,它允许你在不编写class的情况下使用state和其他React特性。以下是一些高级Hooks:

3.1. useImperativeHandle

useImperativeHandle允许你自定义暴露给父组件的实例值。它接收一个ref和一个创建函数作为参数。创建函数返回的值将成为ref的current属性。

import React, { useRef, useImperativeHandle, forwardRef } from 'react'; const MyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, getValue: () => { return inputRef.current.value; }, })); return <input type="text" ref={inputRef} />; }); function ParentComponent() { const inputRef = useRef(); const handleFocus = () => { inputRef.current.focus(); }; const handleGetValue = () => { console.log(inputRef.current.getValue()); }; return ( <div> <MyInput ref={inputRef} /> <button onClick={handleFocus}>Focus Input</button> <button onClick={handleGetValue}>Get Value</button> </div> ); } export default ParentComponent;

代码详解:

  • forwardRef用于将ref传递给子组件。

  • useImperativeHandle允许MyInput组件自定义暴露给父组件的实例值。

  • 父组件可以通过inputRef.current.focus()inputRef.current.getValue()来控制子组件的行为。

3.2. useLayoutEffect

useLayoutEffectuseEffect类似,但它会在DOM更新之后、浏览器绘制之前同步执行。这可以避免一些视觉上的闪烁问题。

区别:

  • useEffect是异步执行的,不会阻塞浏览器的绘制。

  • useLayoutEffect是同步执行的,会阻塞浏览器的绘制。

使用场景:

  • 需要读取DOM元素的尺寸或位置,并根据这些信息更新DOM。

  • 需要避免视觉上的闪烁问题。

示例:

import React, { useRef, useLayoutEffect } from 'react'; function MyComponent() { const ref = useRef(null); useLayoutEffect(() => { console.log('useLayoutEffect executed'); // 获取DOM元素的高度 const height = ref.current.offsetHeight; console.log('Height:', height); }, []); return <div ref={ref}>Hello, world!</div>; } export default MyComponent;

代码详解:

  • useLayoutEffect会在DOM更新之后、浏览器绘制之前同步执行。

  • 可以在useLayoutEffect中读取DOM元素的尺寸或位置,并根据这些信息更新DOM。

3.3. useReducer

useReducer是一个Hook,用于管理复杂的状态逻辑。它接收一个reducer函数和一个初始状态作为参数,并返回当前状态和一个dispatch函数。

import React, { useReducer } from 'react'; const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> Count: {state.count} <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div> ); } export default Counter;

代码详解:

  • useReducer接收一个reducer函数和一个初始状态作为参数。

  • reducer函数接收当前状态和一个action作为参数,并返回新的状态。

  • dispatch函数用于触发action。

3.4. 自定义 Hooks

自定义 Hooks允许你将组件逻辑提取到可重用的函数中。自定义 Hooks以use开头,可以访问其他的Hooks。

import { useState, useEffect } from 'react'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { const response = await fetch(url); const json = await response.json(); setData(json); setLoading(false); } catch (error) { setError(error); setLoading(false); } }; fetchData(); }, [url]); return { data, loading, error }; } function MyComponent() { const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1'); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return <div>{data.title}</div>; } export default MyComponent;

代码详解:

  • useFetch是一个自定义Hook,用于从API获取数据。

  • useFetch使用useState Hook管理数据、加载状态和错误。

  • useFetch使用useEffect Hook在组件挂载时发起API请求。

总结

本文深入探讨了React进阶主题,包括组件优化、状态管理和高级Hooks的使用。掌握这些知识可以帮助你构建高性能、可维护的React应用。 通过结合代码实践和详细讲解,希望能够帮助你更好地理解和应用这些概念。记住,持续学习和实践是成为一名优秀的React开发者的关键。


发布者: 作者: 转发
评论区 (0)
U