react中性能优化的方法
一、影响浏览器渲染效率的因素有哪些?
1. DOM操作:对DOM进行频繁的操作可能会导致性能问题。每次更改DOM时,浏览器都必须重新计算布局和绘制,这可能会导致性能下降。为了避免这种情况,可以尝试将多个DOM更改合并为单个更改,或者使用DocumentFragment来一次性添加多个元素。
2. 重绘和回流:重绘和回流是浏览器重新计算布局和绘制的过程。重绘是指浏览器重新绘制元素的外观,而回流是指浏览器重新计算元素的位置和大小。这些过程可能会导致性能问题,因为它们需要大量的计算资源。为了避免这种情况,可以尝试使用CSS动画代替JavaScript动画,或者使用transform和opacity属性来避免回流。
3. JavaScript执行:JavaScript执行可能会导致性能问题,因为它需要大量的计算资源。为了避免这种情况,可以尝试将计算移到Web Worker中,或者使用requestAnimationFrame来优化动画。
4. 图像大小:图像大小可能会影响DOM渲染的性能。大型图像需要更长的时间来下载和解码,这可能会导致性能问题。为了避免这种情况,可以尝试使用适当大小的图像,并使用srcset属性来提供不同大小的图像。
二、在 React 中性能优化的hook使用原则
在 React 中,使用 React.memo
、useCallback
、useMemo
等优化性能的 Hook 应该是在需要解决特定问题时才去应用,而不是尽可能多的使用。 虽然这些 Hook 可以帮助我们避免不必要的渲染和重复计算,但是它们也会增加代码的复杂度并且在某些情况下会产生负面影响。 因此,在开发过程中,我们应该遵循以下原则:
- 首先,我们应该专注于编写可读性更好、可维护性更高的代码。在代码编写完成后,我们可以使用性能优化的 Hook 来解决性能问题。
- 只有在组件的渲染性能确实受到了影响时,才应该考虑使用
React.memo
、useCallback
、useMemo
等 Hook 进行优化。 - 在应用性能优化 Hook 时,我们需要仔细分析组件的渲染逻辑和数据依赖关系,确保使用这些 Hook 不会产生负面影响。
三、React中如何使用hook
1.不要过多使用useState
过多使用 useState
钩子的示例,会导致不必要的复杂性,使代码难以阅读和维护。
使用过多的useState
可能会导致性能问题。每次调用useState
都会创建一个新的状态变量,并且每次更新状态都会触发组件重新渲染。如果组件中有很多useState
,那么每次更新都会导致大量的重复渲染,从而影响性能。
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [address, setAddress] = useState('');
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={age} onChange={(e) => setAge(e.target.value)} />
<input value={address} onChange={(e) => setAddress(e.target.value)} />
</div>
);
}
以下进行优化:
import React, { useState } from 'react';
function Example() {
// 使用单个对象存储所有状态变量,而不是使用多个 useState 钩子。
const [state, setState] = useState({
count: 0,
name: '',
age: 0,
address: ''
});
return (
<div>
<p>你点击了 {state.count} 次</p>
<button onClick={() => setState({ ...state, count: state.count + 1 })}>
点我
</button>
<input value={state.name} onChange={(e) => setState({ ...state, name: e.target.value })} />
<input value={state.age} onChange={(e) => setState({ ...state, age: e.target.value })} />
<input value={state.address} onChange={(e) => setState({ ...state, address: e.target.value })} />
</div>
);
}
2.管理多个状态变量useReducer
如果需要在组件中管理多个状态变量,可以考虑使用useReducer
。useReducer
是另一种React Hook
,它可以管理复杂的状态逻辑,并且可以更好地控制组件的渲染。
在这个示例中使用dispatch
函数来更新用户资料表单的各个字段。当用户提交表单时,我们可以将表单数据发送到服务器。
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'updateName':
return { ...state, name: action.payload };
case 'updateAge':
return { ...state, age: action.payload };
case 'updateAddress':
return { ...state, address: action.payload };
default:
throw new Error();
}
}
function Profile() {
const [state, dispatch] = useReducer(reducer, {
name: '',
age: 0,
address: ''
});
const handleSubmit = (e) => {
e.preventDefault();
// 发送表单数据到服务器
};
return (
<form onSubmit={handleSubmit}>
<label>
姓名:
<input
type="text"
value={state.name}
onChange={(e) =>
dispatch({ type: 'updateName', payload: e.target.value })
}
/>
</label>
<label>
年龄:
<input
type="number"
value={state.age}
onChange={(e) =>
dispatch({ type: 'updateAge', payload: e.target.value })
}
/>
</label>
<label>
地址:
<input
type="text"
value={state.address}
onChange={(e) =>
dispatch({ type: 'updateAddress', payload: e.target.value })
}
/>
</label>
<button type="submit">提交</button>
</form>
);
}
在上面的示例中,我们使用useReducer
来管理一个用户资料表单的状态。reducer
函数接收当前状态和一个操作,然后返回新的状态。useReducer
返回一个包含当前状态和dispatch
函数的数组。dispatch
函数用于触发操作并更新状态。
使用useReducer的主要优点是可以更好地管理多个状态变量,同时也可以更好地控制状态变量的更新。但是,使用useReducer也有一些弊端:
代码复杂度:相比于使用useState,使用useReducer需要编写更多的代码,包括reducer函数和各种action类型。这会增加代码的复杂度和维护成本。
学习成本:相比于useState,useReducer的使用方法更为复杂,需要理解reducer函数和dispatch函数的概念。
性能问题:在某些情况下,使用useReducer可能会导致性能问题。例如,在更新状态变量时,useReducer会创建一个新的状态对象,并将其与旧的状态对象进行比较。如果状态对象很大,这可能会导致性能问题。
3. 使用React.memo优化组件
React.memo
是一个高阶组件,可以用于优化组件的性能。使用 React.memo
可以避免组件在 props
没有变化的情况下进行重复渲染。当组件的 props
发生变化时,React.memo
会对组件进行重新渲染。
使用React.memo
来优化<MyComponent/>
的渲染。 当“data”prop
未更改时,React.memo
将防止<MyComponent/>
的不必要重新呈现。 这可以提高应用程序的性能。
React.memo
的使用方法如下:
// 将创建一个名为“MyComponent”的函数组件,它接受名为“data”的prop
const MyComponent = React.memo(({ data }) => {
// 此组件将从“data”prop中呈现项目列表
return (
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
});
// 将创建一个名为“App”的父组件,它将呈现“MyComponent”
const App = () => {
// 将使用项目数组初始化状态
const [items, setItems] = React.useState([
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" },
]);
// 将定义一个方法,该方法将使用新项目更新状态
const addItem = () => {
const newItem = { id: 4, name: "Item 4" };
setItems((prevState) => [...prevState, newItem]);
};
// 将使用“items”状态作为prop呈现“MyComponent”
return (
<div>
<MyComponent data={items} />
<button onClick={addItem}>添加项目</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
React.memo
仅应用于昂贵的组件,且在给定相同props的情况下具有相同的结果。组件依赖于外部状态或具有副作用,则可能不适合进行记忆化。
4. 使用useCallback和useMemo
React 中的 useCallback
和 useMemo
都可以用于优化组件的性能。其中,useCallback
可以用于缓存函数,而 useMemo
可以用于缓存值。这两个 Hook 都可以避免重复计算和创建对象,从而提高组件的性能。
在这种情况下,我们使用 useCallback
来记忆传递给 ItemList 组件的 handleItemClick
函数。这可以防止 ItemList
组件由于 ParentComponent
中的状态更新而不必要地重新渲染。
useCallback
的使用方法如下:
// 定义一个渲染项目列表的组件
function ItemList({ items, onItemClick }) {
return (
<ul>
{items.map((item) => (
<li key={item.id} onClick={() => onItemClick(item)}>
{item.name}
</li>
))}
</ul>
);
}
// 定义一个渲染 ItemList 组件的父组件
function ParentComponent() {
const [selectedItem, setSelectedItem] = useState(null);
// 定义一个函数,该函数将作为一个 prop 传递给 ItemList 组件
// 当单击项目时,将调用此函数
// 使用 useCallback 来记忆函数并防止 ItemList 组件不必要的重新渲染
const handleItemClick = useCallback((item) => {
setSelectedItem(item);
}, []);
// 定义要由 ItemList 组件渲染的项目数组
const items = [
{ id: 1, name: "项目 1" },
{ id: 2, name: "项目 2" },
{ id: 3, name: "项目 3" },
];
return (
<div>
<h1>已选择的项目:{selectedItem ? selectedItem.name : "无"}</h1>
<ItemList items={items} onItemClick={handleItemClick} />
</div>
);
}
在上面的例子中,handleClick
函数被包裹在 useCallback
函数中,这样就可以缓存函数,避免重复创建函数。
当用户在搜索框中输入关键字时,需要根据关键字实时搜索商品并展示在页面上。为了提高搜索速度,我们可以使用useMemo
来缓存搜索结果,避免每次重新搜索。
useMemo
的作用:
- useMemo是一个hook,它可以用于缓存计算结果。
- 它将接收一个函数和一个依赖项数组作为参数。
- 当依赖项发生变化时,useMemo将重新计算函数的结果。
- 如果依赖项没有变化,则useMemo将返回上一次计算的结果,而不会重新计算。
下面是一个简单的示例代码:
import React, { useState, useMemo } from 'react';
function ProductList({ keyword }) {
const [products, setProducts] = useState([]);
// 使用useMemo缓存搜索结果
const filteredProducts = useMemo(() => {
return products.filter(product => product.name.includes(keyword));
}, [products, keyword]);
// 模拟异步获取商品列表
useEffect(() => {
setTimeout(() => {
setProducts([
{ id: 1, name: 'iPhone 12' },
{ id: 2, name: 'iPad Pro' },
{ id: 3, name: 'MacBook Air' },
{ id: 4, name: 'AirPods Pro' },
]);
}, 1000);
}, []);
return (
<div>
<h2>Search Results:</h2>
<ul>
{filteredProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
function App() {
const [keyword, setKeyword] = useState('');
return (
<div>
<input type="text" value={keyword} onChange={e => setKeyword(e.target.value)} />
<ProductList keyword={keyword} />
</div>
);
}
export default App;
useMemo与React.memo区别
React.memo和useMemo的区别在于它们的用途不同。
React.memo
用于优化组件的渲染,而useMemo
用于缓存计算结果。React.memo
将检查组件的prop是否发生变化,而useMemo
将检查依赖项是否发生变化。 另外,React.memo
是一个高阶组件,而useMemo
是一个hook
。
5. 使用React.lazy和Suspense
React.lazy
和 Suspense
是 React 18 中新增的功能,可以用于按需加载组件。使用 React.lazy
和 Suspense
可以避免在应用程序启动时加载所有组件,从而提高应用程序的性能。 React.lazy
和 Suspense
的使用方法如下:
import React, { lazy, Suspense } from 'react';
const MyComponent = lazy(() => import('./MyComponent'));
const App = () => {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
</div>
);
};
export default App;
在上面的例子中,MyComponent
组件被包裹在 Suspense
组件中,这样就可以在组件加载完成之前显示 Loading...。
6. 使用useLayoutEffect
useLayoutEffect
是 React 18 中新增的 Hook
,可以用于在渲染之前执行副作用。使用 useLayoutEffect
可以在组件更新之前修改 DOM
,从而提高应用程序的性能。 useLayoutEffect
的使用方法如下:
jsxCopy code
import React, { useLayoutEffect, useRef } from 'react';
const MyComponent = () => {
const ref = useRef(null);
useLayoutEffect(() => {
// 修改 DOM
ref.current.style.backgroundColor = 'red';
}, [/* 依赖项 */]);
return <div ref={ref}>Hello, world!</div>;
};
在上面的例子中,useLayoutEffect
函数被用于修改 DOM,这样可以在组件更新之前修改 DOM,提高应用程序的性能。 总结: 使用 React 18
进行性能优化可以从以下方面入手:
- 使用
React.memo
优化组件的性能。 - 使用
useCallback
和useMemo
避免重复计算和创建对象。 - 使用
React.lazy
和Suspense
按需加载组件。 - 使用
useLayoutEffect
在渲染之前执行副作用。 以上的React Hook
在实际开发中的使用场景和使用案例还需要开发者结合具体的业务场景进行分析和实践。
7. React Fragment
React Fragment
简写方式<></>
,但<>
不接受键值或者属性 ,React Fragment
对比无效 HTML 的问题的<div>
元素有以下优点。
React Fragment
的代码可读性更高。- 因为
React Fragment
有一个更小的DOM,它们渲染更快,使用更少的内存。React Fragment
允许按预期呈现 React 组件,而不会引起任何父子关系问题。Fragment
允许返回多个 JSX 元素,这解决了 react 应用程序中由每个组件只能返回一个元素的约束引起的无效 HTML标记的问题。
import "./App.css";
import React from "react";
const Table = ({ children, style }) => {
return <div>{children}</div>;
};
const TableData = () => {
return (
<React.Fragment>
<td>John Doe</td>
<td>16</td>
<td>Developer</td>;
</React.Fragment>
);
}
function App() {
return (
<Table>
<tr>
<th>Name</th>
<th>Age</th>
<th>Occupation</th>
</tr>
<TableData />
</Table>
);
}
export default App;
8、SomeContext.provider 与useContext
当你需要在组件树中传递数据,而不需要在每个层级手动传递props
时,就可以使用context
。
SomeContext.Provider
是一个React
组件,用于创建一个新的上下文环境。它接收一个value
属性,用于传递状态数据。所有在SomeContext.Provider
组件内部渲染的子组件都可以通过useContext
来访问这个上下文环境中的状态数据。
在下面的示例中创建了一个名为ThemeContext
的上下文环境,并将其传递给ThemeContext.Provider
组件的value属性。在Panel
组件中,我们使用useContext
来访问ThemeContext
中的状态数据。
需要注意的是,useContext
只能用于访问通过SomeContext.Provider
传递的状态数据。如果没有在组件树中找到匹配的SomeContext.Provider
,那么useContext
会返回SomeContext
的默认值(如果有的话)。
import { createContext, useContext } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
)
}
function Form() {
return (
<Panel title="Welcome">
<Button>Sign up</Button>
<Button>Log in</Button>
</Panel>
);
}
function Panel({ title, children }) {
const theme = useContext(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ children }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}
参考
转载自:https://juejin.cn/post/7225511164124512293