React16.9 Unsafe 生命周期 支持組件性能評估

React團隊在2019年8月9日發(fā)布了最新的16.9版本,該版本的關(guān)鍵變更主要有以下幾點:

  • React 新增<React.Profiler>API,對組件進行性能評估
  • React DOM Unsafe 生命周期
  • React DOM 廢棄javascript:URLs
  • React DOM 廢棄 Factory 組件
  • 用于測試的 act()方法正式支持異步

1、新增<React.Profiler> API,支持組件進行性能評估

<React.Profiler>提供了一種通過編程的方式來收集測量代碼的方式,一般較小的應用不會使用它,通常用于大型應用中。

<Profiler>可以作為一個節(jié)點添加到 React 項目中的任意一個子樹上,來評估該子樹的渲染的頻率及渲染 “成本”??梢栽诙嗵幨褂?,也可以嵌套使用。其旨在幫助標識應用程序中渲染緩慢的部分,并可能會更有利于進行 memoization等優(yōu)化。它接受兩個參數(shù)id和onRender。會在React更新的commit階段調(diào)用它。

render(
  <App>
    <Profiler id="app" onRender={callback}>
      <Navigation {...props} />
    </Profiler>
    <Main {...props} />
  </App>
);

onRender觸發(fā)時,會返回關(guān)于本次更新的性能參數(shù):

  • id,用于區(qū)分多個Pofiler,由props傳入
  • phase,值為 "mount" 或者 "update" ,表示當前組件樹是第一次掛載(mount)還是處于更新周期(update)
  • actualDuration,當前組件樹更新所花費的時間,使用了一些組件的緩存方法。例如React.memo可以看到較為明顯的減少
  • baseDuration,初始掛載組件樹的時間,可以理解為沒有任何優(yōu)化情況下的渲染所花費的時間
  • startTime,本次更新的初始時間戳
  • commitTime,本次更新的結(jié)束時間戳(到達commit階段截止)
  • interactions,本次更新的調(diào)度堆棧

注意:

Profiling 會增加一些額外的開銷,不推薦在生產(chǎn)環(huán)境使用。

2、Unsafe 生命周期

在16.3版本時,React團隊就討論過這三個生命周期潛在的問題,并且在16.3版本中將加入UNSAFE_前綴作為他們的別名,按照當時定下的計劃,將會在16.9中拋出warning,并且在17.0的大版本中徹底移除。

  • componentWillMount → UNSAFE_componentWillMount
  • componentWillReceiveProps → UNSAFE_componentWillReceiveProps
  • componentWillUpdate → UNSAFE_componentWillUpdate

React v16.9 不包含破壞性更改,而且舊的生命周期方法在此版本依然沿用。但是,當你在新版本中使用舊的生命周期方法時,會提示如下警告:

componentWillReceiveProps已被棄用的warning

官方保證即便在17.0中,使用UNSAFE_的生命周期也可以正常使用,也只是生命周期函數(shù)名字變更了而已。想要在老項目升級時避免拋出warning,可以手動變更函數(shù)名。當然也有更好的方案,運行一個自動重命名的 codemod 腳本實現(xiàn)一鍵變更:

cd your_project
npx react-codemod rename-unsafe-lifecycles

開發(fā)團隊也可以在項目中加入嚴格模式(Strict Mode)<React.StrictMode>來禁止使用這類有潛在風險的生命周期。

3、廢棄 javascript:URLs

a標簽的href如果使用javascript:的寫法很容易遭受攻擊,因為它很容易意外在標簽中(<a href>)引入未經(jīng)處理的輸出,造成安全漏洞。在16.9版本中繼續(xù)使用這種寫法React將會拋出警告。

const userProfile = {
  website: "javascript: alert('you got hacked')",
};
// This will now warn:
<a href={userProfile.website}>Profile</a>

在未來的主要版本中,如果遇到 javascript:形式的 URL,React 將拋出錯誤。

4、React DOM 廢棄 Factory 組件

在用 Babel 編譯 JavaScript 類流行前,可以在React中采用factory的寫法來創(chuàng)建組件,該組件使用 render 方法返回一個對象

function FactoryComponent() {
  return { render() { return <div />; } }
}

這種模式令人困惑,因為它看起來像函數(shù)組件 ,然而它并不是。

React支持它會導致庫變大、變慢。因此,在 16.9 中正式棄用此模式,并在遇到警告時輸出警告。如果項目中依賴了此組件,可以通過添加 FactoryComponent.prototype = React.Component.prototype來做兼容。

5、用于測試的 act()方法正式支持異步

React 16.8 引入了名為 act()的新測試實用程序,來幫助你編寫更匹配瀏覽器行為的測試代碼。例如,對單個 act()中的多個狀態(tài)更新進行批處理。這與 React 已有的處理真實瀏覽器事件時的工作方式相匹配,并有助于為將來 React 組件更頻繁地批處理更新做準備。

然而,React v16.8 中的 act() 僅支持同步函數(shù),在act()中寫異步代碼(異步狀態(tài)更新)將會拋出如下警告,并無法輕易修復:

An update to SomeComponent inside a test was not wrapped in act(...).

在 React 16.9 中 act()支持異步函數(shù) ,你可以在調(diào)用它時,使用 await

await act(async () => {
  // ...
});

React團隊是非常推薦大家為自己組件提供測試用例的,參考Testing Recipes中提供的一些測試技巧和應用場景以及使用act()的地方,也包括對hooks的測試場景,比如測試一個hook的事件:

import React, { useState } from "react";

export default function Toggle(props) {
  const [state, setState] = useState(false);
  return (
    <button
      onClick={() => {
        setState(previousState => !previousState);
        props.onChange(!state);
      }}
      data-testid="toggle"
    >
      {state === true ? "Turn off" : "Turn on"}
    </button>
  );
}

測試用例如下

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Toggle from "./toggle";

let container = null;
beforeEach(() => {
  // setup a DOM element as a render target
  container = document.createElement("div");
  // container *must* be attached to document so events work correctly.
  document.body.appendChild(container);
});

afterEach(() => {
  // cleanup on exiting
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("changes value when clicked", () => {
  const onChange = jest.fn();
  act(() => {
    render(<Toggle onChange={onChange} />, container);
  });

  // get a hold of the button element, and trigger some clicks on it
  const button = document.querySelector("[data-testid=toggle]");
  expect(button.innerHTML).toBe("Turn off");

  act(() => {
    button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });

  expect(onChange).toHaveBeenCalledTimes(1);
  expect(button.innerHTML).toBe("Turn on");

  act(() => {
    for (let i = 0; i < 5; i++) {
      button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    }
  });

  expect(onChange).toHaveBeenCalledTimes(6);
  expect(button.innerHTML).toBe("Turn on");
});

這些示例采用了原生 DOM API,但也可以使用 React Testing Library來減少樣板代碼。它的許多方法已經(jīng)通過 act() 進行了實現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容