首先,把我知道从16.0开始到现在为止新特性全部列举出来:
v16.0
render 支持返回数组和字符串
createPortal
支持自定义 DOM 属性
Error Boundary
Fiber
SSR优化
减少文件体积
v16.1
react-call-return
v16.2
Fragment
v16.3
生命周期函数的更新
createContext
createRef
forwardRef
v16.4
新增Pointer Events
fix getDerivedStateFromProps bug
v16.5
React Profiler 调试
v16.6
memo
lazy
suspense
简化 contextType
增加 getDerivedStateFromError
v16.8
React hooks
React16新增加了render的返回格式,你可以return返回number,boolean,null,portal(Portals提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。),以及新支持的string和fragments(带有key属性的数组),不需要外层包含div标签,但必须有key。
render() {
// 不再需要在外边包裹一个额外的元素!
return [
// 不要忘记加key哦 :)
First item,
Second item,
Third item,
];
}
Portals机制提供了一种最直接的方式可以把一个子组件渲染到父组件渲染的DOM树之外。默认情况下,React组件树和DOM树是完全对应的,因此对于一些Modal,Overlay之类的组件,通常是将它们放在顶层,但逻辑上它们可能只是属于某个子组件,不利于组件的代码组织。通过使用createPortal,我们可以将组件渲染到我们想要的任意DOM节点中,但该组件依然处在React的父组件之内。带来的一个特性就是,在子组件产生的event依然可以被React父组件捕获,但在DOM结构中,它却不是你的父组件。对于组件组织,代码切割来说,这是一个很好的属性。
不使用 ReactDOM.createPortal 使用 ReactDOM.createPortal 子组件 父组件以前的 React 版本 DOM 不识别除了 HTML 和 SVG 支持的以外属性,在 React16 版本中将会把全部的属性传递给 DOM 元素。这个新特性可以让我们摆脱可用的 React DOM 属性白名单。笔者之前写过一个方法,用于过滤非 DOM 属性 filter-react-dom-props,16 之后即可不再需要这样的方法。
React V16之前如果页面出现错误,整个页面会崩溃掉,本次版本您提供一个稳定的api,componentDidCatch,就像 try catch一样,用于捕获渲染过程的错误信息,避免整个页面崩溃掉。在組件中加入 componentDidCatch 方法就成为 Error Boundary 組件,當「子組件」發生錯誤時就會被這個 Error Boundary 捕捉,不会让错误信息影响到其他组件。但是并不是所有错误都可以被捕捉上:
只能捕捉「子」组件错误,边界本身无法被捕捉。
只能捕捉子组件「渲染」「各生命周期方法」中的错误。
牵涉到非同步执行 (ex. setTimeout, callback ) 中的错误无法被捕捉。
无法捕捉 event handler 中发生的错误。
专门写一篇文章研究。请持续关注 React 中文社区,近期推送
生成更简洁的HTML
宽松的客户端一致性校验
无需提前编译
react 16服务端渲染速度更快
支持流式渲染
先看下面的HTML,react 15与react 16的服务端分别会生成什么。
renderToString(
This is some server-generated HTML.
);
有data-reactid, text noded ,react-text各种属性。
This is some
server-generated
HTML.
This is some server-generated
HTML.
可以看到,react 16去掉了很多属性,它的好处很明显:增加易读性,同时很大程度上减少html的文件大小。
react 15:会将SSR的结果与客户端生成的做一个个字节的对比校验 ,一点不匹配发出waring同时就替换整个SSR生成的树。
react 16:对比校验会更宽松一些,比如,react 16允许属性顺序不一致,而且遇到不匹配的标签,还会做子树的修改,不是整个替换。
注意点:react16不会自动fix SSR 属性跟client html属性的不同,但是仍然会报waring,所以我们需要自己手动去修改。
react 15:如果你直接使用SSR,会有很多需要检查procee.env的地方,但是读取在node中读取process.env是很消耗时间的。所以在react 15的时候,需要提前编译,这样就可以移除 process.env的引用。
react 16:只有一次检查process.env的地方,所以就不需要提前编译了,可以开箱即用。
为什么呢?因为react 15下,server client都需要生成vDOM,但是其实在服务端, 当我们使用renderToString的时候,生成的vDom就会被立即抛弃掉, 所以在server端生成vDom是没有意义的。
使用流式渲染会提升首个字节到(TTFB)的速度。但是什么是流式渲染呢?
可以理解为内容以一种流的形式传给前端。所以在下一部分的内容被生成之前,开头的内容就已经被发到浏览器端了。这样浏览器就可以更早的编译渲染文件内容。
React 库大小从 20.7kb(压缩后 6.9kb)降低到 5.3kb(压缩后 2.2kb)
ReactDOM 库大小从 141kb(压缩后 42.9kb)降低到 103.7kb(压缩后 32.6kb)
React + ReactDOM 库大小从 161.7kb(压缩后 49.8kb)降低到 109kb(压缩后 43.8kb)
现在只是实验性质的api,不建议使用。
使用 React.Fragment 组件,React.Fragment 组件可以在 React 对象上使用。这可能是必要的,如果你的工具还不支持 JSX 片段。注意在 React 中, <>> 是
class Columns extends React.Component {
render() {
return (
Hello
World
);
}
}
function Glossary(props) {
return (
{props.items.map(item => (
// 没有`key`,将会触发一个key警告
- {item.term}
- {item.description}
))}
);
}
context 就是可以使用全局的变量,不需要一层层props传递,原本组件传递需要一层一层props传递,传递太过繁琐,例如以下代码:
import React from 'react'
function NeedData(props) {
return {props.value2};
}
// 中间组件
function Middle(props) {
return (
);
}
export default class Communication8 extends React.Component {
render() {
return ;
}
}
createContext 解决无需层级关系传递,主要有以下概念
createContext 创建一条索引,相当于开辟一个空间 ,可以配置默认参数,支持任何类型
Provider提供的数据(方法)共享 ,传参使用value,且value固定写法默认,必填,写其他不生效。value接收的变量建议state中:每一次 Provider 重渲染时,consumers 组件将会重新渲染,因为value属性总被赋予新值
Consumer接收者,必须以函数方式获取 第一种 Provider传递数据 Consumer接受数据
import React, { Component, createContext } from 'react';
const firstContext = createContext(); //firstContext自定义名称
//子组件
class Two extends Component {
static contextType = secondContext
render() {
console.log(this.context)
return (
{value => {value}}
)
}
}
//中间组件
class Middle extends Component {
render() {
return (
)
}
}
//父组件
class Communication9 extends Component {
onfunction() {
return `数据`
}
render() {
return (
)
}
}
export default Communication9
第二种 Provider传递方法 Consumer接受方法
import React, { Component, createContext } from 'react';
const secondContext = createContext(); //secondContext自定义名称
//子组件
class Two extends Component {
static contextType = secondContext
render() {
return (
{onfunction => {onfunction()}}
)
}
}
//中间组件
class Middle extends Component {
render() {
return (
)
}
}
//父组件
class Communication9 extends Component {
onfunction() {
return `数据`
}
render() {
return (
)
}
}
export default Communication9
createContext 创建多个
import React, { Component, createContext } from 'react';
const firstContext = createContext(); //firstContext自定义名称
const secondContext = createContext(); //secondContext自定义名称
//子组件
class Two extends Component {
static contextType = secondContext
render() {
console.log(this.context)
return (
{value => {value}}
{onfunction => {onfunction()}}
)
}
}
//中间组件
class Middle extends Component {
render() {
return (
)
}
}
//父组件
class Communication9 extends Component {
onfunction() {
return `数据`
}
render() {
return (
)
}
}
export default Communication9
contextType 代替 Consumer 接收值 static
import React, { Component, createContext } from 'react';
const secondContext = createContext(); //secondContext自定义名称
//子组件
class Two extends Component {
static contextType = secondContext
render() {
console.log(this.context)
return (
{this.context()}
)
}
}
//中间组件
class Middle extends Component {
render() {
return (
)
}
}
//父组件
class Communication9 extends Component {
onfunction() {
return `数据`
}
render() {
return (
)
}
}
export default Communication9
import React, { Component, createContext } from 'react';
const test = {
value: {
val: '12345'
},
value2: {
val: '6789'
}
};
//设置全局createContext
const overallContext = React.createContext(
test.value
);
class Communication9 extends Component {
onfunction() {
return `数据`
}
render() {
return (
{this.context.val}
)
}
}
Communication9.contextType = overallContext;
export default Communication9
Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素。
16.3之前,ref 通过字符串(string ref)或者回调函数(callback ref)的形式进行获取
使用 React.createRef() 创建 refs,通过 ref 属性来获得 React 元素。当构造组件时,refs 通常被赋值给实例的一个属性,这样你可以在组件中任意一处使用它们.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return ;
}
}
那为什么要替换 string ref ,下面我们来列举string ref的诸多罪状:
当 ref 定义为 string 时,需要 React 追踪当前正在渲染的组件,在 reconciliation 阶段,React Element 创建和更新的过程中,ref 会被封装为一个闭包函数,等待 commit 阶段被执行,这会对 React 的性能产生一些影响。
function coerceRef(
returnFiber: Fiber,
current: Fiber | null,
element: ReactElement,
) {
...
const stringRef = '' + element.ref;
// 从 fiber 中得到实例
let inst = ownerFiber.stateNode;
// ref 闭包函数
const ref = function(value) {
const refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
if (value === null) {
delete refs[stringRef];
} else {
refs[stringRef] = value;
}
};
ref._stringRef = stringRef;
return ref;
...
}
当使用 render callback 模式时,使用 string ref 会造成 ref 挂载位置产生歧义。
class MyComponent extends Component {
renderRow = (index) => {
// string ref 会挂载在 DataTable this 上
return ;
// callback ref 会挂载在 MyComponent this 上
return this['input-' + index] = input} />;
}
render() {
return
}
}
string ref 无法被组合,例如一个第三方库的父组件已经给子组件传递了 ref,那么我们就无法再在子组件上添加 ref 了,而 callback ref 可完美解决此问题。
/** string ref **/
class Parent extends React.Component {
componentDidMount() {
// 可获取到 this.refs.childRef
console.log(this.refs);
}
render() {
const { children } = this.props;
return React.cloneElement(children, {
ref: 'childRef',
});
}
}
class App extends React.Component {
componentDidMount() {
// this.refs.child 无法获取到
console.log(this.refs);
}
render() {
return (
);
}
}
/** callback ref **/
class Parent extends React.Component {
componentDidMount() {
// 可以获取到 child ref
console.log(this.childRef);
}
render() {
const { children } = this.props;
return React.cloneElement(children, {
ref: (child) => {
this.childRef = child;
children.ref && children.ref(child);
}
});
}
}
class App extends React.Component {
componentDidMount() {
// 可以获取到 child ref
console.log(this.child);
}
render() {
return (
{
this.child = child;
}} />
);
}
}
在根组件上使用无法生效。
ReactDOM.render( , document.getElementById('main'));
对于静态类型较不友好,当使用 string ref 时,必须显式声明 refs 的类型,无法完成自动推导。
编译器无法将 string ref 与其 refs 上对应的属性进行混淆,而使用 callback ref,可被混淆。
/** string ref,无法混淆 */
this.refs.myRef
/** callback ref, 可以混淆 */
this.myRef
{ this.myRef = dom; }}>
this.r
{ this.r = e; }}>
Ref forwarding是组件一个可选的特征。一个组件一旦有了这个特征,它就能接受上层组件传递下来的ref,然后顺势将它传递给自己的子组件。
为什么会有这个api呢?这个api主要作用就是通过父元素获取子元素的 Ref,可能是管理元素的聚焦,文本选择或者动画相关的操作。在HOC上,ref是无法通过props进行传递的,我们只可以采用折中的方案,但是这种这种方案,相当于从HOC上获取了这个接口,这从设计上是不优雅的,应该是透过HOC来获取到子组件。我们来看看在16.3之前我们实现方式,我们通过实现一个类中的方法,方法返回的就是这个Child实例,即input实例。
import React from 'react'
function HOCFc(WrappedComponent) {
class paintRedComponent extends React.Component {
constructor(props) {
super(props);
this.setWrappedInstance = this.setWrappedInstance.bind(this);
}
getWrappedInstance() { // 返回的即是WrappedComponent即input
return this.wrappedInstance;
}
setWrappedInstance(ref) {
this.wrappedInstance = ref;
}
render() {
return ;
}
}
return paintRedComponent;
}
class Child extends React.Component {
inputRef = React.createRef()
render() {
return (
)
}
}
const Parent = HOCFc(Child)
export default class ForwardRef extends React.Component {
isFocus = false
ref = React.createRef()
inputNode = null
componentDidMount() {
console.log(this.ref, 'ref')
this.inputNode = this.ref.inputRef.current
}
toggleFocus = () => {
this.isFocus = !this.isFocus
if (this.isFocus) {
this.inputNode.focus()
} else {
this.inputNode.blur()
}
}
render() {
return (
{this.ref = dom.getWrappedInstance()}}/>
)
}
}
然后我们将这部分代码,改为forwardRef形式:
import React from 'react'
const HOCFc = Component => React.forwardRef(
(props, ref) => (
)
)
class Child extends React.Component {
inputRef = React.createRef()
render() {
return (
)
}
}
const Parent = HOCFc(Child)
export default class ForwardRef extends React.Component {
isFocus = false
ref = React.createRef()
inputNode = null
componentDidMount() {
this.inputNode = this.ref.current.inputRef.current
}
toggleFocus = () => {
this.isFocus = !this.isFocus
if (this.isFocus) {
this.inputNode.focus()
} else {
this.inputNode.blur()
}
}
render() {
return (
)
}
注意,上面提到的第二个参数ref只有在你通过调用React.forwardRef()来定义组件的情况下才会存在。普通的function component和 class component是不会收到这个ref参数的。同时,ref也不是props的一个属性。
Pointer Events API 是Hmtl5的事件规范之一,它主要目的是用来将鼠标(Mouse)、触摸(touch)和触控笔(pen)三种事件整合为统一的API.
在web页面复用在多种设备下的情况下, Pointer Events整合了mouse, touch, pen的触摸事件, 使其能在多种设备上触发,我们无需对各种类型的事件区分对待,更高的提高了开发效率。
新增:
onPointerDown
onPointerMove
onPointerEnter
onPointerLeave
onPointerOver
onPointerOut
详细可以参考developer.mozilla.org/e
React这次更新修复了getDerivedStateFromProps这个生命周期的触发节点, 在之前, 它触发的方式和旧生命周期getDerivedStateFromProps类似, 都是在被父组件re-render的时候才会触发,并且本组件的setState的调用也不会触发 这种方式在之前同步渲染的时候是没有问题的, 但是为了支持新的还未启用的fiber异步渲染机制, 现在, getDerivedStateFromProps在组件每一次render的时候都会触发,也就是说无论是来自父组件的re-render, 还是组件自身的setState, 都会触发getDerivedStateFromProps这个生命周期。v 16.3:
v 16.3React 16.5 添加了对新的 profiler DevTools 插件的支持。这个插件使用 React 的 Profiler 实验性 API 去收集所有 component 的渲染时间,目的是为了找出你的 React App 的性能瓶颈。它将会和我们即将发布的 时间片 特性完全兼容。
专门写文章介绍,请持续关注 React 中文社区,近期推送
memo的缩写是memoization即记忆化,刚开始不了解,专门看了下维基百科上的解释:
在react中React.memo是一个更高阶的组件。它类似于React.PureComponent功能组件而不是类。如果你的函数组件在给定相同的道具的情况下呈现相同的结果,你可以React.memo通过记忆结果将它包装在一些调用中以提高性能。这意味着React将跳过渲染组件,并重用最后渲染的结果。默认情况下,它只会浅显比较props对象中的复杂对象。如果要控制比较,还可以提供自定义比较函数作为第二个参数。此方法仅作为性能优化存在。不要依赖它来“防止”渲染,因为这会导致错误。
组件仅在它的 props 发生改变的时候进行重新渲染。通常来说,在组件树中 React 组件,只要有变化就会走一遍渲染流程。但是通过 PureComponent 和 React.memo(),我们可以仅仅让某些组件进行渲染。由于只有需要被渲染的组件被渲染了,所以这是一个性能提升。PureComponent 要依靠 class 才能使用。而 React.memo() 可以和 functional component 一起使用。
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);
实际意义是:函数式组件也有“shouldComponentUpdate”生命周期了注意,compare默认是shallowEqual,所以React.memo第二个参数compare实际含义是shouldNotComponentUpdate,而不是我们所熟知的相反的那个。
lazy与suspense可以让我们在不依赖第三方库的情况下简化对延迟加载(lazy loading)的处理,这项新功能使得可以不借助任何附加库就能通过代码分割(code splitting)延迟加载 react 组件。延迟加载是一种优先渲染必须或重要的用户界面项目,而将不重要的项目悄然载入的技术。这项技术已经被完全整合进了 react 自身的核心库中。之前我们要用 react-loadable 达到这一目的,但现在用 react.lazy() 就行了。Suspense是一个延迟函数所必须的组件,通常用来包裹住延迟加载组件。多个延迟加载的组件可被包在一个 suspense 组件中。它也提供了一个 fallback 属性,用来在组件的延迟加载过程中显式某些 react 元素。
import React, { lazy, Suspense } from ‘react’;
import ReactDOM from ‘react-dom’;
import ‘./index.css’;
// import Artists from ‘./Artists’;
const Artists = lazy(() => import(‘./Artists’))
class App extends React.Component {
render(){
return(
Still Loading…}>
);
}
}
ReactDOM.render( , document.getElementById(‘root’));
不足:暂不支持ssr
简化获取context的方式,之前需要用一个在外层包裹一个,如下:
// Theme context, default to light theme
const ThemeContext = React.createContext('light');
// Signed-in user context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// App component that provides initial context values
return (
);
}
}
function Layout() {
return (
);
}
// A component may consume multiple contexts
// 同时如果是function component 用Consumer
function Content() {
return (
{theme => (
{user => (
)}
)}
);
}
现在可以直接通过this.context获取。
class MyClass extends React.Component {
static contextType = MyContext;
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;
v16.3这个版本里,React 除了Error Boundaries来捕获错误,里面主要是使用了componentDidCatch来捕获 错误。但是它是在错误已经发生之后并且render函数被调用之后,才会被调用。也就是说如果一个组件出现的错误,在调用 componentDidCatch之前只能返回null给用户。而 getDerivedStateFromError 可以在render函数之嵌捕获到错误,所以它更适合写用来显示fallback UI的逻辑。
注意事项:componentDidCatch,getDerivedStateFromError都无法捕获服务端的错误,但是React团队正在努力支持SSR。
改进前的ErrorBoundary:
class ErrorBoundary extends React.Component {
state = { hasError: false };
componentDidCatch(error, info) {
this.setState({ hasError: false })
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return Something went wrong.
;
}
return this.props.children;
}
改进后的ErrorBoundary(推荐写法):
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
// 更新state所以下次render可以立刻显示fallback UI
return { hasError: true };
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return Something went wrong.
;
}
return this.props.children;
}
[面试必问]-你不知道的 React Hooks 那些糟心事
[面试必问]-全网最简单的React Hooks源码解析
最后, 送人玫瑰,手留余香,觉得有收获的朋友可以点赞,关注一波 ,相关 文档资料 已上传至 React 中文社区交流群, 请关注公众号,点击菜单栏 入群学习,添加 入群小助手 后入群领取。陆续更新前端超硬核文章。
推荐阅读
(点击标题可跳转阅读)
[极客前沿]-你不知道的 React 18 新特性
[极客前沿]-写给前端的 K8s 上手指南
[极客前沿]-写给前端的Docker上手指南
[面试必问]-你不知道的 React Hooks 那些糟心事
[面试必问]-一文彻底搞懂 React 调度机制原理
[面试必问]-一文彻底搞懂 React 合成事件原理
[面试必问]-全网最简单的React Hooks源码解析
[面试必问]-一文掌握 Webpack 编译流程
[面试必问]-一文深度剖析 Axios 源码
[面试必问]-一文掌握JavaScript函数式编程重点
[面试必问]-阿里,网易,滴滴,头条等20家面试真题
[架构分享]-石墨文档 Websocket 百万长连接技术实践
[自我提升]-Javascript条件逻辑设计重构
[自我提升]-你不知道的Git神操作
[自我提升]-代码整洁之道(上)
[自我提升]-从表单抽象到表单中台
[自我提升]-页面可视化工具的前世今生
[大前端之路]-连前端都看得懂的《Nginx 入门指南》
[基础总结]-学习Less,看这篇就够了
[软实力提升]-金三银四,如何写一份面试官心中的简历
觉得本文对你有帮助?请分享给更多人
关注「React中文社区」加星标,每天进步