2
原文:5 common practices that you can stop doing in React

从下面这点来说,很难说React是这个星球上最受欢迎的库之一。很多人对React感兴趣,新的开发者之所以倾向于使用这个框架的原因是因为它的UI优先方法。虽然这些年React和它的整个生态已经比较成熟,但是在某些情况下你仍然会这样问自己:“正确的做法到底是什么?”

这是一个好问题--并不总是有一个通用的“正确”的做事方式。事实上,正如你所知,最佳实践并不总是那么的适用。长远来看,其中的某些写法可能会影响性能,可读性,并导致效率低下。

在本文中,我将描述在React开发中5种被普遍接受的写法,但是实际上你是可以避免这样做的。当然,我将解释为什么我认为这些做法是可以避免的,并且我会建议你用其他的写法来完成相同的事情。

从一开始就优化React

React的开发人员在如何使React更快投入了大量的精力,每次迭代新的优化方法又会被添加进去。在我看来,你不应该花费时间优化这些东西,除非你发现了真正的性能影响。

为什么?

React相比其它的前端平台更容易扩展,因为你不必重写某个模块来加快应用的速度。导致性能问题的罪魁祸首通常是React使用更新虚拟DOMreconciliation过程。

让我们看一下React是如何处理下面的事情的。在每个render()中,React生成了一个由UI元素组成的树——子节点是实际的DOM元素。当state或者props更新的时候,React需要使用最小的改变次数来生成一个新的树,并保持可预测性。

enter image description here

想象一下,你看到的树是这个样子的:

enter image description here

想象一下,你的应用接收到新的数据,下列的节点需要被更新:

enter image description here

React通常会重新渲染整个子树,而不是像这样仅仅渲染相关节点:

enter image description here

当顶层组件的state发生改变的时候,它的子元素都会重新渲染。这是默认的行为,在小型应用中不需要担心。随着应用的逐渐扩展,您应该考虑使用Chrome Profiling工具来测量实际性能。这个工具将为你提供在不必要的渲染上所浪费时间的精确数据。如果这些数据很重要,你可以通过向你的组件中添加shouldComponentUpdate钩子来优化你的渲染时间。

这个钩子在重新渲染开始之前将被触发,默认返回true:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

当返回true时,Reactdiff算法接管并重新渲染整个子树。你能避免这样的情况发生,通过在shouldComponentUpdate中添加比较逻辑,并且仅当相关的props发生改变的时候更新逻辑。

shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

除了color/count,其它任何props/state改变,这个组件都不会更新。

除此之外,还有一些开发人员通常忽略的non-React优化技巧,但它们会影响应用程序的性能。

我将在下面列举一些可以避免的习惯和解决方案:

  • 未优化的图片--如果你的应用中有很多动态图片,你需要在处理图片时考虑你的选项。较大的图片可能会给用户一种应用很慢的印象。在将图片推入服务器之前压缩图片,或者使用动态图片处理解决方案来替代。我个人喜欢Cloudinary来优化react图片因为它有它自己的react库,但是你也可以使用Amazon S3或者Firebase
  • 未压缩的构建文件--Gzipping构建文件(bundle.js)可以有效的减少文件大小。你需要修改webserver的配置文件。Webpack有一个gzip压缩插件,叫做compression-webpack-plugin。你可以使用这个技术在构建时间生成bundle.js.gz

服务端渲染的SEO

虽然单页面应用非常棒,但是他们仍然存在两个问题。

  • 当应用首次加载的时候,浏览器中没有JavaScript缓存。如果应用很大,首次加载应用的时间将会很慢。
  • 由于应用程序是在客户端进行渲染,搜索引擎使用的web爬虫程序将无法索引JavaScript生成的内容。搜索引擎将看到你的应用是空白的,然后排名很差。

这就是服务端渲染技术派上用处的地方。在SSR中,JavaScript内容最初是由服务器渲染的。在首次渲染后,客户端的脚本接管,并像一个正常的SPA应用运行。由于你需要使用Node/Express服务,因此在以传统SSR进行构建时复杂度和花费更高。

如果你是为了搜索引擎优化、谷歌索引和没有任何问题地抓取JavaScript内容,那么有一个好消息。谷歌实际上是在2016年开始抓取JavaScript素材的,这个算法现在已经可以完美地工作了。

以下是2015年10月Webmaster博客的节选。

今天,只要你不阻止Googlebot抓取你的JavaScript或CSS文件,我们就能像现代浏览器一样渲染和理解你的网页。为了反映这一改进,我们最近更新了我们的技术站长指南,建议不要禁止Googlebot抓取你网站的CSS或JS文件。

如果你使用服务器端渲染,是因为你担心你的谷歌页面排名,那么你不需要使用SSR。它曾经是过去的事,但现在不是了。

然而,如果你正在改善首次渲染速度,那么你应该尝试使用像Next.js这样的库来实现更加简单的SSRNext将节省你在设置Node/Express 服务所花费的时间。

Inline styles和CSS imports

