CharmyZ/React

React V16

Opened this issue · 0 comments

_20180925105839

2018-09-25_110505

React 16.3 新增的生命周期方法

getDerivedStateFromProps()

getSnapshotBeforeUpdate()


逐渐废弃的生命周期方法:

componentWillMount()

componentWillReceiveProps()

componentWillUpdate()


一般将生命周期分成三个阶段:

创建阶段(Mounting)

更新阶段(Updating)

卸载阶段(Unmounting)

从 React v16 开始,还对生命周期加入了错误处理(Error Handling)。


创建阶段 Mounting

组件实例创建并插入 DOM 时,按顺序调用以下方法:

  • constructor()
  • static getDerivedStateFromProps()
  • componentWillMount()/UNSAFE_componentWillMount()(being deprecated)
  • render()
  • componentDidMount()

有定义 getDerivedStateFromProps 时,会忽略 componentWillMount()/UNSAFE_componentWillMount()


constructor()

constructor(props)

构造函数通常用于:

  • 使用 this.state 来初始化 state
  • 给事件处理函数绑定 this

注意:ES6 子类的构造函数必须执行一次 super()React 如果构造函数中要使用 this.props,必须先执行 super(props)


static getDerivedStateFromProps()

static getDerivedStateFromProps(nextProps, prevState)

当创建时、接收新的 props 时、setState 时、forceUpdate 时会执行这个方法。

注意:v16.3 setState 时、forceUpdate 时不会执行这个方法,v16.4 修复了这个问题。

这是一个 静态方法,参数 nextProps 是新接收的 propsprevState 是当前的 state。返回值(对象)将用于更新 state,如果不需要更新则需要返回 null

下面是官方文档给出的例子

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

这个方法的常用作用也很明显了:父组件传入新的 props 时,用来和当前的 state 对比,判断是否需要更新 state。以前一般使用 componentWillReceiveProps 做这个操作。

这个方法在建议尽量少用,只在必要的场景中使用,一般使用场景如下:

  1. 无条件的根据 props 更新 state
  2. props state 的不匹配情况更新 state

详情可以参考官方文档的最佳实践 You Probably Don’t Need Derived State

componentWillMount()/UNSAFE_componentWillMount()(弃用)

UNSAFE_componentWillMount()

这个方法已经不推荐使用。因为在未来异步渲染机制下,该方法可能会多次调用。它所行使的功能也可以由componentDidMount()constructor() 代替:

  • 之前有些人会把异步请求放在这个生命周期,其实大部分情况下都推荐把异步数据请求放在 componentDidMount() 中。
  • 在服务端渲染时,通常使用 componentWillMount() 获取必要的同步数据,但是可以使用 constructor() 代替它。

可以使用 setState,不会触发 re-render


render

rander()

每个类组件中,render() 唯一必须的方法。

render() 正如其名,作为渲染用,可以返回下面几种类型:

  • React 元素(React elements)
  • 数组(Arrays)
  • 片段(fragments)
  • 插槽(Portals)
  • 字符串或数字(String and numbers)
  • 布尔值或 null(Booleans or null)

注意:

  • Arrays 和 String 是 v16.0.0 新增。
  • fragments 是 v16.2.0 新增。
  • Portals 是 V16.0.0 新增。

里面不应该包含副作用,应该作为纯函数。

不能使用 setState


componentDidMount()

componentDidMount()

组件完成装载(已经插入 DOM 树)时,触发该方法。这个阶段已经获取到真实的 DOM

一般用于下面的场景:

  • 异步请求 ajax
  • 添加事件绑定(注意在 componentWillUnmount 中取消,以免造成内存泄漏)

可以使用 setState,触发re-render,影响性能。


更新阶段 Updating

  • componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()(being deprecated)
  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • componentWillUpdate()/UNSAFE_componentWillUpdate()(being deprecated)
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

