A Changelog for react

16.2 ~ 16.8

16.3

  • createContext
  • createRef
  • forwadRef
  • Lifecycle change
  • <StictMode />

create Context

  public render() {
    return (
      <PanelContext.Provider value={ this.state }>
        <Panel />
      </PanelContext.Provider>
    )
  }

Parent:

Any Nested Children:

export class Panel extends React.Component {

  public render() {
    return (
      <PanelContext.Consumer>
        { this.content }
      </PanelContext.Consumer>
    )
  }

  private content(context: IPanelContext) {
    return (
      <>
        <div>
          { context.actived  }
        </div>
        <button onClick={ context.changeType }>
          change
        </button>
      </>
    )
  }

}

但凡在组件树中的某一个节点上挂载了 Context.Provider

那么这个节点的任意一个子孙节点都可以使用 Context.Consumer 来获得状态

(一种跨层级的状态传递)

  • 可以一定程度缓解使用 Redux 时产生的层层传递状态的问题
  • 解决 GTA 埋点需要各种破坏组件的 interface 问题

create Ref


export default class extends React.Component {

  private ref = React.createRef<HTMLDivElement>()

  private handleClick = () => {
    console.log(this.ref.current, '当前节点')
  }

  public render() {
    return (
      <div ref={ this.ref } onClick={ this.handleClick }>
        CREATE REF DEMO
      </div>
    )
  }

}

CURRENT:

PREVIOUS:

export default class extends React.Component {

  private ref: HTMLDivElement | null
  private saveRef = (ref: HTMLDivElement | null) => {
    this.ref = ref
  }

  render() {
    return <div ref={ this.saveRef } />
  }

}

没什么好多说的,更好用呗

FORWARD Ref

import React from 'react'

import { Button } from '@teambition/clarity-design'

export HOCWrap class extends React.Component {
  
  render() {
    return React.forwardRef((props, ref) => <Button ref={ ref } { ...props } />)
  }

}

解决 HOC / Decorator 下 ref 穿透的问题

Lifecycle change

Add:

getDerivedStateFromProps
getSnapshotBeforeUpdate

UNSAFE:

componentWillMount
componentWillReceiveProps
componentWillUpdate
static getDerivedStateFromProps
  • 用于取代 componentWillReceiveProps
  • 原先 componentWillReceiveProps 是一个实例方法,而这个 API 是一个类的静态方法
  • 如果改变 props 的同时,有副作用的产生,这时应该使用 componentDidUpdate
  • 如果想要根据 props 计算属性,应该考虑将结果 memoization 化
  • 如果想要根据 props 变化来重置某些状态,应该考虑使用受控组件
static getDerivedStateFromProps

  UNSAFE_componentWillReceiveProps(nextProps: TabsProps) {
    if ('activeKey' in nextProps) {
      this.setState({
        activeKey: nextProps.activeKey!
      })
    } else if (!this.activeKeyIsValid(nextProps, this.state.activeKey)) {
      this.setState({
        activeKey: this.getDefaultActiveKey(nextProps),
      })
    }
  }

abstract class A extends React.Component {

  static getDerivedStateFromProps(props: TabsProps, state: TabsState) {
    if ('activeKey' in props) {
      return {
        activeKey: nextProps.activeKey!
      }
    } else if (!A.activeKeyIsValid(props, state.activeKey)) {
      return {
        activeKey: A.getDefaultActiveKey(props),
      }
    }

    return null
  }

  static activeKeyIsValid(props: TabsProps, key: string) {
    return true
  }

  static getDefaultActiveKey() {
    return // balabala
  }

  abstract render() 

}
getSnapshotBeforeUpdate
  // 使用 getSnapshotBeforeUpdate 时必须和 componentDidUpdate 成对出现

  getSnapshotBeforeUpdate(prevProps: any, prevState: any) {
    if (this.props.value !== this.state.str) {
      return { needScroll: true }
    }
    return null
  }

  componentDidUpdate(_1, _2, snapshot) {
    if (snapshot.needScroll) {
      this.domRef.scrollTop = 0
    }
  } 

<StrictMode />
  • 识别被标志位不安全的生命周期函数
  • 对弃用的 API 进行警告
  • 探测某些产生副作用的方法
  • 检测是否使用 findDOMNode
  • 检测是否采用了老的 Context API

