Skip to main content

React 16.6 New APIs

Free2018-11-17#JS#React代码拆分#React组件动态加载#React动态加载#React Lazy Component#React contextType vs contextTypes

Function components also get "shouldComponentUpdate", and beautiful Code-Splitting support

I. Overview

Added several convenient features:

  • React.memo: Function components also have "shouldComponentUpdate" lifecycle now

  • React.lazy: Easily and elegantly complete Code-Splitting with Suspense feature

  • static contextType: Class components can more easily access single Context

  • static getDerivedStateFromError(): SSR-friendly "componentDidCatch"

Among them the most important is Suspense feature, mentioned in previous React Async Rendering:

Additionally, a suspense (suspend) API will be provided in the future, allowing view rendering to be suspended, waiting for asynchronous operations to complete, making loading scenarios easier to control, see the 2nd Demo in Sneak Peek: Beyond React 16 presentation video for details

And now (v16.6.0, released on 2018/10/23), is the "future" approximately 8 months later

II. React.memo

const MyComponent = React.memo(function MyComponent(props) {
  /* only rerenders if props change */
});

There's also an optional compare parameter:

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);

Similar to PureComponent higher-order component, wrap a layer of memo, can give ordinary function components the performance advantages of PureComponent:

React.Component doesn't implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.

Internal Implementation

Implementation is very simple:

export default function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  return {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
}

It's just external shouldComponentUpdate lifecycle, comparing to class components:

// Ordinary class component
class MyClassComponent {
  // No default shouldComponentUpdate, can implement manually
  shouldComponentUpdate(oldProps: Props, newProps: Props): boolean {
    return true;
  }
}

// Component inherited from PureComponent is equivalent to
class MyPureComponent {
  // Has default shouldComponentUpdate, i.e., shallowEqual
  shouldComponentUpdate(oldProps: Props, newProps: Props): boolean {
    return shallowEqual(oldProps, newProps);
  }
}

// Function component
function render() {
  // Function component, doesn't support shouldComponentUpdate
}

// Memo component is equivalent to
const MyMemoComponent = {
  type: function render() {
    // Function component, doesn't support shouldComponentUpdate
  }
  // Has default (external) shouldComponentUpdate, i.e., shallowEqual
  compare: shallowEqual
};

In this way, stuck a shouldComponentUpdate onto function components, next things can be guessed:

// ref: react-16.6.3/react/packages/react-reconciler/src/ReactFiberBeginWork.js
function updateMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  updateExpirationTime,
  renderExpirationTime: ExpirationTime,
): null | Fiber {
    // 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,
      );
    }
  }
}

So, from implementation perspective, React.memo() this API has little to do with memo, actual meaning is: function components also have "shouldComponentUpdate" lifecycle now

Note, compare defaults to shallowEqual, so React.memo second parameter compare actual meaning is shouldNotComponentUpdate, not the opposite one we're familiar with. API design is indeed a bit confusing, had to introduce an opposite thing:

Unlike the shouldComponentUpdate() method on class components, this is the inverse from shouldComponentUpdate.

P.S. During RFC finalization process second parameter was indeed controversial (equal, arePropsEqual, arePropsDifferent, renderOnDifference, isEqual, shouldUpdate... etc. within 10000), see React.memo() for details

Manually Implement a memo?

Speaking of which, such a higher-order component is actually not difficult to implement:

function memo(render, shouldNotComponentUpdate = shallowEqual) {
  let oldProps, rendered;
  return function(newProps) {
    if (!shouldNotComponentUpdate(oldProps, newProps)) {
      rendered = render(newProps);
      oldProps = newProps;
    }

    return rendered;
  }
}

This manually implemented pirated version is functionally equivalent to official version (even performance is comparable), so another icing on the cake thing

III. React.lazy: Code-Splitting with Suspense

Quite beautiful feature, due to space limitations, see React Suspense for details

IV. static contextType

v16.3 introduced New Context API:

const ThemeContext = React.createContext('light');

class ThemeProvider extends React.Component {
  state = {theme: 'light'};

  render() {
    return (
      <ThemeContext.Provider value={this.state.theme}>
        {this.props.children}
      </ThemeContext.Provider>
    );
  }
}