getDerivedStateFromProps 或者 getSnapshotBeforeUpdate 时,componentWillReceiveProps()/UNSAFE_componentWillReceiveProps() componentWillUpdate()/UNSAFE_componentWillUpdate() 不会执行 (详情查看源码)


componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()(弃用)

UNSAFE_componentWillReceiveProps(nextProps)

这个方法在接收新的 props 时触发,即使 props 没有变化也会触发。

一般用这个方法来判断 props 的前后变化来更新 state,如下面的例子:

class ExampleComponent extends React.Component {
  state = {
    isScrollingDown: false,
  };
 
  componentWillReceiveProps(nextProps) {
    if (this.props.currentRow !== nextProps.currentRow) {
      this.setState({
        isScrollingDown:
          nextProps.currentRow > this.props.currentRow,
      });
    }
  }
}

这个方法将被弃用,推荐使用 getDerivedStateFromProps 代替。

可以使用 setState

static getDerivedStateFromProps()

Mounting 时所述一致。

shouldComponentUpdate()

在接收新的 props 或新的 state 时,在渲染前会触发该方法。

该方法通过返回 true 或者 false 来确定是否需要触发新的渲染。返回 false, 则不会触发后续的 UNSAFE_componentWillUpdate()render() componentDidUpdate()(但是 state 变化还是可能引起子组件重新渲染)。

所以通常通过这个方法对 props state 做比较,从而避免一些不必要的渲染。

PureComponent 的原理就是对 props state 进行浅对比(shallow comparison),来判断是否触发渲染。


componentWillUpdate()/UNSAFE_componentWillUpdate() (弃用)

UNSAFE_componentWillUpdate(nextProps, nextState)

当接收到新的 props state 时,在渲染前执行该方法。

在以后异步渲染时,可能会出现某些组件暂缓更新,导致 componentWillUpdate componentDidUpdate 之间的时间变长,这个过程中可能发生一些变化,比如用户行为导致 DOM 发生了新的变化,这时在 componentWillUpdate 获取的信息可能就不可靠了。

不能使用 setState

render()
Mounting 时所述一致。


getSnapshotBeforeUpdate()

getSnapShotBeforeUpdate(prevProps, prevState)

这个方法在 render() 之后,componentDidUpdate() 之前调用。

两个参数 prevProps 表示更新前的 propsprevState 表示更新前的 state

返回值称为一个快照(snapshot),如果不需要 snapshot,则必须显示的返回 null —— 因为返回值将作为 componentDidUpdate() 的第三个参数使用。所以这个函数必须要配合componentDidUpdate()一起使用。

这个函数的作用是在真实 DOM 更新(componentDidUpdate)前,获取一些需要的信息(类似快照功能),然后作为参数传给 componentDidUpdate。例如:在 getSnapShotBeforeUpdate 中获取滚动位置,然后作为参数传给 componentDidUpdate,就可以直接在渲染真实的 DOM 时就滚动到需要的位置。

下面是官方文档给出的例子:

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }
 
  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) {
      const list = this.listRef.current;
      return list.scrollHeight - list.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) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }
 
  render() {
    return (
      
{/* ...contents... */}
); }

componentDidUpdate()

componentDidUpdate(prevProps, prevState, snapshot)

这个方法是在更新完成之后调用,第三个参数 snapshot 就是 getSnapshotBeforeUpdate 的返回值。

正如前面所说,有 getSnapshotBeforeUpdate 时,必须要有 componentDidUpdate。所以这个方法的一个应用场景就是上面看到的例子,配合 getSnapshotBeforeUpdate 使用。

可以使用 setState,会触发 re-render,所以要注意判断,避免导致死循环。


卸载阶段 Unmounting

componentWillUnmount()

componentWillUnmount()

在组件卸载或者销毁前调用。这个方法主要用来做一些清理工作,例如:

  • 取消定时器
  • 取消事件绑定
  • 取消网络请求

不能使用 setState


错误处理 Error Handling

