欢迎你们前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~node
自从去年9月份 React 团队发布了 v16.0 版本开始,到18年3月刚发布的 v16.3 版本,React 陆续推出了多项重磅新特性,并改进了原有功能中反馈呼声很高的一些问题,例如 render 方法内单节点层级嵌套问题,提供生命周期错误捕捉,组件指定 render 到任意 DOM 节点 (Portal) 等能力,以及最新的 Context API 和 Ref API。咱们在对以上新特性通过一段时间的使用事后,经过本文进行一些细节分享和总结。redux
为了符合 React 的 component tree 和 diff 结构设计,在组件的 render() 方法中顶层必须包裹为单节点,所以实际组件设计和使用中老是须要注意嵌套后的层级变深,这是 React 的一个常常被人诟病的问题。好比如下的内容结构就必须再嵌套一个 div 使其变为单节点进行返回:数组
render() {
return (
<div>
注:
<p>产品说明一</p>
<p>产品说明二</p>
</div>
);
}
复制代码
如今在更新 v16 版本后,这个问题有了新的改进,render 方法能够支持返回数组了:bash
render() {
return [
"注:",
<p key="t-1">产品说明一</h2>,
<p key="t-2">产品说明二</h2>,
];
}
复制代码
这样确实少了一层,但你们又继续发现代码仍是不够简洁。首先 TEXT 节点须要用引号包起来,其次因为是数组,每条内容固然还须要添加逗号分隔,另外 element 上还须要手动加 key 来辅助 diff。给人感受就是不像在写 JSX 了。微信
因而 React v16.2 趁热打铁,提供了更直接的方法,就是 Fragment:并发
render() {
return (
<React.Fragment>
注:
<p>产品说明一</p>
<p>产品说明二</p>
</React.Fragment>
);
}
复制代码
能够看到是一个正常单节点写法,直接包裹里面的内容。可是 Fragment 自己并不会产生真实的 DOM 节点,所以也不会致使层级嵌套增长。异步
另外 Fragment 还提供了新的 JSX 简写方式 <></>:async
render() {
return (
<>
注:
<p>产品说明一</p>
<p>产品说明二</p>
</>
);}
复制代码
看上去是否舒服多了。不过注意若是须要给 Fragment 添加 key prop,是不支持使用简写的(这也是 Fragment 惟一会遇到须要添加props的状况):ide
<dl>
{props.items.map(item => (
// 要传key用不了 <></>
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
复制代码
错误边界是指以在组件上定义 componentDidCatch 方法的方式来建立一个有错误捕捉功能的组件,在其内嵌套的组件在生命过程当中发生的错误都会被其捕捉到,而不会上升到外部致使整个页面和组件树异常 crash。
例以下面的例子就是经过一个 ErrorBoundary 组件对其内的内容进行保护和错误捕捉,并在发生错误时进行兜底的UI展现:
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { error: null };
}
componentDidCatch(error,
{componentStack}
) {
this.setState({
error,
componentStack,
});
}
render() {
if (this.state.error) {
return (
<>
<h1>报错了.</h1>
<ErrorPanel {...this.state} />
</>
);
}
return this.props.children;
}
}
export default function App(){
return (
<ErrorBoundary>
<Content />
</ErrorBoundary>
);
}
复制代码
须要注意的是错误边界只能捕捉生命周期中的错误 (willMount / render 等方法内)。没法捕捉异步的、事件回调中的错误,要捕捉和覆盖全部场景依然须要配合 window.onerror、Promise.catch、 try/catch 等方式。
这个 API 是用来将部份内容分离式地 render 到指定的 DOM 节点上。不一样于使用 ReactDom.render 新建立一个 DOM tree 的方式,对于要经过 createPortal() “分离”出去的内容,其间的数据传递,生命周期,甚至事件冒泡,依然存在于本来的抽象组件树结构当中。
class Creater extends Component {
render(){
return (
<div onClick={() =>
alert("clicked!")
}>
<Portal>
<img src={myImg} />
</Portal>
</div>
);
}
}
class Portal extends Component {
render(){
const node = getDOMNode();
return createPortal(
this.props.children,
node
);
}
}
复制代码
例如以上代码, 经过 把里面的 内容渲染到了一个独立的节点上。在实际的 DOM 结构中,img 已经脱离了 Creater 自己的 DOM 树存在于另外一个独立节点。但当点击 img 时,仍然能够神奇的触发到 Creater 内的 div 上的 onclick 事件。这里实际依赖于 React 代理和重写了整套事件系统,让整个抽象组件树的逻辑得以保持同步。
之前的版本中 Context API 是做为未公开的实验性功能存在的,随着愈来愈多的声音要求对其进行完善,在 v16.3 版本,React 团队从新设计并发布了新的官方 Context API。
使用 Context API 能够更方便的在组件中传递和共享某些 "全局" 数据,这是为了解决以往组件间共享公共数据须要经过多余的 props 进行层层传递的问题 (props drilling)。好比如下代码:
const HeadTitle = (props) => {
return (
<Text>
{props.lang.title}
</Text>;
);
};
// 中间组件
const Head = (props) => {
return (
<div>
<HeadTitle lang={props.lang} />
</div>
);
};
class App extends React.Component {
render() {
return (
<Head lang={this.props.lang} />;
);
}
}
export default App = connect((state) => {
return {
lang:state.lang
}
})(App);
复制代码
咱们为了使用一个语言包,把语言配置存储到一个 store 里,经过 Redux connect 到顶层组件,然而仅仅是最底端的子组件才须要用到。咱们也不可能为每一个组件都单独加上 connect,这会形成数据驱动更新的重复和不可维护。所以中间组件须要一层层不断传递下去,就是所谓的 props drilling。
对于这种全局、不常修改的数据共享,就比较适合用 Context API 来实现:
首先第一步,相似 store,咱们能够先建立一个 Context,并加入默认值:
const LangContext = React.createContext({
title:"默认标题"
});
复制代码
而后在顶层经过 Provider 向组件树提供 Context 的访问。这里能够经过传入 value 修改 Context 中的数据,当value变化的时候,涉及的 Consumer 内整个内容将从新 render:
class App extends React.Component {
render() {
return (
<LangContext.Provider
value={this.state.lang}
>
<Head />
</LangContext.Provider>
);
}
}
复制代码
在须要使用数据的地方,直接用 Context.Consumer 包裹,里面能够传入一个 render 函数,执行时从中取得 Context 的数据。
const HeadTitle = (props) => {
return (
<LangContext.Consumer>
{lang =>
<Text>{lang.title}</Text>
}
</LangContext.Consumer>
);
};
复制代码
以后的中间组件也再也不须要层层传递了,少了不少 props,减小了中间漏传致使出错,代码也更加清爽:
// 中间组件
const Head = () => {
return (
<div>
<HeadTitle />
</div>
);
};
复制代码
那么看了上面的例子,咱们是否能够直接使用 Context API 来代替掉全部的数据传递,包括去掉 redux 这些数据同步 library 了?其实并不合适。前面也有提到,Context API 应该用于须要全局共享数据的场景,而且数据最好是不用频繁更改的。由于做为上层存在的 Context,在数据变化时,容易致使全部涉及的 Consumer 从新 render。
好比下面这个例子:
render() {
return (
<Provider value={{
title:"my title"
}} >
<Content />
</Provider>
);
}
复制代码
实际每次 render 的时候,这里的 value 都是传入一个新的对象。这将很容易致使全部的 Consumer 都从新执行 render 影响性能。
所以不建议滥用 Context,对于某些非全局的业务数据,也不建议做为全局 Context 放到顶层中共享,以避免致使过多的 Context 嵌套和频繁从新渲染。
除了 Context API 外,v16.3 还推出了两个新的 Ref API,用来在组件中更方便的管理和使用 ref。
在此以前先看一下咱们以前使用 ref 的两种方法。
// string命名获取
componentDidMount(){
console.log(this.refs.input);
}
render() {
return (
<input
ref="input"
/>
);
}
复制代码
// callback 获取
render() {
return (
<input
ref={el => {this.input = el;}}
/>
);
}
复制代码
前一种 string 的方式比较局限,不方便于多组件间的传递或动态获取。后一种 callback 方法是以前比较推荐的方法。可是写起来略显麻烦,并且 update 过程当中有发生清除可能会有屡次调用 (callback 收到 null)。
为了提高易用性,新版本推出了 CreateRef API 来建立一个 ref object, 传递到 component 的 ref 上以后能够直接得到引用:
constructor(props) {
super(props);
this.input = React.createRef();
}
componentDidMount() {
console.log(this.input);
}
render() {
return <input ref={this.input} />;
}
复制代码
另外还提供了 ForwardRef API 来辅助简化嵌套组件、component 至 element 间的 ref 传递,避免出现 this.ref.ref.ref 的问题。
例如咱们有一个包装过的 Button 组件,想获取里面真正的 button DOM element,原本须要这样作:
class MyButton extends Component {
constructor(props){
super(props);
this.buttonRef = React.createRef();
}
render(){
return (
<button ref={this.buttonRef}>
{props.children}
</button>
);
}
}
class App extends Component {
constructor(props){
super(props);
this.myRef = React.createRef();
}
componentDidComponent{
// 经过ref一层层访问
console.log(this.myRef.buttonRef);
}
render(){
return (
<MyButton ref={this.myRef}>
Press here
</MyButton>
);
}
}
复制代码
这种场景使用 forwardRef API 的方式作一个“穿透”,就能简便许多:
import { createRef, forwardRef } from "react";
const MyButton = forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
class App extends Component {
constructor(props){
super(props);
this.realButton = createRef();
}
componentDidComponent{
//直接拿到 inner element ref
console.log(this.realButton);
}
render(){
return (
<MyButton ref={this.realButton}>
Press here
</MyButton>
);
}
}
复制代码
以上就是 React v16 发布以来几个比较重要和有用的新特性,优化的同时也带来了开发体验的提高。另外 v16 对比以前版本还有不错的包大小下降,也是很是具备优点的:
除此以外,想要了解更多的一些变动好比生命周期的更新 (getDerivedStateFromProps, getSnapshotBeforeUpdate) 和 SSR 的优化 (hydrate),以及即将推出的 React Fiber (async render) 动向,能够点击查看原文了解更多的官方信息。
这么多激动人心的特性,若是你还在用 v15 甚至旧版,就赶快升级体验吧!
问答
相关阅读
Android Native 开发之 NewString 与 NewStringUtf 解析
此文已由做者受权腾讯云+社区发布,原文连接:https://cloud.tencent.com/developer/article/1137778?fromSource=waitui
欢迎你们前往腾讯云+社区或关注云加社区微信公众号(QcloudCommunity),第一时间获取更多海量技术实践干货哦~