盘点 React 16.0 ~ 16.5 主要更新及其应用
目录
0. 生命周期函数的更新
1. 全新的 Content API
2. React Strict Mode
3. Portal
4. Refs
5. Fragment
6. 其他
7. 总结
生命周期函数的更新
随着 React 16.0 发布, React 采用了新的内核架构 Fiber,在新的架构中它将更新分为两个阶段:Render Parse 和 Commit Parse, 也由此引入了 getDerivedStateFromProps
、 getSnapshotBeforeUpdate
及 componentDidCatch
等三个生命周期函数。同时,也将 componentWillUpdate
、componentWillReceiveProps
和 componentWillUpdate
标记为不安全的方法。
新生命周期函数图例
新增
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, 这里搬运一下:
- 如果改变
props
的同时,有副作用的产生(如异步请求数据,动画效果),这时应该使用componentDidUpdate
- 如果想要根据
props
计算属性,应该考虑将结果 memoization 化,参见 memoization - 如果想要根据
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 <div ref={this.listRef}>{/* ...contents... */}</div>
}
}
componentDidCatch(error, info)
在 16.0 以前,错误捕获使用 unstable_handleError
或者采用第三方库如 react-error-overlay 来捕获,前者捕获的信息十分有限,后者为非官方支持。而在 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);
}
···
此外,用户还可以采用第三方错误追踪服务,如 Sentry、Bugsnag 等,保证了错误处理效率的同时也极大降级了中小型项目错误追踪的成本。
Bugsnag 错误追踪截图
标记为不安全 componentWillMount
、componentWillReceiveProps
、componentWillUpdate
componentWillMount
componentWillMount
可被开发者用于获取首屏数据或事务订阅。
开发者为了快速得到数据,将首屏请求放在 componentWillMount
中。实际上在执行 componentWillMount
时第一次渲染已开始。把首屏请求放在componentWillMount
的与否都不能解决首屏渲染无异步数据的问题。而官方的建议是将首屏放在 constructor
或 componentDidMount
中。
此外事件订阅也被常在 componentWillMount
用到,并在 componentWillUnmount
中取消掉相应的事件订阅。但事实上 React 并不能够保证在 componentWillMount
被调用后,同一组件的 componentWillUnmount
也一定会被调用。另一方面,在未来 React 开启异步渲染模式后,在 · 被调用之后,组件的渲染也很有可能会被其他的事务所打断,导致 componentWillUnmount
不会被调用。而 componentDidMount
就不存在这个问题,在 componentDidMount
被调用后,componentWillUnmount
一定会随后被调用到,并根据具体代码清除掉组件中存在的事件订阅。
对此的升级方案是把 componentWillMount
改为 componentDidMount
即可。
componentWillReceiveProps
、componentWillUpdate
componentWillReceiveProps
被标记为不安全的原因见前文所述,其主要原因是操作 props 引起的 re-render。与之类似的 componentWillUpdate
被标记为不安全也是同样的原因。除此之外,对 DOM 的更新操作也可能导致重新渲染。
对于 componentWillReceiveProps
的升级方案是使用 getDerivedStateFromProps
和 componentDidUpdate
来代替。 对于 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 (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
)
}
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 (
<ThemeContext.Consumer>{theme => <Button {...props} theme={theme} />}</ThemeContext.Consumer>
)
}
其过程大概如下:
- 通过
React.createContext
创建 Context 对象 - 在父组件上,使用
<ThemeContext.Provider/>
来提供 Provider - 在需要消费的地方,使用
<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 (
<GlobalStore.Provider value={{ ...initialState }}>{this.props.children}</GlobalStore.Provider>
)
}
}
class App extends React.Component {
render() {
return (
<GlobalStoreProvider>
<GlobalStore.Consumer>
{context => (
<div>
<div>{context.theme}</div>
<div>{context.color}</div>
</div>
)}
</GlobalStore.Consumer>
</GlobalStoreProvider>
)
}
}
全新的 Context API 带来的穿透组件的能力对于需要全局状态共享的场景十分有用,无需进入额外的依赖就能对状态进行管理,代码简洁明了。
React Strict Mode
React StrictMode 可以在开发阶段发现应用存在的潜在问题,提醒开发者解决相关问题,提供应用的健壮性。其主要能检测到 4 个问题:
- 识别被标志位不安全的生命周期函数
- 对弃用的 API 进行警告
- 探测某些产生副作用的方法
- 检测是否采用了老的 Context API
使用起来也很简单,只要在需要被检测的组件上包裹一层 React StrictMode
,示例代码 React-StrictMode:
class App extends React.Component {
render() {
return (
<div>
<React.StrictMode>
<ComponentA />
</React.StrictMode>
</div>
)
}
}
若出现错误,则在控制台输出具体错误信息:
React Strict Mode 控制台输出
Portal
由 ReactDOM 提供的 createPortal
方法,允许将组件渲染到其他 DOM 节点上。这对大型应用或者独立于应用本身的渲染很有帮助。其函数签名为 ReactDOM.createPortal(child, container)
, child
参数为任意的可渲染的 React Component,如 element
、sting
、fragment
等,container
则为要挂载的 DOM 节点.
以一个简单的 Modal 为例, 代码见 Portal 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(
<div>
{this.props.children}
<span onClick={this.handleClose}>[x]</span>
</div>,
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 <div ref="myRef" />
}
···
···
// React 16+
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return <div ref={this.myRef} />
}
···
React.forwardRef
另外一个新特性是 Ref 的转发, 它的目的是让父组件可以访问到子组件的 Ref,从而操作子组件的 DOM。 React.forwardRef
接收一个函数,函数参数有 props
和 ref
。看一个简单的例子,代码见 Refs:
const TextInput = React.forwardRef((props, ref) => (
<input type="text" placeholder="Hello forwardRef" ref={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 (
<form onSubmit={this.handleSubmit}>
<TextInput ref={inputRef} />
<button type="submit">Submit</button>
</form>
)
}
}
这个例子使用了 React.forwardRef
将 props
和 ref
传递给子组件,直接就可以在父组件直接调用。
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 (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
)
}
如此,我们不需要单独包裹一层无用的元素(如使用<div></div>
包裹),减少层级嵌套。 此外,还一种精简的写法:
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
)
}
其他
ReactDOM
的 render
函数可以数组形式返回 React Component
render(){
return [
<ComponentA key='A' />,
<ComponentB key='B' />,
]
}
移除内建的react-with-addons.js
, 所有的插件都独立出来
之前常用的react-addons-(css-)transition-group
,react-addons-create-fragment
,react-addons-pure-render-mixin
、react-addons-perf
等,除部分被内置,其余全部都独立为一个项目,使用时要注意。
盘点 React 16.0 ~ 16.5 主要更新及其应用的更多相关文章
- React 特性剪辑(版本 16.0 ~ 16.9)
Before you're going to hate it, then you're going to love it. Concurrent Render(贯穿 16) 在 18年的 JSConf ...
- 安装MYSQL详细教程 版本:mysql-installer-community-5.7.16.0 免安装版本和安装版本出现错误的解决
一.版本的选择 之前安装的Mysql,现在才来总结,好像有点晚,后台换系统了,现在从新装上Mysql,感觉好多坑,我是来踩坑,大家看到坑就别跳了,这样可以省点安装时间,这个折腾了两天,安装了好多个版本 ...
- 增加VirtualBox虚拟机的磁盘空间大小(Host:Win7 VirtualBox5.0.16 VM:Win10)
1 前言 网上关于增加VirtualBox虚拟机的磁盘空间大小的文章非常非常多,这里我之所以再写一篇,是因为在参照这些文章做的时候,由于VirtualBox的版本更新以及其他一些环境问题,碰到到一些问 ...
- Navicat Premium 简体中文版 12.0.16 以上版本国外官网下载地址(非国内)
国内Navicat网址是:http://www.navicat.com.cn 国外Navicat网址是:http://www.navicat.com 国外的更新比国内的快,而且同一个版本,国内和国外下 ...
- CentOS 6.5 搭建 .NET 环境, Mono 5.16.0 + Jexus 5.8
最近有这样一个打算,就是准备把以前的有一个.NET 网站部署在Linux 下面,正好试试 .NET 跨平台的功能,为后续研究 .netCore 方向准备. 搭建环境: CentOS 6.5 + Mon ...
- PowerDesign 16.0 生成的SQL Server2000 数据库脚本时MS_Description不存在的问题解决
根据网上查询到的资料,找到了解决方法,原文出自:http://www.cnblogs.com/24tt/p/5047257.html PowerDesign 16.0 生成的Script语句,Sql2 ...
- ChemOffice Professional 16.0新增了哪些功能
ChemOffice Professional 16.0是为终极化学和生物组件设计,可满足化学家和生物学家的需求.ChemOffice Professional帮助科学家有效地检索数据库,包括SciF ...
- mysql8.0.16操作记录
mysql8.0.16操作记录 2.1.登录 -uroot -p'AnvcTMagdLarwNV3CKaC' mysql: [Warning] Using a password on the comm ...
- 【SQL必知必会笔记(1)】数据库基础、SQL、MySQL8.0.16下数据库、表的创建及数据插入
文章目录 1.数据库基础 1.1 数据库(database) 1.2 表(table) 1.3 列和数据类型 1.4 行 1.5 主键 2.什么是SQL 3.创建后续练习所需数据库.表(MySQL8. ...
随机推荐
- opencv+python 自动绿帽机
具体内容,直接看注释吧,该注释的我都注释掉了. # coding:utf-8 import cv2 # 待检测的图片路径 imagepath = r'D://greenhat//2.jpg' # 获取 ...
- CodeForces 456-C Boredom
题目链接:CodeForces -456C Description Alex doesn't like boredom. That's why whenever he gets bored, he c ...
- angular $digest already in progress
angular.js:11706 Error: [$rootScope:inprog] $digest already in progresshttp://errors.angularjs.org/1 ...
- 使用Qemu运行Ubuntu文件系统(1)
参考 https://blog.csdn.net/mountzf/article/details/51707853 https://blog.csdn.net/stephen_lu_fahai/art ...
- 阿里云ACE深圳同城会 开始报名
大家好,阿里云 ACE深圳同城会本周末第一活动,主要大家一起聚聚,互相认识和熟悉,未来一起玩儿一起进步~ 通知大家一个好消息,当前凡是加入深圳ACE同城会组织(群)的朋友,通过跟群主报名申请,将有机会 ...
- Centos如何安装163yum源
如果Centos使用系统自带的yum源,在用yum进行安装的时候,速度会受到限制,所以我们需要替换为国内的yum源,一般会选择163源,速度比较快包也比较全,使用yum进行安装的时候可以大大节省时间 ...
- grid - 网格轨道对齐方式
网格轨道对齐可以相对于网格容器行和列轴. align-content指定网格轨道沿着行轴对齐方式:justify-content指定网格轨道沿着列轴对齐方式.它们支持下面属性: normal star ...
- Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test java.lang.IllegalStateException
不能找到对应的带有@SpringBootConfiguration 的类,你需要将它放在包的最顶层.
- jetty9.4缓存文件目录自定义
jetty9.4安装包解压之后,有几个修改的地方和jetty7.6有不同,需要单独注意一下: 1. 端口设置 端口设置在${jetty_home}/start.ini中 2. 缓存文件生成目录 项目通 ...
- 阿里云 CentOS7 安装 Nginx 后,无法访问的问题
在阿里云实例中,选择 网络与安全中的安全组.修改安全组规则. 例如: 但是还是不行 需要这样排查 netstat -anp | grep 80 iptables -L -n firewall- ...