专栏首页前端迷React 16 新特性全解(中)

React 16 新特性全解(中)

前言

这篇文章主要介绍v16.4~ v16.6的特性,Hooks请关注下一期

目录

  • v16.4
  1. 新增指针事件
  2. fix生命周期函数
  • v16.5
  1. 提供新的调试工具
  • v16.6
  1. memo
  2. lazy
  3. suspense
  4. 简化 contextType
  5. 增加 getDerivedStateFromError

16.4

新增指针事件

新增了对对指针设备(例如鼠标,触控笔,或者手指触摸)触发的dom事件:

  • onPointerDown
  • onPointerMove
  • onPointerEnter
  • onPointerLeave
  • onPointerOver
  • onPointerOut

等等。

但是这个事件仅支持那些支持指针事件的浏览器,比如目前最新版本的Chrome,Firefox,Edge IE浏览器)。但是如果你的应用程序真的依赖这些事件,可以使用第三方的polyfill。因为React团队对了避免增大react的bundle size,所以没有放进去。

fix生命周期函数 - getDerivedStateFromProps

React 16.0 ~ React 16.3 :

生命周期的图如下:

这里我们可以看到getDerivedStateFromProps这个生命周期函数有个问题就是在Updating阶段的时候,无论使用setState还是forceUpdate,都不调用getDerivedStateFromProps。

喂,这明显有问题啊?! 那我在updating阶段都没办法监听到props的改变来搞事情了。

React团队还是很快意识到了这个问题的。所以在这个版本,他们fix了这个问题,新的图长这样:

很简单,就是fix了之前Updating阶段用setState,forceUpdate调用不到getDerivedStateFromProps这个问题。

v16.4.2

这个小版本的发布主要是为了fix一个服务端的XSS漏洞,我本来不想讲,但是看大家那么好学,还是提一下把。

呃,别告诉我你还不知道什么是XSS漏洞?,拜托赶紧现在立刻马上去学一下,这个必会。一个简单的例子就是用户输入了你不想让他输入的内容,尤其带有JS html标签的,给你的网站带来了麻烦。

这个XSS具体场景是这样的:

let props = {};
// 注意:这里props的属性依靠用户输入
props[userProvidedData] = "hello";
let element = <div {...props} />;
let html = ReactDOMServer.renderToString(element);

此时,用户输入:

let userProvidedData = '></div><script>alert("hi")</script>';

那么最终生成的html就会变成:

<div ></div><script>alert("hi")</script>

这就是我们常见的XSS攻击。

但是现实中我们dom元素属性需要依赖用户输入的场景非常的少,所以对于大部分应用来说没有影响,最重要的是意味着对大部分开发者都没有影响,这样我们就不用担心要半夜起来改代码,还是可以的。

v16.5 React Profiler

这个版本提供了对新的Profiler DevTools插件的支持。这个插件就厉害了,可以通过收集每个组件的渲染耗时,来帮助我们找到ReactApp的渲染瓶颈,并且整个界面更加清爽清晰,简直是开发者的福利。

话不多说,下面来讲解下如何使用:

  1. 首先安装React DevTools插件

如果你的React版本已经升到16.5以上,那么你的DevTools的界面会变成这样: 打开第二个tab。

2.点击中间这个button开始记录

3.接着操作你想要记录的操作,完事之后点stop

4.看结果图

在讲解之前,先普及一些知识。知道Fiber的同学应该都了解,现在React渲染过程分为两个阶段:

一、render阶段

这个阶段主要是对比,有那些DOM节点需要更新。

二、commit阶段

将第一阶段收集的信息更新到真实节点,完成之后会调用componentDidMount跟componentDidUpdate。

接着看结果图。

图示一的地方显示的是每一次commit的耗时,其中黑色表示当前选中的commit,可以左右移动来选择,其中柱子越高说明这次的commit耗时约多。

图示二表示的是这次commit发生在第几S,它的render阶段耗时多少。

图示三表示的是这次commit里每一个组件的耗时。由图中我们可以看到Router耗时最多,达到18.4ms。 而耗时主要来源于Nav 跟 Route组件。 如果你去点击每个组件,还可以在右边看到这个组件此时的state跟props,那就可以很清晰的知道这个组件此刻在渲染什么。

小技巧

再说最后一个小技巧,你可以选中某个组件,然后点击右上角两次相邻的commit,这样你就知道是哪个 state的改变引发了这次的re-render。 比如下面的例子,就是scrollOffset的变化导致整个App的变化。

以上就是一些基本使用,关于实际例子的演示,还挺多的,可以单独写一篇文章了。 有时间的话在给大家介绍。

v16.6

memo

React 15:如果你想阻止组件的重复渲染,在class component里可以使用PureComponent, shouldComponentUpdate来帮助你。但是如果你是function component,对不起,没有这个功能, 只能每次都重新渲染。

React 16:为了全面拥抱function component,React团队写了memo来帮助function component实现这个阻止重复渲染的功能。

lazy、suspense

lazy需要跟Suspence配合使用,所以这里放在一起介绍。

lazy实际上是帮助我们实现代码分割的功能,使用过webpack的同学都知道,webpack也有这个功能。 那为什么他们都要做这个功能呢?

其实是这样的:由于有些内容,我们并不一定要在首屏展示,所以这些资源我们没有必要一开始就要去获取,那么这些资源就可以动态获取。这样的话,相当于把不需要首屏展示的代码分割出来,减少首屏代码的体积,提升性能。

我们的首屏只需要先展示一个,所以其他的可以动态引入。 这里以动态引入B为例:

