必须避免的 10 个 React 错误

这篇文章是作者实际工作经验的总结,以及一些错误使用react的方法,希望能帮助大家改掉这些同样的错误。
1.Props 透传
props传递是将单个道具从父组件向下传递到多个层的做法。理想情况下,道具不应超过两层。当我们选择多层交付时,会造成一些性能问题,这也让React官方很头疼。通过道具可能会导致不必要的重新渲染。因为当 props 改变时,React 组件总是会被重新渲染,而那些不需要 props 而只提供传递函数的中间层组件会被渲染。除了性能问题,props透传会导致数据难以追踪,对于很多想看懂代码的人来说也是一个很大的挑战。
const A = () => {
const [title, setTitle] = useState('')
return <B title={title} />
}
const B = ({ title }) => {
return <C title={title} />
}
const C = ({ title }) => {
return <D title={title} />
}
const D = ({ title }) => {
return <div>{title}</div>
}有很多方法可以解决这个问题,比如 React Context Hook,或者像 Redux 这样的库。但是使用 Redux 需要一些额外的编码,它更适合单一状态改变很多事情的复杂场景。使用 Context Hook 的简单项目选择是更好的选择。
2.导入比实际使用更多的代码
React 是一个前端框架,它有很多代码量。当我们编写 React 程序时,我们应该避免导入许多未使用的模块。因为它们也会被打包成运行时代码,发送给用户的客户端/浏览器/移动设备。额外的依赖会导致应用体积膨胀,增加用户的加载时间,拖慢网页速度,降低用户体验。
import _ from 'lodash' // entire package import
import _map from 'lodash/map' // only import required packages为了保证良好的用户体验,我们应该将FCP保持在1.8秒以内,所以我们需要简化代码量。现代打包工具具有 tree-shaking 功能,使用各种方法来缩小和压缩我们用于生产的代码,例如 webpack。但在某些情况下它并不能很好地去除无用代码,最好知道应该打包哪些代码,而不是依赖打包工具来尝试修复我们的代码问题。当前的 JavaScript 经历了多次重大更新,并具有许多新特性。
以前我们需要借助lodash等库来实现这些功能,但是现在lodash的优势正在慢慢减弱。在 youmightnotneed.com/lodash/ 上查看如何用现代 JavaScript 替换 lodash。当然,这取决于您的用户使用的浏览器和 JavaScript 版本。但是我们大多数人使用 babel 或类似的转译器来处理这个问题。现在几乎每个人都在使用 Chrome,对吧?其他图书馆也是如此。
3.不要将业务逻辑与组件逻辑分开
过去,很多人认为 React 组件应该包含逻辑,逻辑是组件的一部分。但放在今天来看,这种观点是有问题的。
const Example = () => {
const [data, setData] = useState([])
useEffect(() => {
fetch('...')
.then(res => res.json())
.then(data => {
const filteredData = data.filter(item => item.status === ture)
setData(filteredData)
})
}, [])
return <div>...</div>
}将组件和逻辑放在一起使得组件变得复杂,在修改或添加业务逻辑时,对开发人员来说更加复杂,理解整个过程也更具挑战性。
const Example = () => {
const { data, error } = useData()
return <div>...</div>
}分离组件和逻辑有两个好处:
- 专注于分离点。
- 重用业务逻辑。
4. 每次渲染重复工作
即使您是经验丰富的 React 老手,您可能仍然对渲染一无所知。渲染很频繁,而且常常出乎意料。这是使用 React 编写组件的核心原则之一,在编写 React 组件时应牢记于心。这也意味着当组件被渲染时,一些逻辑将被重新执行。React 提供了两个 Hook,useMemo 和 useCallback。如果使用得当,这些 Hooks 可以缓存计算结果或函数,减少不必要的重复渲染,最终提升性能。
import React, { useMemo } from 'react'
const MemoExample = ({ items, filter }) => {
const filteredItems = useMemo(() => {
return items.filter(filter )
}, [filter, items])
return filteredItems.map(item => <p>{item}</p>)
}上面的例子是一个item列表的展示,需要通过一定的条件过滤,最后展示给用户。这种数据过滤在前端是不可避免的,所以我们可以使用useMemo来缓存过滤数据的过程,这样只有当items和filter发生变化时才会重新渲染。
5、useEffect使用不当
useEffect 是 React 中最常用的 Hooks 之一。在class组件时代,componentDidMount是一个通用的生命周期函数,用来做一些数据请求,事件绑定等,在Hooks时代,useEffect已经取代了它。但是不正确地使用 useEffect 最终可能会创建多个事件绑定。以下是错误的用法。
import React, { useMemo } from 'react'
const useEffectBadExample = () => {
useEffect(() => {
const clickHandler = e => console.log('e:', e)
document.getElementById('btn').addEventListener('click', clickHandler)
})
return <button id="btn">click me</button>
}正确的做法是:
- useEffect 的回调函数应该返回一个解除绑定的函数。
- useEffect 应该提供第二个参数,它是一个空数组,并且保证只运行一次。
import React, { useMemo } from 'react'
const UseEffectBadExample = () => {
useEffect(() => {
const clickHandler = e => console.log('e:', e)
document.getElementById('btn').addEventListener('click', clickHandler)
return () => document.getElementById('btn').removeEventListener('click', clickHandler)
}, [])
return <button id="btn">click me</button>
}6、useState使用不当
useState 也是 React 中使用最多的两个 Hooks 之一。但是让很多人感到困惑的是,useState 可能并没有达到预期的效果。比如一个图片压缩组件:
function Compress() {
const [files, setFiles] = useState([])
const handleChange = (newFiles) => {
api(newFiles).then((res)=>{
const cloneFiles = [...files]// file here is always []
cloneFiles. map(
// some logic...
)
setFiles(cloneFiles)
})
}
return <input type="upload" multiple onChange={handleChange}/>
}应该改为:
function Compress() {
const [files, setFiles] = useState([])
const handleChange = (newFiles) => {
api(newFiles).then((res)=>{
setFiles((oldFiles) => {
const cloneFiles = [...files]//The file here is the latest
return cloneFiles. map(
// some logic...
)
})
})
}
return <input type="upload" multiple onChange={handleChange}/>
}原因是该函数基于当前闭包使用的状态。但是状态更新后,会触发渲染并创建新的上下文,而不会影响之前的闭包。因此,要使程序按预期执行,必须使用以下语法:
setFiles(oldFiles => [...oldFiles, ...res.data])7.布尔运算符的错误使用
很多时候我们会使用布尔值来控制页面上某些元素的渲染,这是很正常的事情。还有一些其他方法可以处理此逻辑,最常见的是 && 运算符,这也完全是 JavaScript 的一项功能,但有时会产生意想不到的后果。
const total = 0
const Component = () => total && `Total number of items: ${total}`当我们需要显示商品数量时,如果数量为0,则只显示0,而不是商品总数:0。原因是JavaScript对待0,所以最好不要依赖JavaScript的boolean true-false比较。正确的做法如下:
const total = 0
const Component = () => {
const hasItem = total > 0
return hasItem && `Total number of items: ${total}`
}8.处处使用三元表达式进行条件渲染
三元表达式是一种非常简洁的语法,在短代码中非常令人满意。所以很多人喜欢在 React 中使用三元表达式来渲染组件。但它的问题是难以扩展。最简单的三元表达式没有问题,但是多个三元表达式一旦组合在一起,就形成了一个非常大的组件,难以阅读。
import React, { useMemo } from 'react'
const VIPExample = ({ vipLevel }) => {
return (<div>
membership system
{vipLevel === 0 ? (
<button>Activate VIP</button>
) : vipLevel === 1 ? (
<p>Dear Bronze VIP, you have 3 privileges:...</p>
) : vipLevel === 2 ? (
<p>...</p>
) : <p>...</p>}
</div>)
}这种代码没有功能性错误,但可读性很差。有两种方法可以解决。第一种是使用条件判断代替三元表达式。
import React, { useMemo } from 'react'
const VIPDetail = (vipLevel) => {
if(vipLevel === 0) return <button>Activate VIP</button>
if(vipLevel === 1) return <p>Dear Bronze VIP, you have 3 privileges:...</p>
//...
}
const VIPExample = ({ vipLevel }) => {
return (<div>
membership system
{VIPDetail(vipLevel)}
</div>)
}如果每个分支中的组件比较复杂,我们更进一步,我们使用抽象来封装组件。
import React, { useMemo } from 'react'
const VIPZeroDetail = ({ vipLevel }) => {
if(vipLevel !== 0) return null
return <button>Activate VIP</button>
}
const VIPOneDetail = ({ vipLevel }) => {
if(vipLevel !== 1) return null
return <p>Dear Bronze VIP, you have 3 privileges:...</p>
}
//...
const VIP = ({ vipLevel }) => {
return <>
<VIPZeroDetail vipLevel={vipLevel} />
<VIPOneDetail vipLevel={vipLevel} />
<!-->...<-->
</>
}
const VIPExample = ({ vipLevel }) => {
return (<div>
membership system
<VIP vipLevel={vipLevel} />
</div>)
}在大多数情况下,使用条件判断的方法就足够了。使用抽象封装组件的方法有一个缺点,就是组件过于分散,同步逻辑比较麻烦。
9. 不要定义 propTypes 或解构 props
React 中的大部分内容与 JavaScript 中的几乎相同。React props 在 JavaScript 中也只是对象,这意味着我们可以在对象中传递许多不同的值,组件很难知道它们。这使得组件使用 props 更加麻烦。许多人喜欢通过这种方式访问道具。
const Example = (props) => {
return <div>
<h1>{props.title}</h1>
<p>{props.content}</p>
</div>
}无需使用 TypeScript 或定义 propsTypes,我们可以自由使用 props.xxx 来访问 props。为了解决这个问题,我们可以选择使用 TypeScript 来声明组件 props 的类型。如果您不使用 TypeScript,则可以使用 propTypes。同时,建议以破坏性的方式使用道具。
const Example = ({ title, content }) => {
return <div>
<h1>{title}</h1>
<p>{content}</p>
</div>
}
Example.propTypes = {
title: PropTypes.string.isRequired,
content: PropTypes.string.isRequired
}这样一来,我们就可以一目了然地看到组件需要哪些 props。当我们尝试访问 props 上不存在的属性时,我们会收到警告。
10.大型应用程序没有代码拆分
大型应用程序意味着大量组件。这时候我们就应该使用代码拆分,将应用拆分成多个js文件,在文件使用的时候加载。这使应用程序的初始包大小保持较小,并允许用户更快地启动网页。react-loadable 是专门处理这个问题的第三方库。使用它,我们可以很好的拆分组件。
import Loadable from 'react-loadable'
import Loading from 'loading'
const LoadableComponent = Loadable({
loader: () => import('./component'),
loading: Loading
})
export default () => <LoadableComponent />总结
React 为我们提供了强大的开发生态系统和开发工具集,我们可以比以往更轻松地创建 Web 应用程序。然而,它是一套工具,工具可能会被滥用。只有按预期使用工具,并以 JavaScript 优先的方式,才能让我们创建更干净、更强大、性能更高的代码。
作为开发者,不断完善自己的代码,让用户用起来舒服,让其他开发者读起来舒服,是我们应该努力的方向和目标。我的10条建议可以作为你用好React的一个起点,希望能帮助你避免很多在开发过程中容易出现的错误。
翻译来源:https://medium.com/@Choco23/10-react-mistakes-you-must-avoid-6b5102e4c1e1
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!