就像人们对更新移动应用程序和操作系统感到兴奋一样,开发人员也应该对更新框架感到兴奋。不同框架的新版本具有新特性和开箱即用的技巧。
下面是将现有应用程序从 React 15 迁移到 React 16 时应该考虑的一些好特性。
错误处理
React 16 引入了错误边界的新概念。
现在在 React 16 中, 大家就能使用错误边界功能,而不用一发生错误就解除整个程序挂载了。把错误边界看成是一种类似于编程中 try-catch 语句的机制,只不过是由 React 组件来实现的。
错误边界是一种 React 组件。它及其子组件形成一个树型结构,能捕获 JavaScript 中所有位置的错误,记录下错误,并且还能显示一个后备界面,避免让用户直接看到组件树的崩溃信息。
这里涉及到一种新的生命周期函数叫componentDidCatch(error, info)
。无论什么样的类组件,只要定义了这个函数,就成为了一个错误边界。
class ErrorBoundary extends React.Component {constructor(props) {super(props);
this.state = {hasError: false};
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({hasError: true});
// 你还可以将错误记录到错误报告服务中
logErrorToMyService(error, info);
}
render() {if (this.state.hasError) {
// 可以渲染任何自定义回退界面
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
也可以将其用作常规组件使用:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
componentDidCatch()
方法的工作原理类似于 JavaScript catch{}
块,但它适用于组件。只有类组件可以是错误边界。实际上,在大多数情况下,你都希望声明一次错误边界组件,然后在整个应用程序中使用它。
请注意 ,错误边界只会捕获位于它们之下的组件中的错误。错误边界无法捕获到自身的错误。如果错误边界渲染错误消息失败,错误将被传播到上方最接近的错误边界。这也类似于 JavaScript 中的 catch{} 块。
有了错误边界,即使某个组件的结果有错误,整个 React 程序挂载也不会被解除。只有出错的那个组件会显示一个后备界面,而整个程序仍然完全正常运行。
点击查看在线事例
关于错误边界更多的内容可查看官网。
新的 render 返回类型:片段和字符串
现在,在渲染时可以摆脱将组件包装在 div
中。
你现在可以从组件的 render 方法返回元素数组。与其他数组一样,你需要为每个元素添加一个键以避免发出键警告:
render() {
// No need to wrap list items in an extra element!
return [// Don't forget the keys :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
从 React 16.2.0 开始,它支持 JSX 的一个特殊片段语法,该语法不需要键。
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
支持返回字符串:
render() {return 'Look ma, no spans!';}
Portal
Portal 提供了一种将子节点渲染到父节点之外的 dom 节点。
ReactDOM.createPortal(child, container)
第一个参数 (child
)是任何可渲染的 React 子元素,例如元素,字符串或片段。第二个参数 (container)
是 DOM 元素。
如何使用它
在 React15.X 版本中,我们只能讲子节点在父节点中渲染,基本用法如下:
render() {
// React 需要创建一个新的 div 来包含子节点
return (
<div>
{this.props.children}
</div>
);
}
但是如果需要将子节点插入到父节点之外的 dom 呢,React15.x 及之前都没有提供这个功能的 API。
可以使用 React16.0 中的 portal:
render() {
// React 不需要创建一个新的 div 去包含子元素,直接将子元素渲染到另一个
//dom 节点中
// 这个 dom 节点可以是任何有效的 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 output:
<div />
在 React16 中,输出将如下所示(会显示自定义属性,并且完全不会被忽略)
// React 16 output:
<div mycustomattribute="something" />
在 state 中设置 null 避免重新渲染
有时候我们需要通过函数来判断组件状态更新是否触发重新渲染,在 React 16 中,我们可以通过调用 setState
时传入 null
来避免组件重新渲染,这也就意味着,我们可以在 setState 方法内部决定我们的状态是否需要更新,
const MAX_PIZZAS = 20;
function addAnotherPizza(state, props) {
// Stop updates and re-renders if I've had enough pizzas.
if (state.pizza === MAX_PIZZAS) {return null;}
// If not, keep the pizzas coming! :D
return {pizza: state.pizza + 1,}
}
this.setState(addAnotherPizza);
更多相关信息请阅读这里
创建 ref
现在使用 React16 创建 refs
要容易得多。为什么需要使用refs
:
- 管理焦点、文本选择或媒体播放。
- 触发动画。
- 与第三方 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}></div>
}
}
访问 ref
上述是创建 Ref
指向的方法,在 Ref 所指向的组件,在 render
后就可以调用,React16.3 中提供了 current
属性,用于引用render
后的节点:
componentDidMount(){console.log(this.myRef.current);
//render 之后就可以输出该 ref 指向的那个节点
}
此外,同样的 Ref 所指向的节点可以是 dom 节点,也可以是类组件。
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
<Provider value={/* some value */}>
一个允许 Consumer 订阅上下文变更的 React 组件。
一个 Provider 可以连接多个 Consumer,可以在树中嵌套 Provider,实现更深的值覆盖。
Consumer
<Consumer>
{value => /* render something based on the context value */}
</Consumer>
订阅上下文更改的 React 组件。
需要一个函数作为子组件。这个函数接收当前上下文值,并返回一个 React 节点。传给函数的 value 参数将等于树中最近的 Provider
的 value
。如果没有匹配的 Provider,则 value
参数将等于传给 createContext()
的 defaultValue。
static getDerivedStateFromProps()
在很长一段时间内,componentWillReceiveProps
是在没有附加渲染的情况下更新状态的唯一方法。
在版本 16.3 中,我们引入了一个全新的生命周期函数 getDerivedStateFromProps
用来替换componentWillReceiveProps
,并用更安全的方式处理相同的场景。
与此同时,我们意识到人们对如何使用这两种方法有很多误解,我们发现了一些反模式,这些错误导致了微妙而令人困惑的 bug。
在 16.4 中,有关 getDerivedStateFromProps
的修复使得派生状态更加可预测,因此错误使用的结果更容易被注意到。
getDerivedStateFromProps
会在调用 render
方法之前被调用,它应该返回一个用于更新状态的对象,或者如果不更新任何状态就返回 null
。
这个方法适用于一些罕见的用例,其中 state
依赖 prop
的变化。例如,可以很方便地实现一个<Transition>
组件,它会比较上一个和下一个子组件,然后决定它们中的哪个需要进行动画渲染。
衍生 state 会导致冗长的代码,并让你的组件难以开发和维护。
你可以考虑更简单的替代方案:
- 如果你需要在 prop 发生变更时做一些其他事情(例如数据提取或动画),请改用
componentDidUpdate
生命周期。 - 如果你只想在 prop 发生变更时重新计算某些数据,请改用 memoization helper:
* 如果你想在 prop 发生变更时“重置”某个状态,请考虑创建受控组件或带有键的非受控组件。
- 此方法无权访问组件实例。如果你愿意,可以通过提取组件
props
的纯函数和类定义之外的状态,在getDerivedStateFromProps()
和其他类方法之间重用一些代码。
注意,不管怎样,这个方法都会在每次进行渲染时触发。这与 UNSAFE_componentWillReceiveProps
完全相反。它只在父组件进行重新渲染时触发,而且不作为本地 setState
的结果。
将 nextProps.someValue
与 this.props.someValue 进行比较。如果两者都不同,那么我们执行一些操作:
static getDerivedStateFromProps(nextProps, prevState){if(nextProps.someValue!==prevState.someValue){return { someState: nextProps.someValue};
}
return null
}
它接收两个参数 nextProps
和 prevState
。如前所述,你无法在这个方法中访问 this
。你必须将 prop 存储在 state 中,然后将 nextProps
与之前的 prop 进行对比。在上面的代码中,nextProps 和 prevState 进行了比较。如果两者不同,则返回一个用于更新状态的对象,否则就返回 null,表示不需要更新状态。如果 state 发生变更,就会调用 componentDidUpdate,我们可以像在 componentWillReceiveProps 中那样执行所需的操作。
React 生命周期事件
react v16.3,最大的变动莫过于生命周期去掉了以下三个:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
同时为了弥补失去上面三个周期的不足又加了两个:
- static getDerivedStateFromProps
- getSnapshotBeforeUpdate
为什么要改
旧的生命周期十分完整,基本可以捕捉到组件更新的每一个 state/props/ref,没有什从逻辑上的毛病。
但是架不住官方自己搞事情,react 打算在 17 版本推出新的 Async Rendering,提出一种可被打断的生命周期,而可以被打断的阶段正是实际 dom 挂载之前的虚拟 dom 构建阶段,也就是要被去掉的三个生命周期。
生命周期一旦被打断,下次恢复的时候又会再跑一次之前的生命周期,因此 componentWillMount,componentWillReceiveProps,componentWillUpdate 都不能保证只在挂载 / 拿到 props/ 状态变化的时候刷新一次了,所以这三个方法被标记为不安全。
你的点赞是我持续分享好东西的动力,欢迎点赞!
欢迎加入前端大家庭,里面会经常分享一些技术资源。