<!--import B from "./B";-->

// 需要用到的时候才加载进来,当然还有预加载更好
const B = lazy(() => import("./B"));

但是这样的话,一开始就可以点击 B,会报错。 因为需要当组件还在加载渲染的时候,需要一个place holder防止组件还没加载完毕的时候可以有东西显示给用户。

这时候Suspence得作用就出来了。Suspence 很像Error Boundary,不同的是Error Boundary是用来捕获错误,显示相应的callback组件。而Suspence是用来捕获还没有加载好的组件,并暂停渲染,显示相应的callback。

我们给B加上Suspense 就搞定了。

 <Suspense fallback={<div>Loading...</div>}>
    <TabPanel>
    <B />
    </TabPanel>
 </Suspense>

跟Error Boundary一样,Suspence也有一个放置位置的问题,是整个包裹在App外,还是只是给单独的组件包裹?

我的选择与Error Boundary依旧一致,应该将其包裹在子组件外面,因为这样当某个组件没有加载好的时候,不会影响到整个App都显示一个loading的标识。

注意:

  1. SSR不支持lazy这个特性。
  2. Lazy 必须搭配Suspence使用,否则会报错

进一步优化:

这里我们在进一步思考一个点,目前我们的B组件是需要用到的时候才加载。万一这个组件需要获取数据,使得他显示比较慢,就会显示loading,导致我们用户体验比较差呢。所以我们可否在浏览器闲着的时候预加载这些即将要用到资源?

答案是可以的,React团队也在做这件事情。 但是这个API目前为止还没有Ready,大家先知道这个事情好了~

简化static contextType

简化获取context的方式,之前需要用一个在外层包裹一个<Consumer>,如下:

// Theme context, default to light theme
const ThemeContext = React.createContext('light');

// Signed-in user context
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App component that provides initial context values
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// A component may consume multiple contexts
// 同时如果是function component 用Consumer
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

现在可以直接通过this.context获取。

class MyClass extends React.Component {
  static contextType = MyContext;
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of MyContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* render something based on the value of MyContext */
  }
}
MyClass.contextType = MyContext;

新增static getDerivedStateFromError

v16.3这个版本里,React 除了Error Boundaries来捕获错误,里面主要是使用了componentDidCatch来捕获 错误。但是它是在错误已经发生之后并且render函数被调用之后,才会被调用。 也就是说如果一个组件出现的错误,在调用 componentDidCatch之前只能返回null给用户。 而 getDerivedStateFromError 可以在render函数之嵌捕获到错误,所以它更适合写用来显示fallback UI的逻辑。

注意事项:componentDidCatch,getDerivedStateFromError都无法捕获服务端的错误,但是React团队正在努力支持SSR。

改进前的ErrorBoundary:

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  componentDidCatch(error, info) {
    this.setState({ hasError: false })
    logErrorToMyService(error, info);
  } 
  
  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    
    return this.props.children; 
  }

改进后的ErrorBoundary(推荐写法):

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    // 更新state所以下次render可以立刻显示fallback UI
    return { hasError: true };
  }
  
  componentDidCatch(error, info) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }
  
  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    
    return this.props.children; 
  }

以上就是v16.4 ~ v16.6的全部内容,考虑到大家应该比较累了,Hooks又是比较大的特性,所以还是分到下一篇,建议大家点击原文链接,会有demo展示,体验会更好

本文分享自微信公众号 - 前端迷(love_frontend),作者:daisy

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【技术篇】如何搞定react组件化

    在主流前端框架里,因为React的入门难度高而果断投入Vue怀抱的人绝不在少数。但我要告诉大家,如果你有一定的js基础,其实React没你想象中那么困难

    前端迷
  • React16 新特性

    于 2017.09.26 Facebook 发布 React v16.0 版本,时至今日已更新到 React v16.6,且引入了大量的令人振奋的新特性,本文章...

    前端迷
  • 数据结构与前端开发(3)-链表

    链表是一个线性结构,同时也是一个天然的递归结构。链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了...

    前端迷
  • React Native开发之React基础

    为了帮助大家快速上手React Native开发,在这本节中将向大家介绍开发React Native所需要的一些React必备基础知识。

    CrazyCodeBoy
  • 一段探索React自建内部构造的旅程

    在先前的文章里我们涵盖了React基本原理和如何构建更加复杂的交互组件。此篇文章我们将会继续探索React组件的特性,特别是生命周期。

    一个会写诗的程序员
  • react 创建组件以及组件通信

    关于React.createClass方法与class App extends Component方法的区别

    念念不忘
  • 组件设计基础(2)

    早期的react设计了许多的生命周期钩子。它们严格定义了组件的生命周期,一般说,生命周期可能会经历如下三个过程:

    一粒小麦
  • 微信小程序-音乐播放器+背景播放

    1.正常播放音频 2.可以滑动进度条 3.可以切换上一条,下一条音频 4.退出当前页或关闭小程序之后仍然可以正常播放 5.试听功能进入该播放页不可以播放上一条,...

    super.x
  • React组件生命周期小结

    下面所写的,只适合前端的React。(React也支持后端渲染,而且和前端有点小区别,不过我没用过。)

    linjinhe
  • 关于蚂蚁庄园登山赛你可以还不知道的秘密!我也写一个康康!Cocos Creator 3D!

    监听TOUCH_MOVE事件控制小鸡左右移动,并要判断边界。注意手指移动的变化和实际屏幕看到的大小是不一样的,这是因为摄像机投影的原因。我是乘了一个系数,可能有...

    白玉无冰

扫码关注云+社区

领取腾讯云代金券