react

React16.9 Unsafe 生命周期 支持组件性能评估

2020-09-29  本文已影响0人  LXEP

React团队在2019年8月9日发布了最新的16.9版本,该版本的关键变更主要有以下几点:

1、新增<React.Profiler> API,支持组件进行性能评估

<React.Profiler>提供了一种通过编程的方式来收集测量代码的方式,一般较小的应用不会使用它,通常用于大型应用中。

<Profiler>可以作为一个节点添加到 React 项目中的任意一个子树上,来评估该子树的渲染的频率及渲染 “成本”。可以在多处使用,也可以嵌套使用。其旨在帮助标识应用程序中渲染缓慢的部分,并可能会更有利于进行 memoization等优化。它接受两个参数id和onRender。会在React更新的commit阶段调用它。

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

onRender触发时,会返回关于本次更新的性能参数:

注意:

Profiling 会增加一些额外的开销,不推荐在生产环境使用。

2、Unsafe 生命周期

在16.3版本时,React团队就讨论过这三个生命周期潜在的问题,并且在16.3版本中将加入UNSAFE_前缀作为他们的别名,按照当时定下的计划,将会在16.9中抛出warning,并且在17.0的大版本中彻底移除。

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

componentWillReceiveProps已被弃用的warning

官方保证即便在17.0中,使用UNSAFE_的生命周期也可以正常使用,也只是生命周期函数名字变更了而已。想要在老项目升级时避免抛出warning,可以手动变更函数名。当然也有更好的方案,运行一个自动重命名的 codemod 脚本实现一键变更:

cd your_project
npx react-codemod rename-unsafe-lifecycles

开发团队也可以在项目中加入严格模式(Strict Mode)<React.StrictMode>来禁止使用这类有潜在风险的生命周期。

3、废弃 javascript:URLs

a标签的href如果使用javascript:的写法很容易遭受攻击,因为它很容易意外在标签中(<a href>)引入未经处理的输出,造成安全漏洞。在16.9版本中继续使用这种写法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的写法来创建组件,该组件使用 render 方法返回一个对象

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

这种模式令人困惑,因为它看起来像函数组件 ,然而它并不是。

React支持它会导致库变大、变慢。因此,在 16.9 中正式弃用此模式,并在遇到警告时输出警告。如果项目中依赖了此组件,可以通过添加 FactoryComponent.prototype = React.Component.prototype来做兼容。

5、用于测试的 act()方法正式支持异步

React 16.8 引入了名为 act()的新测试实用程序,来帮助你编写更匹配浏览器行为的测试代码。例如,对单个 act()中的多个状态更新进行批处理。这与 React 已有的处理真实浏览器事件时的工作方式相匹配,并有助于为将来 React 组件更频繁地批处理更新做准备。

然而,React v16.8 中的 act() 仅支持同步函数,在act()中写异步代码(异步状态更新)将会抛出如下警告,并无法轻易修复:

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

在 React 16.9 中 act()支持异步函数 ,你可以在调用它时,使用 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来减少样板代码。它的许多方法已经通过 act() 进行了实现

上一篇 下一篇

猜你喜欢

热点阅读