深圳快速seo排名優(yōu)化關鍵詞優(yōu)化排名工具
背景
雙十一大促時,客戶客服那邊反饋商品信息加載卡頓,在不斷有訂單咨詢時,甚至出現(xiàn)了商品信息一直處于加載狀態(tài)的情況,顯然,在這種高峰期接待客戶時,是沒法進行正常的接待工作的。
起初,頁面一直處于加載狀態(tài),初步認為是后端接口返回太慢導致,后經(jīng)過后端日志排查,發(fā)現(xiàn)接口返回很快,根本不會造成頁面一直處于加載狀態(tài),甚至出現(xiàn)卡死的狀態(tài)。后經(jīng)過不斷排查,發(fā)現(xiàn)是客戶端性能問題導致。
優(yōu)化前
咨詢訂單時,只咨詢一條訂單,用時需要3秒左右,當連續(xù)咨詢5、6條訂單時,用時甚至達到了一分多鐘,僅僅5、6條訂單竟然用時這么久,那么在持續(xù)不斷有訂單咨詢時,頁面就會出現(xiàn)一直加載,甚至卡死的狀態(tài),明顯存在很大的性能問題。
利用performance工具可以分析主線程的Event Loop,圖中標出的Main就是主線程。
主線程是不斷執(zhí)行 Event Loop 的,可以看到有很多個 Task(宏任務),當主線程中的任務過多時,會導致主線程長時間被占用,無法及時響應用戶的交互操作,從而影響用戶體驗。這種情況下,頁面可能會出現(xiàn)卡頓、延遲響應等問題。
優(yōu)化后
當只咨詢一條訂單時,用時需要1秒時間,連續(xù)咨詢5、6條訂單,用時優(yōu)化到只需要3秒時間,并且頁面流暢,對于用戶體驗上得到了明顯的提升。
可以看出long task 減少了很多。
那么,如何來優(yōu)化呢?請看下面的內(nèi)容。
優(yōu)化點
在合適的時機進行組件渲染
在排查代碼的過程中發(fā)現(xiàn),很多本不該當前狀態(tài)渲染的組件,都渲染出來了,顯然這是不合理的。過多的組件渲染會占用大量的內(nèi)存,并且也會增加頁面的渲染時間,自然,響應性能就會變得很差,用戶與頁面的交互就會變得遲緩。
而商品信息加載部分最常見的不必要的組件渲染表現(xiàn)在使用Modal彈窗時,我們都知道當visible為true時,會彈出彈窗相應的頁面內(nèi)容,但是當visible為false時,其實是不希望渲染Modal彈窗中的內(nèi)容的,這會帶來額外的性能開銷。
下面是一些示例:
- ...
- <Modal
- ...
- visible={editVisible}
- ...>
- ...
- </Modal>
- ...
+ {editVisible && (
+ <GoodsAttributeModal
+ editVisible
+ ...
+ />
+ )}
// 把Modal彈窗作為一個單獨組件提取出去,并且只有當editVisible為true時才渲染組件
第一段代碼中,使用了visible={editVisible}來控制Modal組件的顯示與隱藏。當editVisible為true時,Modal組件會被渲染出來,否則不會被渲染。
第二段代碼中,使用了條件渲染的方式,即通過{editVisible && …}來判斷是否渲染Modal組件。當editVisible為true時,Modal組件會被渲染出來,否則不會被渲染。
這兩種方式的主要區(qū)別在于組件的渲染時機。在第一種方式中,Modal組件在每次渲染時都會被創(chuàng)建和銷毀,而在第二種方式中,只有在editVisible為true時才會創(chuàng)建和渲染Modal組件。
使用條件渲染的方式可以提高性能,特別是在組件層級較深或渲染頻繁的情況下。因為只有在需要顯示Modal組件時才會進行渲染,避免了不必要的組件創(chuàng)建和銷毀,減少了內(nèi)存消耗和渲染時間。
總結(jié)起來,使用條件渲染的方式可以根據(jù)需要動態(tài)地控制組件的顯示與隱藏,提高性能和用戶體驗。
使用useCallback、useMemo、React.memo提升性能
下面是一些示例:
useCallback
- renderContent = (content, searchKey) => {
- if(content) {
- const contentWithBr = content.replace(/\?/g, '<br>').replace(/\n/g, '<br>')
- const regex = new RegExp(`(${searchKey})`, 'gi'); // 創(chuàng)建正則表達式,忽略大小寫匹配
- const matches = content.match(regex) || []; // 匹配結(jié)果數(shù)組
- return (
- <React.Fragment>
- {contentWithBr.split('<br>').map((text, index) => (
- <React.Fragment key={index}>
- {index > 0 && <br />}
- {text.split(regex).map((subText, subIndex) => {
- // console.log('subText',subText,matches)
- return (
- <React.Fragment key={subIndex}>
- {matches.includes(subText) ? (
- <span style={{ color: '#FF8800' }}>{subText}</span>
- ) : (
- subText
- )}
- </React.Fragment>
- )
- })}
- </React.Fragment>
- ))}
- </React.Fragment>
- )
- } else {
- return '-'
- }
- }+ const renderContent = useCallback((content, searchKey) => {
+ if (content) {
+ const contentWithBr = content.replace(/\?/g, '<br>').replace(/\n/g, '<br>')
+ const regex = new RegExp(`(${searchKey})`, 'gi') // 創(chuàng)建正則表達式,忽略大小寫匹配
+ const matches = content.match(regex) || [] // 匹配結(jié)果數(shù)組
+ return (
+ <React.Fragment>
+ {contentWithBr.split('<br>').map((text, index) => (
+ <React.Fragment key={index}>
+ {index > 0 && <br />}
+ {text.split(regex).map((subText, subIndex) => {
+ //console.log('subText',subText,matches)
+ return (
+ <React.Fragment key={subIndex}>
+ {matches.includes(subText) ? (
+ <span style={{ color: '#FF8800' }}>{subText}</span>
+ ) : (
+ subText
+ )}
+ </React.Fragment>
+ )
+ })}
+ </React.Fragment>
+ ))}
+ </React.Fragment>
+ )
+ } else {
+ return '-'
+ }
+ }, [])
上面的代碼使用了React的useCallback鉤子函數(shù)來定義了一個名為renderContent的函數(shù)。useCallback的作用是用來緩存函數(shù),以便在依賴項不變的情況下避免函數(shù)的重新創(chuàng)建。
使用useCallback的好處是可以優(yōu)化性能,特別是在父組件重新渲染時,避免不必要的函數(shù)重新創(chuàng)建。當依賴項數(shù)組為空時,useCallback會在組件的初始渲染時創(chuàng)建函數(shù),并在后續(xù)的渲染中重復使用同一個函數(shù)。
而沒有使用useCallback的情況下,每次組件重新渲染時都會創(chuàng)建一個新的renderContent函數(shù),即使函數(shù)的實現(xiàn)邏輯完全相同。這可能會導致性能問題,特別是在組件層級較深或渲染頻繁的情況下。
因此,使用useCallback可以提高組件的性能,避免不必要的函數(shù)創(chuàng)建和內(nèi)存消耗。但需要注意的是,只有在確實需要緩存函數(shù)并且依賴項不變的情況下才使用useCallback,否則可能會導致不必要的優(yōu)化和錯誤。
useMemo
- const tooltip = (
- <div>
- <h2>
- <span className={styles.title}>{title}</span>
- {
- !window.isVisibleGoods && (
- <span>
- {renderKnowledgeModal({
- label: '編輯',
- record: item,
- platGoodsId: plat_goods_id,
- classification_id: classificationId,
- })}
- <a
- className={styles.delete}
- onClick={() => handleDeleteKnowledage(item, classificationId)}
- >
- 刪除
- </a>
- </span>
- )
- }
- </h2>
- <div className={styles.img_block}>{images}</div>
- <div
- className={classnames(styles.context, styles.tooltipsContext)}
- dangerouslySetInnerHTML={{ __html: ParseBrow.parse(context) }}
- />
- </div>
- )
+ const tooltip = useMemo(
+ () => (
+ <div>
+ <h2>
+ <span className={styles.title}>{title}</span>
+ {!isVisibleGoods && (
+ <span>
+ {renderKnowledgeModal({
+ label: '編輯',
+ record: item,
+ platGoodsId: plat_goods_id,
+ classification_id: classificationId,
+ })}
+ <a
+ className={styles.delete}
+ onClick={() => handleDeleteKnowledage(item, classificationId)}
+ >
+ 刪除
+ </a>
+ </span>
+ )}
+ </h2>
+ <div className={styles.img_block}>{images}</div>
+ <div
+ className={classnames(styles.context, styles.tooltipsContext)}
+ dangerouslySetInnerHTML={{ __html: ParseBrow.parse(context) }}
+ />
+ </div>
+ ),
+ [
+ title,
+ renderKnowledgeModal,
+ item,
+ plat_goods_id,
+ classificationId,
+ images,
+ context,
+ handleDeleteKnowledage,
+ isVisibleGoods,
+ ]
+ )
在上面的代碼中,使用了useMemo來緩存了一個變量tooltip的計算結(jié)果。這個計算結(jié)果是一個React元素,包含了一些子元素和事件處理函數(shù)等。通過將tooltip作為依賴數(shù)組的一部分,當依賴數(shù)組中的值發(fā)生變化時,useMemo會重新計算tooltip的值;如果依賴數(shù)組中的值沒有發(fā)生變化,則直接返回上一次緩存的tooltip的值。
這樣做的好處是,當依賴數(shù)組中的值沒有發(fā)生變化時,可以避免重復計算tooltip的值,提高組件的性能。而如果依賴數(shù)組中的值發(fā)生變化,useMemo會重新計算tooltip的值,確保tooltip的值是最新的。
相比之下,如果不使用useMemo,每次組件重新渲染時都會重新計算tooltip的值,即使依賴數(shù)組中的值沒有發(fā)生變化,這樣會造成不必要的性能損耗。
總結(jié)起來,使用useMemo可以優(yōu)化組件的性能,避免不必要的計算。但是需要注意的是,只有在計算的成本比較高時才需要使用useMemo,否則可能會帶來額外的開銷
React.memo
- export default Item
+ import { isEqual } from 'lodash'
+ export default React.memo(Item, isEqual)
export default Item 直接導出組件,每次父組件重新渲染都會重新渲染 Item 組件;
而 export default React.memo(Item, isEqual) 使用 React.memo 進行包裹,并傳入自定義的比較函數(shù) isEqual,只有在 props 發(fā)生變化且通過 isEqual 函數(shù)比較不相等時才會重新渲染 Item 組件。
注意:自定義的比較函數(shù) isEqual 用于比較兩個 props 是否相等。如果不傳入比較函數(shù),則默認使用淺比較(即 Object.is)來比較 props。如果傳入了比較函數(shù),則會使用該函數(shù)來比較 props。
props解構(gòu)變量時的默認值
在這段代碼中,KnowledgeTab是一個使用了React.memo進行優(yōu)化的組件。React.memo是一個高階組件,用于對組件進行淺層比較,以確定是否需要重新渲染組件。當組件的props沒有發(fā)生變化時,React.memo會返回之前渲染的結(jié)果,從而避免不必要的重新渲染。
在KnowledgeTab組件中,knowledge_list是一個從props中解構(gòu)出來的屬性。而const knowledge_list_default = useMemo(() => [], [])是使用useMemo鉤子函數(shù)創(chuàng)建的一個空數(shù)組。這樣做的目的是為了在組件的初始渲染時,給knowledge_list一個默認值,以避免在解構(gòu)時出現(xiàn)undefined的情況。
如果直接使用knowledge_list=[]來給knowledge_list賦值,會破壞React.memo的優(yōu)化。因為每次父組件重新渲染時,knowledge_list都會被重新創(chuàng)建,即使它的值沒有發(fā)生變化。這樣會導致KnowledgeTab組件的props發(fā)生變化,從而觸發(fā)不必要的重新渲染。
而使用useMemo創(chuàng)建一個空數(shù)組作為默認值,可以保證在父組件重新渲染時,knowledge_list_default的引用不會發(fā)生變化,從而避免不必要的重新渲染。這樣就能夠保持React.memo的優(yōu)化效果,只有在knowledge_list的值真正發(fā)生變化時才會重新渲染KnowledgeTab組件。
所以,總結(jié)起來就是默認值如果傳給子組件,父組件每一次更新都會導致子組件更新,導致子組件的React.memo失效
拆分為狀態(tài)自治的獨立組件
當一個組件的代碼變得復雜或包含大量的子組件時,可以考慮將其中的一部分代碼抽取為一個獨立的子組件。這樣做的好處是可以將復雜的邏輯拆分為多個小組件,提高代碼的可讀性和可維護性。
同時,抽取組件也可以配合使用React.memo進行優(yōu)化。
下面是一個抽取獨立組件的例子
import React, { memo } from 'react'
import { Tooltip } from 'antd'
import classNames from 'classnames'
import Item from './item'
import styles from '../../index.less'interface Item {name: stringid: string
}
interface CategoryProps {item: ItemactiveKey: stringonClickItem: () => void
}
const Category: React.FC<CategoryProps> = props => {const { item, activeKey, onClickItem } = propsconst { name, id } = itemreturn (<Tooltiptitle={name}placement="topRight"align={{offset: [0, 5],}}><spankey={id}className={classNames(styles.tab_item, {[styles.active_item]: activeKey === id,})}onClick={onClickItem}>{name}</span></Tooltip>)
}export default memo(Category)