class ThemedButton extends React.Component {
  render() {
    return (
      // This part looks very troublesome, just reading a context
      <ThemeContext.Consumer>
        {theme => <Button theme={theme} />}
      </ThemeContext.Consumer>
    );
  }
}

To make class components access Context data more conveniently, added static contextType feature:

class ThemedButton extends React.Component {
  static contextType = ThemeContext;

  render() {
    let theme = this.context;

    return (
      // The noise has stopped
      <Button theme={theme} />
    );
  }
}

Among them contextType (note, previous old one had multiple s, called contextTypes) only supports React.createContext() return type, renovated Old Context API's this.context (became single value, previously was object)

Usage is not so crazy anymore, but only supports accessing single Context value. To access a bunch of Context values, can only use the very troublesome way above:

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

V. static getDerivedStateFromError()

static getDerivedStateFromError(error)

Another error handling API:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Usage is very similar to v16.0's componentDidCatch(error, info):

class ErrorBoundary extends React.Component {
  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }
}

Both will trigger after subtree rendering errors, but there are subtle differences in trigger timing:

  • static getDerivedStateFromError: Triggers in render phase, not allowed to contain side effects (otherwise multiple executions will cause problems)

  • componentDidCatch: Triggers in commit phase, therefore allowed to contain side effects (such as logErrorToMyService)

Former's trigger timing is early enough, so can do some remedial measures, such as avoiding null ref causing chain errors

Another difference is Did series lifecycles (such as componentDidCatch) don't support SSR, while getDerivedStateFromError considered SSR in design (currently v16.6.3 doesn't support yet, but said will support)

Currently these two APIs have overlapping functionality, both can do UI degradation by changing state after subtree errors, but will subdivide respective responsibilities in the future:

  • static getDerivedStateFromError: Specialized for UI degradation

  • componentDidCatch: Specialized for error reporting

VI. Deprecated APIs

Two more APIs to be put in cold palace:

  • ReactDOM.findDOMNode(): Performance reasons and design problems, suggest switching to ref forwarding

  • Old Context API: Performance and implementation reasons, suggest switching to new Context API

P.S. Can still use temporarily, but will be removed in future versions, can use StrictMode to complete migration

VII. Summary

Function components also get "shouldComponentUpdate", and beautiful Code-Splitting support, as well as patch APIs to alleviate Context Consumer verbosity pain, and clear-responsibility UI layer fallback solution

13 Types of React Components

v16.6 added several types of components (REACT_MEMO_TYPE, REACT_LAZY_TYPE, REACT_SUSPENSE_TYPE), counting them, there are actually this many:

  • REACT_ELEMENT_TYPE: Ordinary React component type, such as <MyComponent />

  • REACT_PORTAL_TYPE: Portals component, ReactDOM.createPortal()

  • REACT_FRAGMENT_TYPE: Fragment virtual component, <></> or <React.Fragment></React.Fragment> or [,:]

  • REACT_STRICT_MODE_TYPE: Strict Mode component with deprecated API checking, <React.StrictMode>

  • REACT_PROFILER_TYPE: Used to enable component-level performance profiling, see Profiler RFC, currently still experimental API, <React.unstable_Profiler> will become <React.Profiler> after stabilization

  • REACT_PROVIDER_TYPE: Context data producer Context.Provider, <React.createContext(defaultValue).Provider>

  • REACT_CONTEXT_TYPE: Context data consumer Context.Consumer, <React.createContext(defaultValue).Consumer>

  • REACT_ASYNC_MODE_TYPE: Async mode component that enables async features, deprecated, switch to REACT_CONCURRENT_MODE_TYPE

  • REACT_CONCURRENT_MODE_TYPE: Used to enable async features, temporarily not released yet, in Demo phase, <React.unstable_ConcurrentMode> will become <React.ConcurrentMode> after stabilization

  • REACT_FORWARD_REF_TYPE: Component that passes Ref downward, React.forwardRef()

  • REACT_SUSPENSE_TYPE: Component-level delayed rendering, <Suspense fallback={<MyLoadingComponent>}>

  • REACT_MEMO_TYPE: Higher-order component similar to PureComponent, React.memo()

  • REACT_LAZY_TYPE: Dynamically imported component, React.lazy()

Once upon a time, v15- only had 1 type REACT_ELEMENT_TYPE...

References

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment