前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 16.6新API

React 16.6新API

作者头像
ayqy贾杰
发布2019-06-12 15:01:35
7240
发布2019-06-12 15:01:35
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬

一.概览

新增了几个方便的特性:

  • 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_TYPEREACT_LAZY_TYPEREACT_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……

参考资料

  • React v16.6.0: lazy, memo and contextType
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.概览
  • 二.React.memo
    • 内部实现
      • 手动实现个memo?
      • 三.React.lazy: Code-Splitting with Suspense
      • 四.static contextType
      • 五.static getDerivedStateFromError()
      • 六.过时API
      • 七.总结
        • 13种React组件
          • 参考资料
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档