博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
盘点 React 16.0 ~ 16.5 主要更新及其应用
阅读量:5935 次
发布时间:2019-06-19

本文共 9828 字,大约阅读时间需要 32 分钟。

大约一年前,React 团队发布了 React 16.0。时至今日,已更新到 16.5 。这其中有不少激动人心的特性(如 Fiber 架构的引入、新的周期函数、全新 Context API、Fragment、Error Boundary、Portal 等)都值得开发者跟进学习。本文就以 为引,选取几个重要且用于工作的更新,和大家一起学习。所有示例代码在 , 配合文章一起食用更佳~ ?

目录


生命周期函数的更新

随着 React 16.0 发布, React 采用了新的内核架构 Fiber,在新的架构中它将更新分为两个阶段:Render Parse 和 Commit Parse, 也由此引入了 getDerivedStateFromPropsgetSnapshotBeforeUpdatecomponentDidCatch 等三个生命周期函数。同时,也将 componentWillMountcomponentWillReceivePropscomponentWillUpdate 标记为不安全的方法。

new lifecycle

新增

  • static getDerivedStateFromProps(nextProps, prevState)
  • getSnapshotBeforeUpdate(prevProps, prevState)
  • componentDidCatch(error, info)

标记为不安全

  • componentWillMount(nextProps, nextState)
  • componentWillReceiveProps(nextProps)
  • componentWillUpdate(nextProps, nextState)

static getDerivedStateFromProps(nextProps, prevState)

根据 getDerivedStateFromProps(nextProps, prevState) 的函数签名可知: 其作用是根据传递的 props 来更新 state。它的一大特点是 无副作用 : 由于处在 Render Phase 阶段,所以在每次的更新都要触发, 故在设计 API 时采用了静态方法,其好处是单纯 —— 无法访问实例、无法通过 ref 访问到 DOM 对象等,保证了单纯且高效。值得注意的是,其仍可以通过 props 的操作来产生副作用,这时应该将操作 props 的方法移到 componentDidUpdate 中,减少触发次数。

例:

state = { isLogin: false }static getDerivedStateFromProps(nextProps, prevState) {  if(nextProps.isLogin !== prevState.isLogin){    return {      isLogin: nextProps.isLogin    }  }  return null}componentDidUpdate(prevProps, prevState){  if(!prevState.isLogin && prevProps.isLogin) this.handleClose()}

但在使用时要非常小心,因为它不像 componentWillReceiveProps 一样,只在父组件重新渲染时才触发,本身调用 setState 也会触发。官方提供了 3 条 checklist, 这里搬运一下:

  1. 如果改变 props 的同时,有副作用的产生(如异步请求数据,动画效果),这时应该使用 componentDidUpdate
  2. 如果想要根据 props 计算属性,应该考虑将结果 memoization 化,参见
  3. 如果想要根据 props 变化来重置某些状态,应该考虑使用受控组件

配合 componentDidUpdate 周期函数,getDerivedStateFromProps 是为了替代 componentWillReceiveProps 而出现的。它将原本 componentWillReceiveProps 功能进行划分 —— 更新 state 和 操作/调用 props,很大程度避免了职责不清而导致过多的渲染, 从而影响应该性能。

getSnapshotBeforeUpdate(prevProps, prevState)

根据 getSnapshotBeforeUpdate(prevProps, prevState) 的函数签名可知,其在组件更新之前获取一个 snapshot —— 可以将计算得的值或从 DOM 得到的信息传递到 componentDidUpdate(prevProps, prevState, snapshot) 周期函数的第三个参数,常常用于 scroll 位置的定位。摘自官方的示例:

class ScrollingList extends React.Component {  constructor(props) {    super(props)    // 取得dom 节点    this.listRef = React.createRef()  }  getSnapshotBeforeUpdate(prevProps, prevState) {    // 根据新添加的元素来计算得到所需要滚动的位置    if (prevProps.list.length < this.props.list.length) {      const list = this.listRef.current      return list.scrollHeight - list.scrollTop    }    return null  }  componentDidUpdate(prevProps, prevState, snapshot) {    // 根据 snapshot 计算得到偏移量,得到最终滚动位置    if (snapshot !== null) {      const list = this.listRef.current      list.scrollTop = list.scrollHeight - snapshot    }  }  render() {    return 
{/* ...contents... */}
}}

componentDidCatch(error, info)

在 16.0 以前,错误捕获使用 unstable_handleError 或者采用第三方库如 来捕获,前者捕获的信息十分有限,后者为非官方支持。而在 16.0 中,增加了 componentDidCatch 周期函数来让开发者可以自主处理错误信息,诸如展示,上报错误等,用户可以创建自己的Error Boundary 来捕获错误。例:

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

此外,用户还可以采用第三方错误追踪服务,如 、 等,保证了错误处理效率的同时也极大降级了中小型项目错误追踪的成本。

图片bugsnag

标记为不安全 componentWillMountcomponentWillReceivePropscomponentWillUpdate

componentWillMount