16.4

  • Pointer Events

Pointer Events

指针事件是为指针设备触发的 DOM 事件。它们旨在创建单个 DOM 事件模型来处理指向输入设备,例如鼠标,笔 / 触控笔或触摸(例如一个或多个手指)。指针是一个与硬件无关的设备,可以定位一组特定的屏幕坐标。拥有指针的单个事件模型可以简化创建 Web 站点和应用程序,并提供良好的用户体验,无论用户的硬件如何。但是,对于需要特定于设备的处理的场景,指针事件定义了一个 pointerType 属性,用于检查产生事件的设备类型。

Ref

https://reactjs.org/blog/2018/05/23/react-v-16-4.html

16.5

  • Profiler

Profiler

React 16.5 添加了对新的 profiler DevTools 插件的支持。这个插件使用 React 的 Profiler 实验性 API 去收集所有 component 的渲染时间,目的是为了找出 React App 的性能瓶颈,它将会和 React 即将发布的 时间片 特性完全兼容。

P.S. 该模块支持 Production 模式下的 profile

Ref

https://juejin.im/post/5ba0f8e4f265da0ab915bcf2

import { unstable_Profiler as Profiler } from "react";
import { render } from "react-dom";
import { unstable_trace as trace } from "scheduler/tracing";

trace("initial render", performance.now(), () =>
  render(
    <Profiler id="Application" onRender={onRender}>
      <Application />
    </Profiler>,
    domElement
  )
);

16.6

  • Memo
  • lazy / Suspense
  • static contextType
  • getDerivedStateFromError

memo

const MemoizedComponent = React.memo(props => {
  // 类似等价于给 SFC 的 SCU
})

lazy / Suspense

React.lazy() 提供了动态 import 组件的能力,实现代码分割。

Suspense 作用是在等待组件时 suspend(暂停)渲染,并显示加载标识。

目前 React v16.6 中 Suspense 只支持一个场景,即使用 React.lazy() 和 实现的动态加载组件。

另外,在将来,Suspense 将会与 react-cache (WIP) 助力开发者面对简单缓存场景下的开发体验

import React, { lazy, Suspense } from 'react'
const OtherComponent = lazy(() => import('./OtherComponent'))

function MyComponent() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <OtherComponent />
    </Suspense>
  )
}

static contextType

主要就是因为之前基于 render callback pattern 的 context 使用体验过于糟糕,因此在 16.6 做了改进

https://reactjs.org/docs/context.html#classcontexttype

const MyContext = React.createContext()

class MyClass extends React.Component {

  static contextType = MyContext
  
  componentDidMount() {
    const value = this.context
  }
  
  componentDidUpdate() {
    const value = this.context
  }
  
  componentWillUnmount() {
    const value = this.context
  }
  
  render() {
    const value = this.context
  }

}

getDerivedStateFromError

与 componentDidCatch 不同的是,该生命周期会更早的触发,允许开发者在 render 完成之前渲染 Fallback UI,也因此,我们可以在 getDerivedStateFromError 中更新状态,在 componentDidCatch 中上报错误信息。

16.7

  • Bugfix

16.8

  • Hooks

Hooks

export function useResizer() {
  const [ value, setValue ] = useState([window.innerWidth, window.innerHeight])

  useEffect(() => {
    const subscription = resizer$.subscribe(setValue)
    return () => subscription.unsubscribe()
  }, []) // 第二个参数很重要

  return value
}
  • 状态逻辑复用

  • 让函数式组件得以拥有 “状态”

  • 降维异步数据

disadvantage

  • HOOK 虽然是一个 FUNCTION,但最好别当它是一个 JAVASCRIPT FUNCTION,更倾向于是一个 REACT FUNCTION,它的本质相对于 JAVASCRIPT 是不自然的

  • HOOK 和函数式组件相依相随,是严格的伴生关系

  • 尽量不要试图去抽象 HOOK

  • 在严格需要测试的场景中,测试的方案仍然不够明朗

Ref

https://zhuanlan.zhihu.com/p/49408348

https://www.zhihu.com/question/300049718

Event driven programming is not _inherently_ incompatible with React but if you start treating React’s render cycle as if it’s push based, then yeah, things are going to break.

Let's review the code

A Changelog for React

By 知白 Saviio

A Changelog for React

  • 571