本文是译者第一次作完整的全篇翻译,主要目的是学习一下这类文章的写做风格,因此挑了一篇相对入门、由浅入深的文章,全篇采用直译,即便有时候以为做者挺啰嗦的,也依然翻译了原文内容。html
原文地址:8 React conditional rendering methodsreact
相较于Javascript,JSX是一个很好的扩展,它容许咱们定义UI组件。可是,它不提供条件、循环表达式的原生支持(增长条件表达式在该issue中被讨论过)。git
译者注:条件、循环表达式通常是模板引擎默认提供的最基本语法程序员
假设你须要遍历一个列表,去渲染多个组件或者实现一些条件判断逻辑,都必须用到JS。不过大部分状况下,可选的方法不多,Array.prototype.map
都能知足需求。github
但,条件表达式呢?web
那就是另外一个故事了。算法
在React中有好几种方法能够实现条件表达式。而且,不一样的方法适用于不一样的场景,取决于你须要处理什么样的问题。编程
本文包含了最多见的几种条件渲染方法:浏览器
为了说明这些方法都是如何使用的,本文实现了一个编辑/展现态互相切换的组件:bash
你能够在JSFiddle运行、体验全部示例代码。
译者注:JSFiddle在墙内打开实在太慢了,故本文不贴出完整示例地址,若有须要,可自行查看原文连接。若是有合适的替代产品,欢迎告知
首先,咱们建立一个基础组件:
class App extends React.Component {
state = {
text: '',
inputText: '',
mode: 'view',
}
}
复制代码
text
属性存储已存的文案,inputText
属性存储输入的文案,mode
属性来存储当前是编辑态仍是展现态。
接下来,咱们增长一些方法来处理input输入以及状态切换:
class App extends React.Component {
state = {
text: '',
inputText: '',
mode: 'view',
}
handleChange = (e) => {
this.setState({ inputText: e.target.value });
}
handleSave = () => {
this.setState({text: this.state.inputText, mode: 'view'});
}
handleEdit = () => {
this.setState({mode: 'edit'});
}
}
复制代码
如今到了render
方法,咱们须要检测state中的mode
属性来决定是渲染一个编辑按钮仍是一个文本输入框+一个保存按钮:
class App extends React.Component {
// …
render () {
if(this.state.mode === 'view') {
return (
<div> <p>Text: {this.state.text}</p> <button onClick={this.handleEdit}> Edit </button> </div>
);
} else {
// 译者注:若是if代码块里有return时,通常不须要写else代码块,不过为了贴合标题仍是保留了
return (
<div> <p>Text: {this.state.text}</p> <input onChange={this.handleChange} value={this.state.inputText} /> <button onClick={this.handleSave}> Save </button> </div> ); } } 复制代码
If/Else是最简便的实现条件渲染的方法,不过我确定,你不认为这是一个好的实现方式。
它的优点是,在简单场景下使用方便,而且每一个程序员都理解这种使用方式;它的劣势是,会存在一些重复代码,而且render方法会变得臃肿。
那咱们来简化一下,咱们把全部的条件判断逻辑放入两个render方法,一个用来渲染输入框,另外一个用来渲染按钮:
class App extends React.Component {
// …
renderInputField() {
if (this.state.mode === 'view') {
return <div />;
} else {
return (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText}
/>
</p>
);
}
}
renderButton() {
if (this.state.mode === 'view') {
return (
<button onClick={this.handleEdit}>
Edit
</button>
);
} else {
return (
<button onClick={this.handleSave}>
Save
</button>
);
}
}
render() {
return (
<div>
<p>Text: {this.state.text}</p>
{this.renderInputField()}
{this.renderButton()}
</div>
);
}
}
复制代码
注意在示例中,renderInputField
函数在视图模式下,返回的是一个空div。一般来讲,不推荐这么作。
若是想隐藏一个组件,你能够经过让该组件的render函数返回null
,不必使用一个空div或者其余什么元素去作占位符。
须要注意的是,即便返回了null,该组件“不可见”,但它的生命周期依然会运行。
举个例子,下面的例子用两个组件实现了一个计数器:
class Number extends React.Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
render() {
if (this.props.number % 2 == 0) {
return (
<div> <h1>{this.props.number}</h1> </div>
);
} else {
return null;
}
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 }
}
onClick(e) {
this.setState(prevState => ({
count: prevState.count + 1
}));
}
render() {
return (
<div> <Number number={this.state.count} /> <button onClick={this.onClick.bind(this)}>Count</button> </div> ) } } ReactDOM.render( <App />, document.getElementById('root') ); 复制代码
Number
组件只有在偶数时才会展现。由于奇数时,render函数返回了null。可是,当你查看console时会发现,componentDidUpdate
函数每次都会执行,不管render
函数返回什么。
回到本文的例子,咱们对renderInputField
函数稍做修改:
renderInputField() {
if (this.state.mode === 'view') {
return null;
} else {
return (
<p> <input onChange={this.handleChange} value={this.state.inputText} /> </p> ); } } 复制代码
此外,返回null而不是空div的另外一个好处是,这能够略微提高整个React应用的性能,由于React不须要在更新的时候unmount这个空div。
举个例子,若是是返回空div,在控制台中,你能够发现,root节点下的div
元素会始终更新:
相对的,若是是返回null,当Edit
按钮被点击时,这个div
元素不会更新:
你能够在这里继续深刻了解React是如何更新DOM元素,以及调和算法是如何工做的。
在这个简单的例子中,也许这点性能差距是微不足道的,但若是是一个大型组件,性能差距就不容忽视。
我会在下文继续讨论条件渲染的性能影响。不过如今,让咱们先继续聚焦在这个例子上。
有时候,我不喜欢在一个方法中包含多个return
。因此,我会使用一个变量去指向这个JSX元素,而且只有当条件为true
的时候才去初始化。
renderInputField() {
let input;
if (this.state.mode !== 'view') {
input =
<p> <input onChange={this.handleChange} value={this.state.inputText} /> </p>; } return input; } renderButton() { let button; if (this.state.mode === 'view') { button = <button onClick={this.handleEdit}> Edit </button>; } else { button = <button onClick={this.handleSave}> Save </button>; } return button; } 复制代码
这些方法的返回结果和上一节的两个方法返回一致。
如今,render函数会变得更易读,不过在本例中,其实不必使用if/else(或者switch)代码块,也不必使用多个render方法。
咱们能够写得更简洁一些。
咱们可使用三元运算符替代if/else代码块:
condition ? expr_if_true : expr_if_false
复制代码
整个运算符能够放在jsx的{}
中,每个表达式能够用()
来包裹JSX来提高可读性。
三元运算符能够用在组件的不一样地方(?),让咱们在例子中实际应用看看。
译者注:标记?的这句话我我的不是很理解
我先移除renderInputField
和renderButton
方法,并在render
中增长一个变量来表示组件是处于view
模式仍是edit
模式:
render () {
const view = this.state.mode === 'view';
return (
<div> </div>
);
}
复制代码
接下来,添加三元运算符——当处于view
模式时,返回null;处于edit
模式时,返回输入框:
// ...
return (
<div> <p>Text: {this.state.text}</p> { view ? null : ( <p> <input onChange={this.handleChange} value={this.state.inputText} /> </p> ) } </div> ); 复制代码
经过三元运算符,你能够经过改变组件内的标签或者回调函数来渲染一个保存/编辑按钮:
// ...
return (
<div> <p>Text: {this.state.text}</p> { ... } <button onClick={ view ? this.handleEdit : this.handleSave } > {view ? 'Edit' : 'Save'} </button> </div>
);
复制代码
三元运算符在某些场景下能够更加简化。例如,当你要么渲染一个组件,要么不作渲染,你可使用&&
运算符。
不像&
运算符,若是&&
执行左侧的表达式就能够确认结果的话,右侧表达式将不会执行。
举个例子,若是左侧表达式结果为false(false && ...
),那么下一个表达式就不须要执行,由于结果永远都是false。
在React中,你能够这样运用:
return (
<div> { showHeader && <Header /> } </div>
);
复制代码
若是showHeader
结果为true
,那么<Header />
组件就会被返回;若是showHeader
结果为false,那么<Header />
组件会被忽略,返回的会是一个空div
。
上文的代码中:
{
view
? null
: (
<p> <input onChange={this.handleChange} value={this.state.inputText} /> </p> ) } 复制代码
能够被改成:
!view && (
<p> <input onChange={this.handleChange} value={this.state.inputText} /> </p> ) 复制代码
如今,完整的例子以下:
class App extends React.Component {
state = {
text: '',
inputText: '',
mode: 'view',
}
handleChange = (e) => {
this.setState({ inputText: e.target.value });
}
handleSave = () => {
this.setState({ text: this.state.inputText, mode: 'view' });
}
handleEdit = () => {
this.setState({mode: 'edit'});
}
render () {
const view = this.state.mode === 'view';
return (
<div> <p>Text: {this.state.text}</p> { !view && ( <p> <input onChange={this.handleChange} value={this.state.inputText} /> </p> ) } <button onClick={ view ? this.handleEdit : this.handleSave } > {view ? 'Edit' : 'Save'} </button> </div> ); } } ReactDOM.render( <App />, document.getElementById('root') ); 复制代码
这样看上去是否是好了不少?
然而,三元运算符有时候会让人困扰,好比以下的复杂代码:
return (
<div> { condition1 ? <Component1 /> : ( condition2 ? <Component2 /> : ( condition3 ? <Component3 /> : <Component 4 /> ) ) } </div> ); 复制代码
很快,这些代码会变为一团乱麻,所以,有时候你须要一些其余技巧,好比:自执行函数。
顾名思义,自执行函数就是在定义之后会被马上执行,没有必要显式地调用他们。
一般来讲,函数是这么被定义并执行的:
function myFunction() {
// ...
}
myFunction();
复制代码
若是你指望一个函数在被定之后马上执行,你须要使用括号将整个定义包起来(将函数做为一个表达式),而后传入须要使用的参数。
示例以下:
( function myFunction(/* arguments */) {
// ...
}(/* arguments */) );
复制代码
或:
( function myFunction(/* arguments */) {
// ...
} ) (/* arguments */);
复制代码
若是这个函数不会在其余地方被调用,你能够省略名字:
( function (/* arguments */) {
// ...
} ) (/* arguments */);
复制代码
或使用箭头函数:
( (/* arguments */) => {
// ...
} ) (/* arguments */);
复制代码
在React中,你能够用一个大括号包裹一整个自执行函数,把全部逻辑都放在里面(if/else、switch、三元运算符等等),而后返回你须要渲染的东西。
举个例子,若是使用自执行函数去渲染一个编辑/保存按钮,代码会是这样的:
{
(() => {
const handler = view
? this.handleEdit
: this.handleSave;
const label = view ? 'Edit' : 'Save';
return (
<button onClick={handler}> {label} </button>
);
})()
}
复制代码
有时候,自执行函数看上去像是黑科技。
使用React的最佳实践是,尽量地将逻辑拆分在各个组件内,使用函数式编程,而不是命令式编程。
因此,将条件渲染的逻辑放入一个子组件,子组件经过props来渲染不一样的内容会是一个不错的方案。
但在这里,我不这么作,在下文中我会向你展现一种更声明式、更函数式的写法。
首先,我建立一个SaveComponent
:
const SaveComponent = (props) => {
return (
<div> <p> <input onChange={props.handleChange} value={props.text} /> </p> <button onClick={props.handleSave}> Save </button> </div> ); }; 复制代码
经过props它接受足够的数据来供它展现。一样的,我再写一个EditComponent
:
const EditComponent = (props) => {
return (
<button onClick={props.handleEdit}> Edit </button>
);
};
复制代码
render
方法如今看起来会是这样:
render () {
const view = this.state.mode === 'view';
return (
<div>
<p>Text: {this.state.text}</p>
{
view
? <EditComponent handleEdit={this.handleEdit} />
: (
<SaveComponent
handleChange={this.handleChange}
handleSave={this.handleSave}
text={this.state.inputText}
/>
)
}
</div>
);
}
复制代码
有些库,例如JSX Control Statements,它们经过扩展JSX去支持条件状态:
<If condition={ true }>
<span>Hi!</span>
</If>
复制代码
这些库提供了更多高级的组件,不过,若是咱们只须要一些简单的if/else,咱们能够写一个组件,相似Michael J. Ryan在这个issue的回复中提到的:
const If = (props) => {
const condition = props.condition || false;
const positive = props.then || null;
const negative = props.else || null;
return condition ? positive : negative;
};
// …
render () {
const view = this.state.mode === 'view';
const editComponent = <EditComponent handleEdit={this.handleEdit} />;
const saveComponent = <SaveComponent
handleChange={this.handleChange}
handleSave={this.handleSave}
text={this.state.inputText}
/>;
return (
<div>
<p>Text: {this.state.text}</p>
<If
condition={ view }
then={ editComponent }
else={ saveComponent }
/>
</div>
);
}
复制代码
高阶组件(HOC)指的是一个函数,它接受一个已存在的组件,而后返回一个新的组件而且新增了一些方法:
const EnhancedComponent = higherOrderComponent(component);
复制代码
应用在条件渲染中,一个高阶组件能够经过一些条件,返回不一样的组件:
function higherOrderComponent(Component) {
return function EnhancedComponent(props) {
if (condition) {
return <AnotherComponent { ...props } />;
}
return <Component { ...props } />;
};
}
复制代码
这篇Robin Wieruch写的精彩文章中,他对使用高阶组件来完成条件渲染有更深刻的研究。
经过这篇文章,我准备借鉴EitherComponent
的概念。
在函数式编程中,Ether
常常被用来作一层包装以返回两个不一样的值。
让咱们先定义一个函数,它接受两个函数类型的参数,第一个函数会返回一个布尔值(条件表达式执行的结果),另外一个是当结果为true
时返回的组件。
function withEither(conditionalRenderingFn, EitherComponent) {
}
复制代码
这种高阶组件的名字通常以with
开头。
这个函数会返回一个函数,它接受原始组件为参数,并返回一个新组件:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
}
}
复制代码
再内层的函数返回的组件将是你在应用中使用的,因此它须要接受一些属性来运行:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
return function FinalComponent(props) {
}
}
}
复制代码
由于内层函数能够拿到外层函数的参数,因此,基于conditionalRenderingFn
的返回值,你能够返回EitherComponent
或者是原始的Component
:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
return function FinalComponent(props) {
return conditionalRenderingFn(props)
? <EitherComponent { ...props } />
: <Component { ...props } />;
}
}
}
复制代码
或者,使用箭头函数:
const withEither = (conditionalRenderingFn, EitherComponent) => (Component) => (props) =>
conditionalRenderingFn(props)
? <EitherComponent { ...props } />
: <Component { ...props } />;
复制代码
你能够用到以前定义的SaveComponent
和EditComponent
来建立一个withEditConditionalRendering
高阶组件,最终,建立一个EditSaveWithConditionalRendering
组件:
const isViewConditionFn = (props) => props.mode === 'view';
const withEditContionalRendering = withEither(isViewConditionFn, EditComponent);
const EditSaveWithConditionalRendering = withEditContionalRendering(SaveComponent);
复制代码
译者注:苍了个天,杀鸡用牛刀
最终,在render
中,你传入全部须要用到的属性:
render () {
return (
<div> <p>Text: {this.state.text}</p> <EditSaveWithConditionalRendering mode={this.state.mode} handleEdit={this.handleEdit} handleChange={this.handleChange} handleSave={this.handleSave} text={this.state.inputText} /> </div> ); } 复制代码
条件渲染有时很微妙,上文中提到了不少方法,它的性能是不同的。
然而,大部分场景下,这些差别不算什么。可是当你须要作的时候,你须要对React的虚拟DOM是如何运转有很好的理解,而且掌握一些优化技巧:
这里有篇关于优化条件渲染的文章,我推荐阅读。
核心点是,若是条件渲染的组件会引发位置的变动,那它会引发重排,从而致使app中的组件装载/卸载。
译者注:这里的重排指的不是浏览器渲染的重排,算是虚拟DOM的概念
基于文中的例子,我作了以下两个例子。
第一个使用if/else来展现/隐藏SubHeader
组件:
const Header = (props) => {
return <h1>Header</h1>;
}
const Subheader = (props) => {
return <h2>Subheader</h2>;
}
const Content = (props) => {
return <p>Content</p>;
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
if(this.state.isToggleOn) {
return (
<div> <Header /> <Subheader /> <Content /> <button onClick={this.handleClick}> { this.state.isToggleOn ? 'ON' : 'OFF' } </button> </div>
);
} else {
return (
<div> <Header /> <Content /> <button onClick={this.handleClick}> { this.state.isToggleOn ? 'ON' : 'OFF' } </button> </div>
);
}
}
}
ReactDOM.render(
<App />, document.getElementById('root') ); 复制代码
另外一个使用短路运算符(&&
)实现:
const Header = (props) => {
return <h1>Header</h1>;
}
const Subheader = (props) => {
return <h2>Subheader</h2>;
}
const Content = (props) => {
return <p>Content</p>;
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<div> <Header /> { this.state.isToggleOn && <Subheader /> } <Content /> <button onClick={this.handleClick}> { this.state.isToggleOn ? 'ON' : 'OFF' } </button> </div>
);
}
}
ReactDOM.render(
<App />, document.getElementById('root') ); 复制代码
打开控制台,并屡次点击按钮,你会发现Content
组件的表如今两种实现中式不一致的。
译者注:例子1中的写法,Content每次都会被从新渲染
就像编程中的其余事情同样,在React中实现条件渲染有不少种实现方式。
你能够自由选择任一方式,除了第一种(if/else而且包含了不少return)。
你能够基于这些理由来找到最适合当前场景的方案:
固然,有些事是始终重要的,那就是保持简单和可读性。