componentWillMount 可被开发者用于获取首屏数据或事务订阅。

开发者为了快速得到数据,将首屏请求放在 componentWillMount中。实际上在执行 componentWillMount时第一次渲染已开始。把首屏请求放在componentWillMount 的与否都不能解决首屏渲染无异步数据的问题。而官方的建议是将首屏放在 constructorcomponentDidMount中。

此外事件订阅也被常在 componentWillMount 用到,并在 componentWillUnmount 中取消掉相应的事件订阅。但事实上 React 并不能够保证在 componentWillMount 被调用后,同一组件的 componentWillUnmount 也一定会被调用。另一方面,在未来 React 开启异步渲染模式后,在 · 被调用之后,组件的渲染也很有可能会被其他的事务所打断,导致 componentWillUnmount 不会被调用。而 componentDidMount 就不存在这个问题,在 componentDidMount 被调用后,componentWillUnmount 一定会随后被调用到,并根据具体代码清除掉组件中存在的事件订阅。

对此的升级方案是把 componentWillMount 改为 componentDidMount 即可。

componentWillReceivePropscomponentWillUpdate

componentWillReceiveProps 被标记为不安全的原因见前文所述,其主要原因是操作 props 引起的 re-render。与之类似的 componentWillUpdate 被标记为不安全也是同样的原因。除此之外,对 DOM 的更新操作也可能导致重新渲染。

对于 componentWillReceiveProps 的升级方案是使用 getDerivedStateFromPropscomponentDidUpdate 来代替。

对于 componentWillUpdate 的升级方案是使用 componentDidUpdate 代替。如涉及大量的计算,可在 getSnapshotBeforeUpdate 完成计算,再在 componentDidUpdate 一次完成更新。

通过框架级别的 API 来约束甚至限制开发者写出更易维护的 Javascript 代码,最大限度的避免了反模式的开发方式。

全新的 Context API

在 React 16.3 之前,Context API 一直被官方置为不推荐使用(don’t use context),究其原因是因为老的 Context API 作为一个实验性的产品,破坏了 React 的分形结构。同时在使用的过程中,如果在穿透组件的过程中,某个组件的 shouldComponentUpdate 返回了 false, 则 Context API 就不能穿透了。其带来的不确定性也就导致被不推荐使用。随着 React 16.3 的发布,全新 Context API 成了一等 API,可以很容易穿透组件而无副作用,官方示例代码:

// Context lets us pass a value deep into the component tree// without explicitly threading it through every component.// Create a context for the current theme (with "light" as the default).const ThemeContext = React.createContext('light')class App extends React.Component {  render() {    // Use a Provider to pass the current theme to the tree below.    // Any component can read it, no matter how deep it is.    // In this example, we're passing "dark" as the current value.    return (      
) }}// A component in the middle doesn't have to// pass the theme down explicitly anymore.function Toolbar(props) { return (
)}function ThemedButton(props) { // Use a Consumer to read the current theme context. // React will find the closest theme Provider above and use its value. // In this example, the current theme is "dark". return (
{theme =>
}
)}

其过程大概如下:

  1. 通过 React.createContext 创建 Context 对象
  2. 在父组件上,使用<ThemeContext.Provider/> 来提供 Provider
  3. 在需要消费的地方,使用<ThemeContext.Consumer/> 以函数调用的方式{theme => <Button {...props} theme={theme} />}获得 Context 对象的值。

Context API 与 Redux

在状态的管理上,全新的 Context API 完全可以取代部分 Redux 应用,示例代码:

const initialState = {  theme: 'dark',  color: 'blue',}const GlobalStore = React.createContext()class GlobalStoreProvider extends React.Component {  render() {    return (      
{this.props.children}
) }}class App extends React.Component { render() { return (
{context => (
{context.theme}
{context.color}
)}
) }}

全新的 Context API 带来的穿透组件的能力对于需要全局状态共享的场景十分有用,无需进入额外的依赖就能对状态进行管理,代码简洁明了。

React Strict Mode

React StrictMode 可以在开发阶段发现应用存在的潜在问题,提醒开发者解决相关问题,提供应用的健壮性。其主要能检测到 4 个问题:

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

使用起来也很简单,只要在需要被检测的组件上包裹一层 React StrictMode ,示例代码 :

class App extends React.Component {  render() {    return (      
) }}

若出现错误,则在控制台输出具体错误信息:

React Strict Mode

Portal

由 ReactDOM 提供的 createPortal 方法,允许将组件渲染到其他 DOM 节点上。这对大型应用或者独立于应用本身的渲染很有帮助。其函数签名为

ReactDOM.createPortal(child, container), child 参数为任意的可渲染的 React Component,如 elementstingfragment 等,container 则为要挂载的 DOM 节点.

以一个简单的 Modal 为例, 代码见 :

