What-Is-New-In-React-16

Stephen Cui ... 2019-08-31 10:19:15
  • React16
  • React.memo
  • React.lazy
About 8 min

# What's new in React 16

# 1. 新的渲染返回值类型

React 16.2 is now available! The biggest addition is improved support for returning multiple children from a component’s render method. We call this feature fragments.[1]

# 1.1 数组结构

render() {
    return [
        <li key="A">A</li>
        <li key="B">B</li>
    ]
}
1
2
3
4
5
6

# 1.2 Fragment类型

React v16.2.0提供了Fragment类型,不同于div等基本类型的区别在于,Fragment只是一个虚拟占用,并不会真正生成Dom节点

render() {
    return <Fragment>
        <span>Hello World 1</span>
        <span>Hello World 2</span>
    </Fragment>
}
1
2
3
4
5
6

或者

render() {
    return <>
        <span>Hello World 1</span>
        <span>Hello World 2</span>
    </>
}
1
2
3
4
5
6

# 1.3 含有Key标签的Fragment

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // Without the `key`, React will fire a key warning
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2. 异步渲染

新增[2][3]

# 2.1 getDerivedStateFromProps

Replace all componentWillReceiveProps uses and could return null (indicate not to update state) or return state. Common Problems: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

// Before
class ExampleComponent extends React.Component {
  state = {
    isScrollingDown: false,
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.currentRow !== nextProps.currentRow) {
      this.setState({
        isScrollingDown:
          nextProps.currentRow > this.props.currentRow,
      });
    }
  }
}
// After
class ExampleComponent extends React.Component {
  // Initialize state in constructor,
  // Or with a property initializer.
  state = {
    isScrollingDown: false,
    lastRow: null,
  };