在使用React做开发时,我曾亲自尝试过多种不同的样式理念,为了能找到一种新的引入样式到React组件的方式。在React组件中使用已经存在多年的传统的CSS-in-CSS 方法。你的样式表全部样放在一个样式表目录,然后你可以将所需要的CSS导入到你的组件中。

然而,当你使用这些组件的时候,样式表不再清晰明了。React鼓励你以组件化的方式来思考你的应用,然而样式表则强迫你以文档的角度来思考。

目前有多种方法将CSS和JS代码合并到一个文件中。内联样式可能是其中最流行的。

const divStyle = {
  margin: '40px',
  border: '5px solid pink'
};
const pStyle = {
  fontSize: '15px',
  textAlign: 'center'
};

const TextBox = () => (
  <div style={divStyle}>
    <p style={pStyle}>Yeah!</p>
  </div>
);

export default TextBox;

你不需要再导入CSS,但是这会牺牲可读性和可维护性。除了这样做,内联样式不支持媒体查询,伪元素以及样式回退。当然,有一些技巧可以让你解决上述问题,但是使用起来却并不方便。

这就是CSS-in-JSS派上用处的地方,内联样式并不完全是CSS-in-JSS。下面的代码演示了使用styled-components的概念。

import styled from 'styled-components';

const Text = styled.div`
  color: white,
  background: black
`
<Text>This is CSS-in-JS</Text>

在浏览器中看上去是这样的:

<style>
.hash234dd2 {
  background-color: black;
  color: white;
}
</style>

<p class="hash234dd3">This is CSS-in-JS</p>

新的<style>标签被添加到了顶层的DOM中,不像内联样式,实际的CSS样式在这里生成。所以,任何能在CSS运行的东西,在样式组件中也能工作。此外,这个技术还增强了CSS,改善了可读性并且适用到组件的架构中。使用styled-components 库,你还可以得到SASS的支持。

嵌套的条件运算符

条件运算符在React中很常用。它是我用来创建状态语句的go-to操作符,在render()方法中得到了很好的应用。例如,在下列中它们帮助你以内联的方式渲染元素,我使用它来显示登入状态。

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}

然而,当你一次又一次的嵌套条件运算符,它们可能变的丑陋并且难以阅读。

int median(int a, int b, int c) {
    return (a<b) ? (b<c) ? b : (a<c) ? c : a : (a<c) ? a : (b<c) ? c : b;
}

如你所见,速记符号更加的简洁,但是它们往往会使代码看起混乱。想象一下如果在你的结构中有12个或者更多嵌套的条件运算符。这比你所认为的要常发生。一旦开始使用条件运算符,就很容易继续嵌套它,最后,您会发现需要一种更好的技术来处理状态渲染。

但好的方面是你有很多其它的选择。你可以使用增强JSX控制语句的babel插件,用来扩展JSX以包含用于条件语句和循环的组件。

// before transformation
<If condition={ test }>
  <span>Truth</span>
</If>

// after transformation
{ test ? <span>Truth</span> : null }

这里有另外一个流行的技术叫做iify(IIFE--立即执行函数表达式)。它是一个在声明之后会立即执行的匿名函数。

(function() {
 // Do something​
 }
)()

为了使匿名函数成为函数表达式,我们将函数封装在一对括号中。这个模式在JavaScript中很流行,原因有很多。但是在React里,我们可以把所有if/else语句放到函数中,然后返回我们想要渲染的内容。

这里有一个例子演示我们怎样在React中使用IFFE

{
   (() => {
      if (this.props.status === 'PENDING') {
         return (<div className="loading" />);
      }
      else {
         return (<div className="container" />);

   })()
} 

IIFE可能对性能有所影响,但是在大多数情况下不会有太大的影响。这里有更多的方法来运行React中的条件语句,并且我们已经在用于React中状态渲染的8个方法中有所说明。

在React中的闭包

闭包是一个获取外部函数变量和参数的内部函数。闭包在JavaScript无处不不在,并且你可能一直使用到它,即使你还没有注意到。

class SayHi extends Component {

    render () {
     return () {
      <Button onClick={(e) => console.log('Say Hi', e)}>
        Click Me
      </Button>
     }
    }
}

但是你在render()方法中使用闭包时,它确实是不好的。每当SayHi组件重新渲染,新的匿名会被重新创建并传入到Button组件中。虽然porps没有改变,但是<Button /> 将被强制重新渲染。正如前面所言,重复的渲染会直接带来性能的损耗。

因此,使用类方法来代替闭包。类方法可读性更高并且更容易调试。

class SayHi extends Component {

  showHiMessage = this.showMessage('Hi')
 
  render () {
   return () {
      <Button onClick={this.showHiMessage}>
            Click Me
      </Button>
   }
  }
}

结论

当一个平台逐渐扩大,每天都会出现新的模式。一些模式帮助你改善整个工作流程。而其它的一些方式则会有明显的副作用。当副作用影响应用的性能的时候或者可读性时,最好是寻找替代方案。在本文中,我介绍了一些因为它们的缺点,你可以在React中避免的做法。

您对React的看法和最佳做法是什么?在评论中分享它们。(想看评论直接去原文看吧)


malloc
562 声望11 粉丝