import React from 'react'import ReactDOM from 'react-dom'const modalRoot = document.querySelector('#modal')export default class Modal extends React.Component {  constructor(props) {    super(props)    this.el = document.createElement('div')  }  componentDidMount() {    modalRoot.appendChild(this.el)  }  componentWillUnmount() {    modalRoot.removeChild(this.el)  }  handleClose = () => [this.props.onClose && this.props.onClose()]  render() {    const { visible } = this.props    if (!visible) return null    return ReactDOM.createPortal(      
{this.props.children}
[x]
, this.el ) }}

具体过程就是使用了 props 传递 children后, 使用 ReactDOM.createPortal, 将 container 渲染在其他 DOM 节点上的过程。

Refs

虽然 React 使用 Virtual DOM 来更新视图,但某些时刻我们还要操作真正的 DOM ,这时 ref 属性就派上用场了。

React.createRef

React 16 使用了 React.createRef 取得 Ref 对象,这和之前的方式还是有不小的差别,例:

// before React 16···  componentDidMount() {    // the refs object container the myRef    const el = this.refs.myRef    // you can  also using ReactDOM.findDOMNode    // const el = ReactDOM.findDOMNode(this.refs.myRef)  }  render() {    return 
}······// React 16+ constructor(props) { super(props) this.myRef = React.createRef() } render() { return
}···

React.forwardRef

另外一个新特性是 Ref 的转发, 它的目的是让父组件可以访问到子组件的 Ref,从而操作子组件的 DOM。

React.forwardRef 接收一个函数,函数参数有 propsref。看一个简单的例子,代码见 :

const TextInput = React.forwardRef((props, ref) => (  ))const inputRef = React.createRef()class App extends Component {  constructor(props) {    super(props)    this.myRef = React.createRef()  }  handleSubmit = event => {    event.preventDefault()    alert('input value is:' + inputRef.current.value)  }  render() {    return (      
) }}

这个例子使用了 React.forwardRefpropsref 传递给子组件,直接就可以在父组件直接调用。

Fragment

在向 DOM 树批量添加元素时,一个好的实践是创建一个document.createDocumentFragment,先将元素批量添加到 DocumentFragment 上,再把 DocumentFragment 添加到 DOM 树,减少了 DOM 操作次数的同时也不会创建一个新元素。

DocumentFragment 类似,React 也存在 Fragment 的概念,用途很类似。在 React 16 之前,Fragment 的创建是通过扩展包 react-addons-create-fragment 创建,而 React 16 中则通过 <React.Fragment></React.Fragment> 直接创建 'Fragment'。例如:

render() {  return (    
)}

如此,我们不需要单独包裹一层无用的元素(如使用<div></div>包裹),减少层级嵌套。

此外,还一种精简的写法:

render() {  return (    <>      
)}

其他

ReactDOMrender 函数可以数组形式返回 React Component

render(){  return [    
,
, ]}

移除内建的react-with-addons.js, 所有的插件都独立出来

之前常用的react-addons-(css-)transition-group,react-addons-create-fragment,react-addons-pure-render-mixinreact-addons-perf 等,除部分被内置,其余全部都独立为一个项目,使用时要注意。

总结

窥一斑而见全豹,React 16.0 ~ 16.5 的升级给了开发者一个更为纯粹的开发流程。API 层面的更改、架构的更替、工具类的拆分都在为构建更易维护的 JavaScript 应用而努力。拥抱变化,顺应时势。

由于笔者能力有限,文中难免有疏漏,还望读者不吝赐教。

以上。

Find me on

参考:

转载地址:http://zsjtx.baihongyu.com/

你可能感兴趣的文章
《大数据时代》作者:当下数据隐私保护方式是完全错误的
查看>>
光伏亟待资本注入 业界呼吁第三方“风险评级”
查看>>
2016年网络安全的五大发展趋势
查看>>
郑州成立中原大数据产业联盟
查看>>
MySQL命令行导出数据库
查看>>
“想哭”之余的追问 IEEE数名专家多角度剖析勒索病毒事件
查看>>
Mozilla计划让Firefox浏览器支持WebKit前缀网站
查看>>
VR 的负面效应有那些?其实专家们也不太清楚
查看>>
《实施Cisco统一通信管理器(CIPT2)》一1.9 参考
查看>>
广告软件购买 Chrome 扩展植入广告
查看>>
《嵌入式设备驱动开发精解》——2.4 建立一个具体的嵌入式开发的小项目
查看>>
《MATLAB 7.0从入门到精通(修订版)》——第1章 MATLAB概述 1.1 MATLAB 7.0简介
查看>>
《为自己工作——世界顶级设计师成功法则》—第1章1.6节专业
查看>>
Tor 项目正尝试用 Rust 重构系统
查看>>
中国开发者真的需要 Siri 吗?
查看>>
Chrome OS 将支持指纹识别,全民普及的节奏?
查看>>
如何判断服务器被DDOS了?
查看>>
《Python参考手册(第4版•修订版)》——1.10 迭代与循环
查看>>
《Android 源码设计模式解析与实战》——第1章,第1.4节让项目拥有变化的能力——依赖倒置原则...
查看>>
浅谈html引入css的几种方式
查看>>