  static getDerivedStateFromProps(props, state) {
    if (props.currentRow !== state.lastRow) {
      return {
        isScrollingDown: props.currentRow > state.lastRow,
        lastRow: props.currentRow,
      };
    }

    // Return null to indicate no change to state.
    return null;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 2.2 getSnapshotBeforeUpdate

The new getSnapshotBeforeUpdate lifecycle is called right before mutations are made (e.g. before the DOM is updated). The return value for this lifecycle will be passed as the third parameter to componentDidUpdate.

# 2.3 组合getSnapshotBeforeUpdatecomponentDidUpdate

class ScrollingList extends React.Component {
  listRef = null;

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      return (
        this.listRef.scrollHeight - this.listRef.scrollTop
      );
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      this.listRef.scrollTop =
        this.listRef.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.setListRef}>
        {/* ...contents... */}
      </div>
    );
  }

  setListRef = ref => {
    this.listRef = ref;
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 3. Context API v16.3.0

# 3.1 Before Use Context

Context is primarily used when some data needs to be accessible by many components at different nesting levels. Apply it sparingly because it makes component reuse more difficult.[4] Please try to use Component Composition[5] mode

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />

  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Note:

At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies. Props and composition give you all the flexibility you need to customize a component’s look and behavior in an explicit and safe way. Remember that components may accept arbitrary props, including primitive values, React elements, or functions. If you want to reuse non-UI functionality between components, we suggest extracting it into a separate JavaScript module. The components may import it and use that function, object, or a class, without extending it.

# 3.2 API

I would recommend reaching for Context when you find yourself passing props down through three or more levels in your component tree. You might notice that you have renamed your props, making it challenging to determine the data’s origin. You might consider implementing context if a bunch of your components know about irrelevant data.

Step 1: create context api

const MyContext = React.createContext(defaultValue);
1

Step 2: define provider

<MyContext.Provider value={/* some value */}>
1

Step 3: use as consumer

<MyContext.Consumer>
  {value => /* render something based on the context value */}
</MyContext.Consumer>
1
2
3

Demo (opens new window)

# 3.3 forwardRef API[6]

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
1
2
3
4
5
6
7
8
9

# 3.4 StrictMode Component

StrictMode currently helps with:

  • Identifying components with unsafe lifecycles
  • Warning about legacy string ref API usage
  • Warning about deprecated findDOMNode usage
  • Detecting unexpected side effects
  • Detecting legacy context API

Note: Strict mode checks are run in development mode only; they do not impact the production build.

# 4. Pointer Events

Only work in browsers that support the Point Event. The following event types are now available in React DOM:

  • onPointerDown
  • onPointerMove
  • onPointerUp
  • onPointerCancel
  • onGotPointerCapture
  • onLostPointerCapture
  • onPointerEnter
  • onPointerLeave
  • onPointerOver
  • onPointerOut

# 5. Lazy, Memo v16.6.0

[7] Lazy and Memo is used for Funcional Component and before this you should know what it is.

# 5.1 Functional Component Vs. Class Component

[8]

// Functional Components
function Hello(props){
   return <div>Hello {props.name}</div>
}
1
2
3
4

Benefits:

  • Easy to read with less code
  • Easy to test with less stub library
  • Better performance improve on package bundle size and mounting performance[9]
  • Reduce coupling with less code in function

# 5.2 Functional Component Performance

We thought this change would instantly improve performance, because when navigating between conversations in Missive, React unmounts and mounts hundreds of components. You’d think functional components would avoid this mounting / unmounting as well as lifecycle events overhead because they are just functions, but you’d be wrong.

Although it is small improvement of performance, but still useful[10]. Demo Gif (opens new window)

class Main extends React.Component {
  render() {
    var dots = Array(500).fill(0).map(x => {
      if(this.props.kind == 'stateless-functional-direct-call') {
        return Dot();
      } else if(this.props.kind == 'stateless-functional-mounted') {
        return React.createElement(Dot, {}, {});
      } else if(this.props.kind == 'stateful') {
        return React.createElement(DotComponent, {}, {});
      }
    })
    return React.createElement('div', {}, ...dots);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 5.3 How to use React Memo

memo just give functional component pure render ability.

// Demo
import React from "react";

class Index extends React.Component {
  state = {
    count: 0
  };

  handleClick = () => {
    this.setState({
      count: ++this.state.count
    });
  };

  render() {
    return <div>
      <button onClick={this.handleClick}>  {"Click Here to Refresh Component"}</button>
      <br/>
      <br/>
      <br/>
      <ClassComponent/>
      <FunctionalComponent/>
      <PureComponent/>
      <MemoFunctionalComponent />
    </div>;
  }
}

export default Index;


function FunctionalComponent(props) {
  console.log("render ----> FunctionalComponent");
  return <div>{"FunctionalComponent"}</div>;
}

const MemoFunctionalComponent = React.memo((props) => {
  console.log("render ----> MemoFunctionalComponent");
  return <div>{"MemoFunctionalComponent"}</div>;
});

class ClassComponent extends React.Component {
  render() {
    console.log("render ----> ClassComponent");
    return <div>{"ClassComponent"}</div>;
  }
}

class PureComponent extends React.PureComponent {
  render() {
    console.log("render ----> PureComponent");
    return <div>{"PureComponent"}</div>;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# 5.4 How to use React Lazy & Code Split

lazy is used for tow purpose: One is lazy loading component and show loading state. Another purpose is good for code split by model bundle tool, like webpack to reduce running package size

import React, { lazy, Suspense } from "react";

const LazyComponent = lazy(() => import("./lazy-component"));

export default class Index extends React.Component {
  state = {
    showLazyComponent: false
  };

  handleShowLazyComponent = () => this.setState({
    showLazyComponent: !this.state.showLazyComponent
  });

  render() {
    return <div>
      <button onClick={this.handleShowLazyComponent}>Click Me to Show Lazy Component</button>
      <br/>
      <br/>
      <br/>
      <Suspense fallback={<div>Loading...</div>}>
        {this.state.showLazyComponent && <LazyComponent/>}
      </Suspense>
    </div>;
  }
}
// Lazy Component
import React from "react";

const LazyComponent = () => {
  return <div>
    {"This is LazyComponent for 10000 items"}
    {new Array(10000).fill(null).map((_, index) => {
      return <div key={index}>
        <div>
          <div>
            <div>
              <div>
                <div>
                  <div>
                    <div>
                      <div>
                        <div>
                          <div>
                            <div>
                              <div>
                                <div>
                                  <div>
                                    <div>
                                      <div>
                                        <div>
                                          <div>
                                            <div>
                                              <div>
                                                <div>
                                                  <div>
                                                    <div>{index}</div>
                                                  </div>
                                                </div>
                                              </div>
                                            </div>
                                          </div>
                                        </div>
                                      </div>
                                    </div>
                                  </div>
                                </div>
                              </div>
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>;
    })}
  </div>;
};

export default LazyComponent;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

# 6. React Hooks v16.8.0

Hooks let you use state and other React features without writing a class. You can also build your own Hooks to share reusable stateful logic between components.

Note that React Hooks don’t cover all use cases for classes yet but they’re very close. Currently, only getSnapshotBeforeUpdate() and componentDidCatch() methods don’t have equivalent Hooks APIs, and these lifecycles are relatively uncommon. If you want, you should be able to use Hooks in most of the new code you’re writing. more examples to see (opens new window)[11]

Hooks:

  • useState
  • useEffect
  • useContext
  • useReducer
  • useRef
  • useLayoutEffect
  • useDebugValue
  • use customize hook

# 6.1 useState

Given functional component ability to maintains state in itself.

function App() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12

# 6.2 useEffect

useEffect called after every render, just like componentDidMount, componentDidUpdate or componentWillUnmount More info about customize useEffect (opens new window)

import React, { useEffect, useState } from "react";

export default function() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 6.3 useContext

More Reading - Redux vs. React Context API (opens new window)

import React, { useContext, useState } from "react";
const NumberContext = React.createContext(100);

export default function() {
  const value = useContext(NumberContext);
  const [count, setCount] = useState(value);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 6.4 useReducer

import React, { useReducer } from "react";

export default function() {
  const [count, dispatch] = useReducer((state, action) => {
    return state + action;
  }, 0);

  return (
    <div>
      <p>You Clicked {count} times</p>
      <button onClick={() => dispatch(1)}>Click Me</button>
    </div>
  );
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 6.5 useRef

import React, { useRef } from "react";

export default function() {
  const inputEl = useRef(null);
  return (
    <div style={{ display: 'flex', flexFlow: 'column'}}>
      <input ref={inputEl} type='text' />
      <button onClick={() =>inputEl.current.focus()}>Click Me to Focus input</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11

# 6.6 useLayoutEffect

https://reactjs.org/docs/hooks-reference.html#uselayouteffect

# 6.7 useDebugValue

https://reactjs.org/docs/hooks-reference.html#uselayouteffect

# 6.8 customize hook

More Examples (opens new window)

import { useState, useRef, useEffect, useCallback } from 'react';

// Usage
export default function (){
  // State for storing mouse coordinates
  const [coords, setCoords] = useState({ x: 0, y: 0 });

  // Event handler utilizing useCallback ...
  // ... so that reference never changes.
  const handler = useCallback(
    ({ clientX, clientY }) => {
      // Update coordinates
      setCoords({ x: clientX, y: clientY });
    },
    [setCoords]
  );

  // Add event listener using our hook
  useEventListener('mousemove', handler);

  return (
    <h1>
      The mouse position is ({coords.x}, {coords.y})
    </h1>
  );
}

function useEventListener(eventName, handler, element = window){
  // Create a ref that stores handler
  const savedHandler = useRef();

  // Update ref.current value if handler changes.
  // This allows our effect below to always get latest handler ...
  // ... without us needing to pass it in effect deps array ...
  // ... and potentially cause effect to re-run every render.
  useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);

  useEffect(
    () => {
      // Make sure element supports addEventListener
      // On
      const isSupported = element && element.addEventListener;
      if (!isSupported) return;

      // Create event listener that calls handler function stored in ref
      const eventListener = event => savedHandler.current(event);

      // Add event listener
      element.addEventListener(eventName, eventListener);

      // Remove event listener on cleanup
      return () => {
        element.removeEventListener(eventName, eventListener);
      };
    },
    [eventName, element] // Re-run if eventName or element changes
  );
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# React 16.9

# How to Use React.Profiler

Profiler对程序运行时的性能监控,React自身提供了性能监控工具,其可以独立运行,也可以在浏览器中加载第三方应用。 首先,本机全局安装yarn global add react-devtools,安装成功后运行react-devtools 其次,在项目的index.html中添加<script src="http://localhost:8097"></script>标签 最后,启动项目即可,yarn start 参考文章:https://kentcdodds.com/blog/profile-a-react-app-for-performance/

# React 16.10 ~ 16.13

fix issues

# React 16.14

# Add Support for New JSX Transform

JSX需要Babel转译城JS,React官方提供了新一代的转译基础语法支持,用户可在无需更改或者升级旧代码的基础上进行升级,需要开启新转译方式的选项即可。 步骤如下:

  1. Upgrade Babel yarn upgrade @babel/core @babel/plugin-transform-react-jsx Or Upgrade Babel Present yarn upgrade @babel/core @babel/preset-react
  2. Upgrade React to 17.0.0

新的转译方式带来几个好处:

  • 不需要在'头部'声明React的导入语句
  • 大概率能减少一些打包体积大小
  • 将会减少一些'概念上'的知识点,入门更快

举个例子:

  1. 使用旧的转译支持
import React from 'react';
function App() {
  return <h1>Hello World</h1>;
}
1
2
3
4

转译后:

import React from 'react';
function App() {
  return React.createElement('h1', null, 'Hello world');
}
1
2
3
4
  1. 使用新的转译支持
function App() {
  return <h1>Hello World</h1>;
}
1
2
3

转译后:

// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
  return _jsx('h1', { children: 'Hello world' });
}
1
2
3
4
5

# React 17.0.1

The React 17 release is unusual because it doesn’t add any new developer-facing features. Instead, this release is primarily focused on making it easier to upgrade React itself. In particular, React 17 is a “stepping stone” release that makes it safer to embed a tree managed by one version of React inside a tree managed by a different version of React. It also makes it easier to embed React into apps built with other technologies.

# ToDo

  • React Profiler[12]
  • Fiber Arth: https://www.jianshu.com/p/863e3c9da4c4

# Resource


  1. Reactv16.6.2 (opens new window) ↩︎

  2. AsyncRender (opens new window) ↩︎

  3. ReactLifecycleDemo (opens new window) ↩︎

  4. ContextAPI (opens new window) ↩︎

  5. ComponentComposition (opens new window) ↩︎

  6. ForwardRefAPI (opens new window) ↩︎

  7. Reactlazy/memo (opens new window) ↩︎

  8. ReactFunctional (opens new window) ↩︎

  9. 45%FasterReactFunctionalComponentsNow (opens new window) ↩︎

  10. StatelessFunctionBenchmark (opens new window) ↩︎

  11. ReactHooksExamples (opens new window) ↩︎

  12. ReactProfiler (opens new window) ↩︎

Last update: October 10, 2021 09:12
Contributors: Stephen Cui