优化 React 中的数据获取
介绍
数据获取是几乎所有实际 React 应用的核心要求。有时我们需要与从 Web 应用快速连续调用的 API 进行交互。自动完成搜索就是一个例子。如果没有仔细规划这些实例,我们很容易最终降低应用和 API 的性能。在本指南中,我们探讨了几种优化数据获取请求的技术及其效果。
为了演示本指南中讨论的想法,我们将使用一个简单的自动完成搜索。自动完成是任何现代应用程序都期望的功能,但由于其简单性,它通常是一个设计不佳的功能。大多数时候,开发人员会考虑提供组件的最佳视觉输出,但实际的数据获取问题却被忽略了。
自动完成搜索
首先,初始化一个 React-Redux 项目并添加一个搜索操作,它将用于检索关键字的搜索结果。为了保持指南重点完整,这里将仅讨论应用程序的某些组件。但您可以在此Github Repo中找到完整的源代码。要提供自动完成 UI,您可以从 npm 安装react-autocomplete库。添加并初始化所有组件后,App.js将如下所示。
import React, { useState } from 'react';
import './App.css';
import { Provider, useSelector, useDispatch } from 'react-redux'
import store from './store';
import Autocomplete from 'react-autocomplete';
import { search } from './actions';
function SearchComponent(){
const [query, setQuery] = useState("");
const hits = useSelector(state => state.hits);
const results = useSelector(state => state.searchResults);
const dispatch = useDispatch();
const onSearch = (query) => {
setQuery(query);
dispatch(search(query));
}
return (
<div>
<div>API hits: {hits}</div>
<Autocomplete
getItemValue={(item) => item}
items={results}
renderItem={(item, isHighlighted) =>
<div style={{ background: isHighlighted ? 'lightgray' : 'white' }}>
{item}
</div>
}
value={query}
onChange={(e) => onSearch(e.target.value)}
/>
</div>
)
}
function App() {
return (
<Provider store={store}>
<div className="App">
<h2>Auto-complete Search</h2>
<SearchComponent />
</div>
</Provider>
)
}
export default App;
请注意,您从 Redux 存储访问两个变量。
- searchResults:匹配给定查询的字符串
- hits:调用 API 的次数
hits将用于说明考虑查询优化的重要性以及每种优化技术的效果。在运行上述代码之前,请先查看mockapi.js,其中创建了一个搜索函数来模拟搜索 API 的操作。
import mockData from './mockdata.json';
var hits = 0;
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
export async function searchAPI(query){
await sleep(50);
return {
data: {
results: mockData.filter(item => item.toLowerCase().includes(query)).slice(0, 5),
hits: ++hits
}
}
}
一个简单的计数器用于跟踪此模拟搜索 API 的 API 命中次数。现在运行完整代码并尝试输入几个关键字,例如samsung和samsung galaxy。您会注意到,在输入时,每次击键都会增加命中次数,这意味着您的 API 已被调用了那么多次。大多数中间 API 调用都是不必要的。所以今天的目标是在不降低用户体验的情况下减少命中次数。
防抖
防抖是一种动作延迟的形式,即在触发对函数的最后一次调用后观察一段定义的时间。这意味着,如果用户正在输入一个单词,应用程序会缓冲所有搜索调用,直到用户停止输入,然后等待另一个时间段以查看用户是否再次开始输入。如果没有,则它会触发最后收到的函数调用。例如,考虑上面的自动完成功能,这意味着对于搜索查询samsung s10,它将仅触发一个搜索操作,查询参数为samsung s10。以前,它会分派十个操作,每个操作针对用户的每次击键。
除了节省 API 的带宽之外,防抖功能还可以防止 React 组件过度重新渲染。例如,如果您在输入时实时显示查询结果,则对于收到的每个响应,结果组件都会重新渲染。
在 React 中实现去抖动的方法有很多种。您可以编写自己的去抖动实现,也可以使用提供该功能的库。一些广泛使用的库是:
在本指南中,您将使用lodash。如果您更愿意自己实现该功能,本文提供了出色的示例。您可以使用npm install --save lodash安装lodash。
下面的示例展示了如何使用lodash debounce 函数来限制动作调用。
// actions.js
import * as mockAPI from './mockapi';
import debounce from 'lodash/debounce';
// These are our action types
export const SEARCH_REQUEST = "SEARCH_REQUEST"
export const SEARCH_SUCCESS = "SEARCH_SUCCESS"
export const SEARCH_ERROR = "SEARCH_ERROR"
// Now we define actions
export function searchRequest(){
return {
type: SEARCH_REQUEST
}
}
export function searchSuccess(payload){
return {
type: SEARCH_SUCCESS,
payload
}
}
export function searchError(error){
return {
type: SEARCH_ERROR,
error
}
}
export function search(query) {
return async function (dispatch) {
dispatch(searchRequest());
try{
const response = await mockAPI.searchAPI(query);
dispatch(searchSuccess(response.data));
}catch(error){
dispatch(searchError(error));
}
}
}
// debouncing the searchAPI method with wait time of 800 miliseconds
// note that we have leading=true, means that initial function call will be fired
// before starting the wait time
// withut it return value is initial null, which will break the search code
const debouncedSearchAPI = debounce(async (query) => {
return await mockAPI.searchAPI(query)
}, 800, { leading: true });
export function debouncedSearch(query) {
return async function (dispatch) {
dispatch(searchRequest());
try{
const response = await debouncedSearchAPI(query);
dispatch(searchSuccess(response.data));
}catch(error){
dispatch(searchError(error));
}
}
}
SearchPage需要进行修改以使用去抖动搜索来代替常规搜索。
// SearchPage.js
// ....
const onSearch = (query) => {
setQuery(query);
dispatch(debouncedSearch(query));
}
// ...
现在,尝试运行与上述相同的一组查询,并观察 API 命中次数的变化。您可以尝试使用去抖动函数中的缓冲时间,以更好地了解去抖动的效果。虽然这可以防止快速执行 API 调用的问题,但现在用户体验似乎有所下降。理想的自动完成应该在用户输入时提供建议,而当前的实现只会在用户停止输入后提供建议。要纠正此问题,您可以使用下一种优化技术:节流。
节流
节流通过限制在给定时间间隔内调用同一函数的操作数量来提供优化。例如,您可以确定搜索操作应每 100 毫秒触发一次。这样,无论用户是否仍在输入,搜索操作都会每 100 毫秒分派一次,从而改善用户体验。
您也可以使用与防抖相同的库来实现节流。以下示例修改了先前的代码以实现节流。
// actions.js
import * as mockAPI from './mockapi';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
// These are our action types
export const SEARCH_REQUEST = "SEARCH_REQUEST"
export const SEARCH_SUCCESS = "SEARCH_SUCCESS"
export const SEARCH_ERROR = "SEARCH_ERROR"
// Now we define actions
export function searchRequest(){
return {
type: SEARCH_REQUEST
}
}
export function searchSuccess(payload){
return {
type: SEARCH_SUCCESS,
payload
}
}
export function searchError(error){
return {
type: SEARCH_ERROR,
error
}
}
export function search(query) {
return async function (dispatch) {
dispatch(searchRequest());
try{
const response = await mockAPI.searchAPI(query);
dispatch(searchSuccess(response.data));
}catch(error){
dispatch(searchError(error));
}
}
}
const debouncedSearchAPI = debounce(async (query) => {
return await mockAPI.searchAPI(query)
}, 800, { leading: true });
export function debouncedSearch(query) {
return async function (dispatch) {
dispatch(searchRequest());
try{
const response = await debouncedSearchAPI(query);
dispatch(searchSuccess(response.data));
}catch(error){
dispatch(searchError(error));
}
}
}
const throttledSearchAPI = throttle(async (query) => {
return await mockAPI.searchAPI(query)
}, 300, { leading: true });
export function throttledSearch(query) {
return async function (dispatch) {
dispatch(searchRequest());
try{
const response = await throttledSearchAPI(query);
dispatch(searchSuccess(response.data));
}catch(error){
dispatch(searchError(error));
}
}
}
// SearchPage.js
// ....
const onSearch = (query) => {
setQuery(query);
dispatch(throttledSearch(query));
}
// ...</sp
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~