React 16.6新API

一.概览

新增了几个方便的特性:

  • React.memo :函数式组件也有“shouldComponentUpdate”生命周期了

  • React.lazy :配合Suspense特性轻松优雅地完成代码拆分(Code-Splitting)

  • static contextType :class组件可以更容易地访问单一Context

  • static getDerivedStateFromError() :SSR友好的“componentDidCatch”

其中最重要的是Suspense特性,在之前的 React Async Rendering 中提到过:

另外,将来会提供一个suspense(挂起)API,允许挂起视图渲染,等待异步操作完成,让loading场景更容易控制,具体见Sneak Peek: Beyond React 16演讲视频里的第2个Demo

而现在(v16.6.0,发布于2018/10/23),就是大约8个月之后的“将来”

二.React.memo

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

还有个可选的 compare 参数:

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);

类似于 PureComponent 的高阶组件,包一层 memo ,就能让普通函数式组件拥有 PureComponent 的性能优势:

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

内部实现

实现上非常简单:

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,
  };
}

无非就是 外挂式shouldComponentUpdate生命周期 ,对比class组件:

// 普通class组件
class MyClassComponent {
  // 没有默认的shouldComponentUpdate,可以手动实现
  shouldComponentUpdate(oldProps: Props, newProps: Props): boolean {
    return true;
  }
}

// 继承自PureComponent的组件相当于
class MyPureComponent {
  // 拥有默认shouldComponentUpdate,即shallowEqual
  shouldComponentUpdate(oldProps: Props, newProps: Props): boolean {
    return shallowEqual(oldProps, newProps);
  }
}

// 函数式组件
function render() {
  // 函数式组件,不支持shouldComponentUpdate
}

// Memo组件相当于
const MyMemoComponent = {
  type: function render() {
    // 函数式组件,不支持shouldComponentUpdate
  }
  // 拥有默认的(挂在外面的)shouldComponentUpdate,即shallowEqual
  compare: shallowEqual
};

如此这般,就给函数式组件粘了个 shouldComponentUpdate 上去,接下来的事情猜也能猜到了:

// 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,
      );
    }
  }
}

所以,从实现上来看, React.memo() 这个API与memo关系倒不大,实际意义是: 函数式组件也有“shouldComponentUpdate”生命周期了

注意, compare 默认是 shallowEqual ,所以 React.memo 第二个参数 compare 实际含义是 shouldNotComponentUpdate ,而不是我们所熟知的相反的那个。API设计上确实有些迷惑,非要引入一个相反的东西:

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

P.S.RFC定稿过程中第二个参数确实备受争议( equal, arePropsEqual, arePropsDifferent, renderOnDifference, isEqual, shouldUpdate... 等10000个以内),具体见 React.memo()

手动实现个memo?

话说回来,这样一个高阶组件其实不难实现:

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

    return rendered;
  }
}

手动实现的这个盗版与官方版本功能上等价(甚至性能也不相上下),所以又一个锦上添花的东西

三.React.lazy: Code-Splitting with Suspense

相当漂亮 的特性,篇幅限制(此处删掉了276行),暂不展开

四.static contextType

v16.3推出了新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 (
      // 这一部分看起来很麻烦,读个context而已
      <ThemeContext.Consumer>
        {theme => <Button theme={theme} />}
      </ThemeContext.Consumer>
    );
  }
}

为了让class组件访问Context数据方便一些,新增了 static contextType 特性:

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

  render() {
    let theme = this.context;

    return (
      // 喧嚣停止了
      <Button theme={theme} />
    );
  }
}

其中 contextType ( 注意 ,之前那个旧的多个 s ,叫 contextTypes )只支持 React.createContext() 返回类型,翻新了 旧Context API 的 this.context (变成单一值了,之前是对象)

用法上不那么变态了,但 只支持访问单一Context值 。要访问一堆Context值的话,只能用上面看起来 很麻烦的那种方式 :

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

五.static getDerivedStateFromError()

static getDerivedStateFromError(error)

又一个错误处理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; 
  }
}

用法与v16.0的 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);
  }
}

二者都会在子树渲染出错后触发,但 触发时机上存在微妙的差异 :

  • static getDerivedStateFromError :在render阶段触发,不允许含有副作用(否则多次执行会出问题)

  • componentDidCatch :在commit阶段触发,因此允许含有副作用(如 logErrorToMyService )

前者的触发时机足够早,所以能够多做一些补救措施,比如避免 null ref 引发连锁错误

另一个区别是 Did 系列生命周期(如 componentDidCatch )不支持SSR,而 getDerivedStateFromError 从设计上就考虑到了SSR(目前v16.6.3还不支持,但说了会支持)

目前这两个API在功能上是有重叠的,都可以在子树出错之后通过改变 state 来做UI降级,但后续会细分各自的职责 :

  • static getDerivedStateFromError :专做UI降级

  • componentDidCatch :专做错误上报

六.过时API

又两个API要被打入冷宫:

  • ReactDOM.findDOMNode() :性能原因以及 设计上的问题 ,建议换用 ref forwarding

  • 旧Context API:性能及实现方面的原因,建议换用新Context API

P.S.暂时还能用,但将来版本会去掉,可以借助 StrictMode 完成迁移

七.总结

函数式组件也迎来了“shouldComponentUpdate”,还有漂亮的Code-Splitting支持,以及缓解Context Consumer繁琐之痛的补丁API,和职责清晰的UI层兜底方案

13种React组件

v16.6新增了几类组件( REACT_MEMO_TYPE 、 REACT_LAZY_TYPE 、 REACT_SUSPENSE_TYPE),细数一下,竟然有这么多了:

  • REACT_ELEMENT_TYPE :普通React组件类型,如 <MyComponent />

  • REACT_PORTAL_TYPE : Protals 组件, ReactDOM.createPortal()

  • REACT_FRAGMENT_TYPE : Fragment 虚拟组件, <></> 或 <React.Fragment></React.Fragment> 或 [,]

  • REACT_STRICT_MODE_TYPE :带过时API检查的 严格模式 组件, <React.StrictMode>

  • REACT_PROFILER_TYPE :用来开启组件范围性能分析,见 Profiler RFC ,目前还是实验性API, <React.unstable_Profiler> 稳定之后会变成 <React.Profiler>

  • REACT_PROVIDER_TYPE :Context数据的生产者 Context.Provider , <React.createContext(defaultValue).Provider>

  • REACT_CONTEXT_TYPE :Context数据的消费者 Context.Consumer , <React.createContext(defaultValue).Consumer>

  • REACT_ASYNC_MODE_TYPE :开启异步特性的异步模式组件,过时了,换用 REACT_CONCURRENT_MODE_TYPE

  • REACT_CONCURRENT_MODE_TYPE :用来开启异步特性,暂时还没放出来,处于 Demo阶段 ,<React.unstable_ConcurrentMode> 稳定之后会变成 <React.ConcurrentMode>

  • REACT_FORWARD_REF_TYPE :向下 传递Ref的组件 , React.forwardRef()

  • REACT_SUSPENSE_TYPE :组件范围 延迟渲染 , <Suspense fallback={<MyLoadingComponent>}>

  • REACT_MEMO_TYPE : 类似于PureComponent的高阶组件 , React.memo()

  • REACT_LAZY_TYPE : 动态引入的组件 , React.lazy()

曾几何时,v15-只有1种 REACT_ELEMENT_TYPE ……

参考资料

原文https://www.tuicool.com/articles/miqAjyF

猜你喜欢

转载自blog.csdn.net/sinat_17775997/article/details/84203089