React 16 引入了错误边界的概念。

错误边界是一种 React 组件,可以捕获子组件树中的 JavaScript 错误。它们会记录这些错误,并在错误 UI 上显示出来。错误边界会在渲染期间、生命周期方法中以及整个树的构造函数中捕获错误。

如果一个类组件定义了一个名为componentDidCatch(error,info) 的生命周期方法,那么它就会成为错误边界:

carbon 3

然后,你可以将其作为常规组件来使用。

carbon 2

componentDidCatch() 方法就像 JavaScript catch{}块,只是用在了组件上。只有类组件可以成为错误边界。实际上,大多数情况下,你每次只需要声明一个错误边界组件,然后就可以在整个应用程序中使用它。
任何子组件在渲染期间,生命周期方法中或者构造函数 constructor 发生错误时调用。

错误边界不会捕获下面的错误:

  • 事件处理 (Event handlers) (因为事件处理不发生在 React 渲染时,报错不影响渲染)
  • 异步代码 (Asynchronous code) (e.g. setTimeout or requestAnimationFrame callbacks)
  • 服务端渲染 (Server side rendering)
  • 错误边界本身(而不是子组件)抛出的错误

新的 render 返回类型:片段和字符串

在渲染时可以摆脱将组件包装在 div 中。

你现在可以从组件的 render 方法返回元素数组。与其他数组一样,你需要为每个元素添加一个键以避免发出键警告:

render() {
  // 不需要将清单项包装在额外的元素中!
  return [
    // 不要忘了分配不同的键 :)
    <li key="A">
             First item
    </li>,
    <li key="B">
             Second item
    </li>,
    <li key="C">
             Third item
    </li>,
  ];
}

从 16.2.0 版本开始,React 支持片段语法,不再需要键。

支持返回字符串:

render() {
  return 'Look ma, no spans!';
}

Portal

Portal 提供了一种将子组件渲染为存在于父组件的 DOM 层次结构之外的 DOM 节点的方法。

ReactDOM.createPortal(child, container)

第一个参数(child)是可渲染的 React 子节点,如元素、字符串或片段。第二个参数(container)是一个 DOM 元素。

在从组件的 render 方法返回一个元素时,它将作为最近父节点的子节点挂载到 DOM 中:

render() {
  // React 加载一个新 div,并在其中渲染子组件
  return (
    <div>
      {this.props.children}
    </div>
  );
}

有时候需要将子组件插入到 DOM 的其他位置:

render() {
  // React 不创建一个新 div。它将子组件渲染到`domNode`中。
  // `domNode` 是一个合法的 DOM 节点,不管它在 DOM 中处于什么位置。
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}

Portal 的一个典型用例是这样的:当父组件带有 overflow:hidden z-index 样式时,你希望子组件在视觉上能够“突破”它的容器。例如,对话框、悬停卡和工具提示。


自定义 DOM 属性

React15 会忽略任何未知的 DOM 属性。React 会跳过它们,因为无法识别它们。

// 你的代码:
<div mycustomattribute="something" />

React 15 将渲染一个空的 div:

// React 15 的输出:
<div />

React16 的输出如下(自定义属性会被显示出来,根本不会被忽略):

// React 16 的输出:
<div mycustomattribute="something" />

在 state 中设置 null 避免重新渲染

在 React16 中,你可以直接在 setState() 中防止 state 更新和重新渲染。你只需要让你的函数返回 null

const MAX_PIZZAS = 20;

function addAnotherPizza(state, props) {
  // 如果我有足够的披萨,就停止更新和重新渲染。
  if (state.pizza === MAX_PIZZAS) {
    return null;
  }

  // 否则的话,让披萨来得更猛烈些吧:D
  return {
    pizza: state.pizza + 1,
  }
}

this.setState(addAnotherPizza);

创建 ref

在 React16 中创建 ref 要容易得多。为什么需要使用 ref

  • 管理焦点、文本选择或媒体播放。
  • 触发动画。
  • 与第三方 DOM 库集成。

