react新特性實例詳解(memo、lazy、suspense、hooks)

1.memo

其實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函數比較prevPropsnextProps,最終決定生成對象,並影響渲染效果。git

其實在這以前,早已經有一個生命週期函數實現了相同的功能。他就是shouldComponentUpdatees6

之因此再增長這個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的緣由吧。

2.lazy and suspence

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的異步渲染原理有篇文章寫的很好,感興趣的在文末查看。

3.hooks(重點介紹useEffect)

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>
  }
}
複製代碼

能夠看到HtmlModalImageModalcomponentDidMountcomponentDidUpdate週期中有很多邏輯是相同的。

若是咱們使用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(官網)

Hooks API Reference(官網)

Making Sense of React Hooks(dan)

React Suspense(中文)

以爲有幫助的點個贊,甚至能夠關注一波哦~

相關文章
相關標籤/搜索