主要包括以下特性的增加:错误边界、render方法新增返回类型、Fragment、createContext、createRef、forwardRef、生命周期函数的更新、lazy、suspense、Portals、memo、Strict Mode
错误边界 Error Boundary
错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。
- 如果一个 class 组件中定义了
static getDerivedStateFromError()
或componentDidCatch()
这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界组件。
//最佳实践:将ErrorBoundary抽象为一个公用的组件类
import React ,{Component} from 'react';
export default ErrorBoundary extends Component{
constructor(props){
super(props);
this.state={hasError:false};
}
componentDidCatch(err,info){
//如果调用了这个函数就说明子组件抛出错误了,改变状态,渲染备用的组件
this.setState({hasError:true});//修改状态,通知出错了
console.log(err,info);//打印错误信息
}
render(){
if(this.state.hasError){
return <div>throw error</div>
}
return this.props.children
}
}
使用错误边界的组件
import React ,{Component} from 'react';
export default class App extends Component{
constructor(){
this.state={user:'aa'}
}
changeState=()=>{
this.setState({
user:null
})
}
render(){
return <div>
<ErrorBoundary>
<CouldThrowErrComponent user={this.state.user} />
</ErrorBoundary>
<button onClick={this.changeState}>Update</button>
</div>
}
}
//可能出错的子组件
export default class CouldThrowErrComponent extends Component{
constructor(props){
super(props);
}
componentDidUpdate(){
return throw new Error('抛错')
}
render(){
let {user} =this.props;
return <div>{user}</div>
}
}
- 错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
- 错误边界仅可以捕获其子组件的错误
- 无法捕捉 1. 异步的抛错(事件,计时器等)、2。服务端渲染、3.自身抛错
- 一般使用static getDerivedStateFromError() 来渲染一个提示错误的UI,使用componentDidCatch() 来记录一些error的详细信息,错误调用栈等等
render方法新增返回类型
render方法支持直接返回string,number,boolean,null,portal,以及fragments(带有key属性的数组),这可以在一定程度上减少页面的DOM层级
//string
render(){
return 'hello,world'
}
//number
render(){
return 12345
}
//boolean
render(){
return isTrue?true:false
}
//null
render(){
return null
}
//fragments,未加key标识符,控制台会出现warning
render(){
return [
<div>hello</div>,
<span>world</span>,
<p>oh</p>
]
}
Fragment
Fragment 组件其作用是可以将一些子元素添加到 DOM tree 上且不需要为这些元素提供额外的父节点,相当于 render 返回数组元素。
render() {
return (
<Fragment>
Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
</Fragment>
);
}
如果使用数组需要加key,而且更麻烦
render() {
return (
[
"Some text.",
<h2 key="1">A heading</h2>,
"More text.",
<h2 key="2">Another heading</h2>,
"Even more text."
]
);
}
createContext / Provider / contextType / Consumer
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。顶层的Provider中传入value,在子孙级的Consumer中获取该值,并且能够传递函数,用来修改context
- React.createContext 是一个函数,它接收初始值并返回带有 Provider 和 Consumer 组件的对象;
- Provider 组件是数据的发布方,一般在组件树的上层并接收一个数据的初始值;
- Consumer 组件是数据的订阅方,它的 props.children 是一个函数,接收被发布的数据,并且返回 React Element;
以前跨组件传递数据都是通过逐层传递
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
// 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
// 因为必须将这个值层层传递所有组件。
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
使用context,避免中间元素传递props
const ThemeContext = React.createContext(
"dark" // 默认值
);
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
// 类组件一般使用添加属性的方式接受数据
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
函数式组件一般使用Consumer接收数据
//创建Context组件
const ThemeContext = React.createContext({
theme: 'dark',
toggle: () => {}, //向上下文设定一个回调方法
});
//运行APP
class App extends React.Component {
constructor(props) {
super(props);
this.toggle = () => { //设定toggle方法,会作为context参数传递
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
this.state = {
theme: themes.light,
toggle: this.toggle,
};
}
render() {
return (
<ThemeContext.Provider value={this.state}> //state包含了toggle方法
<Content />
</ThemeContext.Provider>
);
}
}
//中间组件
function Content() {
return (
<div>
<Button />
</div>
);
}
//接收组件
function Button() {
return (
<ThemeContext.Consumer>
{({theme, toggle}) => (
<button
onClick={toggle} //调用回调
style={{backgroundColor: theme}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
createRef / forwardRef
通过ref只能拿到标签元素或者通过class创建的react元素,不能拿到通过函数创建的react元素
适用于表单获取、元素动画等场景
class Child extends React.Component{
constructor(props){
super(props);
this.myRef=React.createRef();//创建ref对象
}
componentDidMount(){
console.log(this.myRef.current);//通过current拿到ref元素input标签
}
render(){
return <input ref={this.myRef}/>//挂载ref绑定的元素
}
}
React.forwardRef 是 Ref 的转发, 它能够让父组件访问到子组件的 Ref,从而操作子组件的 DOM。
//把FancyButton上的ref转发到button上
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
class App extends from Component{
constructor(props){
super(props);
// 你可以直接获取 DOM button 的 ref:
this.myRef = React.createRef();
}
componentDidMount(){
console.log(this.myRef.current)//拿到button
}
render(){
return <div>
<FancyButton ref={this.myRef}>Click me!</FancyButton>
</div>
}
}
回调 Refs
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// 使用原生 DOM API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// 组件挂载后,让文本框自动获得焦点
this.focusTextInput();
}
render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
生命周期函数的更新
- React 引入了 getDerivedStateFromProps 、 getSnapshotBeforeUpdate 及 componentDidCatch 等三个全新的生命周期函数。
- 将 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 标记为不安全的方法。
getDerivedStateFromProps(nextProps, prevState)
//不仅在 props 更新时会被调用,setState 时也会被触发
getSnapshotBeforeUpdate(prevProps, prevState)
//会在组件更新之前获取一个快照,
//并可以将计算得的值或从 DOM 得到的信息传递到 componentDidUpdate(prevProps, prevState, snapshot) 函数的第三个参数
//常常用于 scroll 位置定位等场景。
componentDidCatch()
//函数让开发者可以自主处理错误信息,诸如错误展示,上报错误等
lazy/suspense
React.lazy() 提供了动态 import 组件的能力,实现代码分割。
Suspense 作用是在等待组件时 suspend(暂停)渲染,并显示加载标识。
import React, { Component, lazy, Suspense } from 'react';
const LazyTest1 = lazy(() => import('./components/LazyTest.1'));
const LazyTest2 = lazy(() => import('./components/LazyTest.2'));
class App extends Component {
fallback = () =>{
return (
<div>Loading...</div>
);
}
render() {
return (
<div>
<Suspense fallback={this.fallback()}>
<h1>懒加载组件</h1>
<LazyTest1 />
<LazyTest2 />
</Suspense>
</div>
);
}
}
Suspense 可以放在懒加载的组件外层的任意位置,fallback 是懒加载组件载入过程中的一个过渡,可以放一些过渡效果或方法。
Portals
使用createPortal将组件渲染到当前组件树之外,我们可以将组件渲染到我们想要的任意DOM节点中,但该组件依然处在React的父组件之内。使用于弹窗、浮层类的组件
//实现一个简易蒙层效果,抽象出一个通用的Overlay组件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
export default class Overlay extends Component {
constructor(props) {
super(props);
this.container = document.createElement('div');
document.body.appendChild(this.container);
}
componentWillUnmount() {
document.body.removeChild(this.container);
}
render() {
return ReactDOM.createPortal(//将组件放到了创建的container上
<div className='overlay'>
<span className='overlay-close' onClick={this.props.onClose}>×</span>
{this.props.children}
</div>,
this.container
)
}
}
//该组件对应的样式如下
.overlay{
box-sizing:border-box;
position: fixed;
top:50%;
left:50%;
width:260px;
height:200px;
margin-left:-130px;
margin-top:-100px;
padding:10px;
background-color: #fff;
outline: rgba(0,0,0,.5) solid 9999px;
}
.overlay-close{
position: absolute;
top:10px;
right:10px;
color:red;
cursor: pointer;
}
使用方式
class App extends Component {
constructor(props) {
super(props);
this.state = {
overlayActive: false
}
this.closeOverlay = this.closeOverlay.bind(this);
this.showOverlay = this.showOverlay.bind(this);
}
closeOverlay() {
this.setState({ overlayActive: false })
}
showOverlay() {
this.setState({ overlayActive: true })
}
render() {
return (
<div className="App">
<div>hello world!</div>
{this.state.overlayActive &&
<Overlay onClose={this.closeOverlay}>overlay content</Overlay>}
<button onClick={this.showOverlay}>show</button>
</div>
);
}
}
memo / PureComponent(类似vue的keepAlive)
React.memo()
是一个高阶函数,它与 React.PureComponent
类似,但是一个函数组件而非一个类。
情景:
import React from 'react';
export default class extends React.Component {
constructor(props){
super(props);
this.state = {
date : new Date()
}
}
componentDidMount(){
setInterval(()=>{
this.setState({
date:new Date()
})
},1000)
}
render(){
return (
<div>
<Child seconds={1}/>
<div>{this.state.date.toString()}</div>
</div>
)
}
}
现在有一个显示时间的组件,每一秒都会重新渲染一次,对于Child组件我们肯定不希望也跟着渲染,所有需要用到PureComponent
class Child extends React.PureComponent {
render(){
console.log('I am rendering');
return (
<div>I am update every {this.props.seconds} seconds</div>
)
}
}
现在新出了一个React.memo()可以满足创建纯函数而不是一个类的需求
function Child({seconds}){
console.log('I am rendering');
return (
<div>I am update every {seconds} seconds</div>
)
};
export default React.memo(Child)
React.memo()可接受2个参数,第一个参数为纯函数的组件,第二个参数用于对比props控制是否刷新,与shouldComponentUpdate()
功能类似。
此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。
import React from "react";
function Child({seconds}){
console.log('I am rendering');
return (
<div>I am update every {seconds} seconds</div>
)
};
function areEqual(prevProps, nextProps) {
if(prevProps.seconds===nextProps.seconds){
return true//返回true就不刷新
}else {
return false//返回false就刷新
}
}
export default React.memo(Child,areEqual)
Strict Mode
启用严格模式下:
- 识别被标志位不安全的生命周期函数
- 对弃用的 API 进行警告
- 探测某些产生副作用的方法
- 检测是否使用 findDOMNode
- 检测是否采用了老的 Context API
class App extends React.Component {
render() {
return (
<div>
<React.StrictMode>
<ComponentA />
</React.StrictMode>
</div>
)
}
}
网友评论