ref 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。ref 通常是在构造组件时被分配给实例的属性,以便在整个组件中引用它们。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

ref 被传给元素后,可以通过 ref current 属性访问对节点的引用。

const node = this.myRef.current;

ref 的值因节点的类型不同而有所不同:

ref 属性用于 HTML 元素时,在构造函数中使用 React.createRef() 创建的 ref 将底层 DOM 元素作为 current 属性。当 ref 属性用于自定义类组件时,ref 对象将已挂载的组件实例作为 current 属性。你可能不会在功能组件上使用 ref 属性,因为它们没有实例。


Context API

Context 提供了一种通过组件树传递数据的方法,无需在每一层手动传递 prop

React.createContext

const {Provider, Consumer} = React.createContext(defaultValue);

创建{Provider,Consumer}对。当 React 渲染 Consumer 时,它将从树中最接近的 Provider 读取当前上下文值。

defaultValue 参数只在消费者在树中找不到匹配的 Provider 时才会用到,这在单独测试组件时十分有用。注意:将 undefined 作为 Provider 值传递进去并不会导致 Consumer 使用 defaultValue

Provider

&lt;Provider value={/* 一些值 */}&gt;

一个允许 Consumer 订阅上下文变更的 React 组件。

一个 Provider 可以连接多个 Consumer,可以在树中嵌套 Provider,实现更深的值覆盖。

Consumer

<Consumer>
  {value => /* 基于上下文值渲染一些东西 */}
</Consumer>

一个订阅上下文变更的 React 组件。

需要一个函数作为子组件。这个函数接收当前上下文值,并返回一个 React 节点。传给函数的 value 参数将等于树中最近的 Provider value。如果没有匹配的 Provider,则 value 参数将等于传给 createContext() defaultValue


静态 getDerivedStateFromProps()

getDerivedStateFromProps 会在调用 render 方法之前被调用,它应该返回一个用于更新状态的对象,或者如果不更新任何状态就返回 null

这个方法适用于一些罕见的用例,其中 state 依赖 prop 的变化。例如,可以很方便地实现一个组件,它会比较上一个和下一个子组件,然后决定它们中的哪个需要进行动画渲染。

衍生 state 会导致冗长的代码,并让你的组件难以开发和维护。

你可以考虑更简单的替代方案:

如果你需要在 prop 发生变更时做一些其他事情(例如数据提取或动画),请改用 componentDidUpdate。如果你只想在 prop 发生变更时重新计算某些数据,请改用备忘辅助器:

点击查看

如果你想在 prop 发生变更时“重置”某个状态,请考虑创建完全控制组件或带有键的完全不受控制组件。

这个方法无法访问到组件实例。如果你愿意,可以在类定义之外提取组件 prop state 的纯函数,在 getDerivedStateFromProps() 和其他类方法之间重用一些代码。

请注意,不管怎样,这个方法都会在每次进行渲染时触发。这与 UNSAFE_componentWillReceiveProps 完全相反。它只在父组件进行重新渲染时触发,而且不作为本地 setState 的结果。

我们将 nextProps.someValuethis.props.someValue 进行对比。如果它们的值不一样,我们就执行一些操作:

static getDerivedStateFromProps(nextProps, prevState){   if(nextProps.someValue!==prevState.someValue){     
   return { someState: nextProps.someValue};  
} else return null;}

它接收两个参数 nextProps prevState。如前所述,你无法在这个方法中访问 this。你必须将 prop 存储在 state 中,然后将 nextProps 与之前的 prop 进行对比。在上面的代码中,nextProps prevState 进行了比较。如果两者不同,则返回一个用于更新状态的对象,否则就返回 null,表示不需要更新状态。如果 state 发生变更,就会调用 componentDidUpdate,我们可以像在 componentWillReceiveProps 中那样执行所需的操作。