原文:5 common practices that you can stop doing in React
从下面这点来说,很难说React
是这个星球上最受欢迎的库之一。很多人对React
感兴趣,新的开发者之所以倾向于使用这个框架的原因是因为它的UI优先方法。虽然这些年React
和它的整个生态已经比较成熟,但是在某些情况下你仍然会这样问自己:“正确的做法到底是什么?”
这是一个好问题--并不总是有一个通用的“正确”的做事方式。事实上,正如你所知,最佳实践并不总是那么的适用。长远来看,其中的某些写法可能会影响性能,可读性,并导致效率低下。
在本文中,我将描述在React
开发中5种被普遍接受的写法,但是实际上你是可以避免这样做的。当然,我将解释为什么我认为这些做法是可以避免的,并且我会建议你用其他的写法来完成相同的事情。
从一开始就优化React
React
的开发人员在如何使React
更快投入了大量的精力,每次迭代新的优化方法又会被添加进去。在我看来,你不应该花费时间优化这些东西,除非你发现了真正的性能影响。
为什么?
React
相比其它的前端平台更容易扩展,因为你不必重写某个模块来加快应用的速度。导致性能问题的罪魁祸首通常是React
使用更新虚拟DOM
的reconciliation
过程。
让我们看一下React
是如何处理下面的事情的。在每个render()
中,React
生成了一个由UI元素组成的树——子节点是实际的DOM
元素。当state
或者props
更新的时候,React需要使用最小的改变次数来生成一个新的树,并保持可预测性。
想象一下,你看到的树是这个样子的:
想象一下,你的应用接收到新的数据,下列的节点需要被更新:
React
通常会重新渲染整个子树,而不是像这样仅仅渲染相关节点:
当顶层组件的state
发生改变的时候,它的子元素都会重新渲染。这是默认的行为,在小型应用中不需要担心。随着应用的逐渐扩展,您应该考虑使用Chrome Profiling
工具来测量实际性能。这个工具将为你提供在不必要的渲染上所浪费时间的精确数据。如果这些数据很重要,你可以通过向你的组件中添加shouldComponentUpdate
钩子来优化你的渲染时间。
这个钩子在重新渲染开始之前将被触发,默认返回true
:
shouldComponentUpdate(nextProps, nextState) {
return true;
}
当返回true
时,React
的diff
算法接管并重新渲染整个子树。你能避免这样的情况发生,通过在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
这样的库来实现更加简单的SSR
。Next
将节省你在设置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的看法和最佳做法是什么?在评论中分享它们。(想看评论直接去原文看吧)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。