其實react.memo的實現很簡單,就幾行代碼。html
export default function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
if (__DEV__) {
if (!isValidElementType(type)) {
warningWithoutStack(
false,
'memo: The first argument must be a component. Instead ' +
'received: %s',
type === null ? 'null' : typeof type,
);
}
}
return {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare,
};
}
複製代碼
能夠看到,最終返回的是一個對象,這個對象帶有一些標誌屬性,在react Fiber的過程當中會作相應的處理。前端
在ReactFiberBeginWork.js中能夠看到:react
if (updateExpirationTime < renderExpirationTime) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
複製代碼
根據傳入的compare
函數比較prevProps
和nextProps
,最終決定生成對象,並影響渲染效果。git
其實在這以前,早已經有一個生命週期函數實現了相同的功能。他就是shouldComponentUpdate
。es6
之因此再增長這個memo
,也是react團隊一直在秉承的信念。那就是讓一切變得更加函數式
。github
經過一個例子來看看memo如何使用。編程
先建立一個簡單組件SubComponent
。後端
const SubComponent = props =>
<>
i am {props.name}. hi~
</>
複製代碼
調用React.memo建立memo組件api
const Memo = React.memo(SubComponent, (prevProps, nextProps) =>
prevProps.name === nextProps.name
);
複製代碼
在頁面上調用memopromise
<div className="App">
<Memo name={name} />
</div>
複製代碼
memo
接收兩個參數,一個是組件,一個是函數。這個函數就是定義了memo需不須要render的鉤子。
比較前一次的props跟當前props,返回true表示不須要render。
也就是傳給Memo的name不變時,不會觸發SubComponent的render函數。
當前頁面上的SubComponent仍是以前的,並無從新渲染。這也是爲啥叫memo的緣由吧。
React.lazy 用於作Code-Splitting
,代碼拆分。相似於按需加載,渲染的時候才加載代碼。
用法以下:
import React, {lazy} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
複製代碼
lazy(() => import('./OtherComponent'))
使用es6的import()返回一個promise,相似於:
lazy(() => new Promise(resolve =>
setTimeout(() =>
resolve(
// 模擬ES Module
{
// 模擬export default
default: function render() {
return <div>Other Component</div>
}
}
),
3000
)
));
複製代碼
React.lazy的提出是一種更優雅的條件渲染解決方案。
之因此說他更優雅,是由於他將條件渲染的優化提高到了框架層。
這裏咱們引出suspense。
當咱們組件未渲染完成,須要loading時,能夠這麼寫:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
複製代碼
在咱們的業務場景中,OtherComponent能夠表明多個條件渲染組件,咱們所有加載完成才取消loding。
只要promise沒執行到resolve,suspense都會返回fallback中的loading。
代碼簡潔,loading可提高至祖先組件,易聚合。至關優雅的解決了條件渲染。
關於suspense的異步渲染原理有篇文章寫的很好,感興趣的在文末查看。
hooks提出有一段時間了,dan也一直在推廣,而且表示很快加入react正式版本。
關於一些介紹,直接看官網會更好。
hooks經常使用api有:useState、useEffect、useContext、useReducer、useRef
等。
主要操做一下useEffect
,用處很大。觸類旁通。
當all is function
,沒了component,天然也沒了各類生命週期函數,此時useEffect
登場。
下面經過一個組件實例來講明。
影像組件,功能有:前端加水印、實現拖拽。
大體實現以下:
class ImageModal extends Component {
constructor(props) {
...
}
componentDidMount() {
// 畫水印、註冊拖拽事件邏輯
// 以及其餘的image處理相關邏輯
}
componentDidUpdate(nextProps, prevProps) {
if (nextProps.cur !== prevProps.cur) {
// 切換時重置狀態(好比 旋轉角度、大小等)邏輯
// image特有邏輯
}
}
render() {
return <>
...
<img ... />
</img>
}
}
複製代碼
ImageModal負責渲染圖片modal,如今有另外一個modal用來渲染html模板。
命名爲HtmlModal,HtmlModal接受後端返回的html,通過處理後內嵌在網頁中。
一樣要求加水印、拖拽的功能等。
也就是image跟html有部分邏輯相同有部分不相同。
基於這個考慮,再寫一個組件。
同理實現以下:
class HtmlModal extends Component {
constructor(props) {
...
}
componentDidMount() {
// 畫水印、註冊拖拽事件邏輯
// 以及其餘的html處理相關邏輯
}
componentDidUpdate(nextProps, prevProps) {
if (nextProps.cur !== prevProps.cur) {
// 切換時重置狀態(好比 旋轉角度、大小等)邏輯
// html特有邏輯
}
}
render() {
return <>
...
<div dangerouslySetInnerHTML={{ __html: ... }}></div>
</img>
}
}
複製代碼
能夠看到HtmlModal
和ImageModal
在componentDidMount
和componentDidUpdate
週期中有很多邏輯是相同的。
若是咱們使用useEffect
的話,能夠怎麼實現這個複用和分離呢?來看看。
export function useMoveEffect() {
// 第二個參數傳了固定值 []
// 至關於 componentDidMount
useEffect(() => {
// 實現拖拽邏輯
}, []);
}
export function useDrawMarkEffect(cur) {
useEffect(() => {
// 實現水印邏輯
}, []);
}
export function useResetEffect(cur); {
// 第二個參數傳了固定值 [ cur ]
// 至關於 componentDidUpdate 比較 cur
useEffect(() => {
// 實現重置邏輯
}, [ cur ]);
}
function useOtherImageEffect(...) {
useEffect(() => {
// 實現image特有邏輯
}, [ ... ]);
}
function ImageModal (props) {
// 細分 Effect,方便複用
useMoveEffect();
useDrawMarkEffect();
useResetEffect(props.cur);
...
useOtherImageEffect(...);
return <>
...
<img ... />
</img>
}
複製代碼
ok,有了上面的梳理和useEffect
重構,咱們來編寫HtmlModal
:
import { useMoveEffect, useDrawMarkEffect, useResetEffect } from './imageModal'
function useOtherHtmlEffect(...) {
useEffect(() => {
// 實現html特有邏輯
}, [ ... ]);
}
function HtmlModal (props) {
// 細分 Effect,方便複用
useMoveEffect();
useDrawMarkEffect();
useResetEffect(props.cur);
...
useOtherHtmlEffect(...);
return <>
...
<img ... />
</img>
}
複製代碼
以上,實現了生命週期中重複邏輯的複用。之後不管新增什麼modal,均可以複用邏輯,擺脫了 ctr c/ ctr v
。
從而組件變得更小、代碼變得簡潔,提高編程體驗。
參考資料: React v16.6.0: lazy, memo and contextType(官網)
Making Sense of React Hooks(dan)
React Suspense(中文)
以爲有幫助的點個贊,甚至能夠關注一波哦~