在将来会破坏你的应用程序的8个实践
找我中等
由于几个原因,我们很多人都爱上了反应库。创建复杂的交互式用户界面非常容易。最重要的是能够在不破坏其他组成组件的情况下将组件组合在另一组件之上。
令人惊讶的是,即使像Facebook,Instagram和Pinterest这样的社交媒体巨头也大量使用它们,同时利用谷歌地图等巨大的API创建无缝的用户体验。
如果您当前正在使用react构建应用程序或考虑对即将到来的项目使用react,那么本教程适合您。我希望本教程能够通过展示一些你应该三思而后行的代码实现来帮助你完成反应应用程序。
不用多说,这里有8个将会破坏你未来应用程序的实践:
1.声明超出Null的默认参数
我在之前的一篇文章中提到了这个话题,但这是令人毛骨悚然的“陷阱”之一,可以在阴沉的星期五欺骗一个粗心的开发人员毕竟,应用程序崩盘不是一个笑话 – 如果处理不当,任何类型的崩盘都可能导致任何时间点的资金损失。
我曾经花了很多时间调试类似这样的东西:
const SomeComponent = ({ items = (), todaysDate, tomorrowsDate }) => { const (someState, setSomeState) = useState(null) return ( <div> <h2>Today is {todaysDate}h2> <small>And tomorrow is {tomorrowsDate}small> <hr /> {items.map((item, index) => ( <span key={`item_${index}`}>{item.email}span> ))} div> ) } const App = ({ dates, ...otherProps }) => { let items if (dates) { items = dates ? dates.map((d) => new Date(d).toLocaleDateString()) : null } return ( <div> <SomeComponent {...otherProps} items={items} /> div> ) }
在我们的App组件中,如果日期最终为false,则将使用null初始化。
如果你像我一样,我们的直觉告诉我们,如果项目是假的,那么默认情况下应该将项目初始化为空数组。但是当日期为假时我们的应用程序会崩盘,因为items为null。什么?
如果没有传递值或未定义,则默认函数参数允许使用默认值初始化命名参数
在我们的例子中,即使null是假的,它仍然是一个值
因此,下次将默认值设置为null时,请确保在执行此操作时三思而后行。如果值是预期的值类型,您只需将值初始化为空数组即可。
2.用方括号抓取属性
有时,抓取属性的方式可能会影响应用程序的行为。如果您想知道这种行为是什么,那就是应用程序崩盘了。以下是使用方括号执行对象查找的示例:
const someFunction = function() { return { names: ('bob', 'joe'), foods: ('apple', 'pineapple'), } } const obj = someFunction() const names = obj('names') console.log(names) // result: ('bob', 'joe')
这些实际上是100%有效的用例,除了比对象键查找慢之外,它们没有什么问题。
无论如何,真正的问题开始蔓延到您的应用程序越深入查找:
const someFunction = function() { return { names: ('bob', 'joe'), foods: ('apple', 'pineapple'), } } const obj = someFunction() const names = obj('names') console.log(names) // result: ('bob', 'joe') console.log(names.joe) // result: undefined
然而,如果没有现实世界的例子,解释这种做法的严重性有点困难。所以我要提出一个真实世界的例子。我要向您展示的代码示例来自于今天8个月后的存储库。为了保护这些代码所产生的一些隐私,我几乎将所有变量重命名,但代码设计,语法和体系结构保持完全相同:
import { createSelector } from 'reselect' // supports passing in the whole obj or just the string to correct the video type const fixVideoTypeNaming = (videoType) => { let video = videoType // If video is a video object if (video && typeof video === 'object') { const media = { ...video } video = media.videoType } // If video is the actual videoType string if (typeof video === 'string') { // fix the typo because brian is an idiot if (video === 'mp3') { video = 'mp4' } } return video } /* ------------------------------------------------------- ---- Pre-selectors -------------------------------------------------------- */ export const getOverallSelector = (state) => state.app(fixVideoTypeNaming(state.app.media.video.videoType)).options.total .overall export const getSpecificWeekSelector = (state, props) => state.app(fixVideoTypeNaming(state.app.media.video.videoType)).options.weekly( props.date ) /* ------------------------------------------------------- ---- Selectors -------------------------------------------------------- */ export const getWeeklyCycleSelector = createSelector( getSpecificWeekSelector, (weekCycle) => weekCycle || null, ) export const getFetchingTotalStatusSelector = createSelector( (state) => state.app(fixVideoTypeNaming(state.app.media.video.videoType)).options.total .fetching, (fetching) => fetching, ) export const getFetchErrorSelector = createSelector( (state) => state.app(fixVideoTypeNaming(state.app.media.video.videoType)).options.total .fetchError, (fetchError) => fetchError, )
fixVideoTypeNaming是一个函数,它将根据作为参数传入的值提取视频类型。如果参数是视频对象,它将从.videoType属性中提取视频类型。如果它是一个字符串,那么调用者传入了videoType,所以我们可以跳过第一步。有人发现videoType .mp4属性在应用程序的几个区域中被拼错了。为了快速解决问题,使用了fixVideoTypeNaming来修补这个错误。
现在你可能已经猜到了,应用程序是用redux构建的(因此语法)。
要使用这些选择器,您可以导入它们以在连接更高阶的组件中使用,以附加组件以侦听该状态的片段。
const withTotalCount = (WrappedComponent) => { class WithTotalCountContainer extends React.Component { componentDidMount = () => { const { total, dispatch } = this.props if (total == null) { dispatch(fetchTotalVideoTypeCount()) } } render() { return <WrappedComponent {...this.props} /> } } WithTotalCountContainer.propTypes = { fetching: PropTypes.bool.isRequired, total: PropTypes.number, fetchError: PropTypes.object, dispatch: PropTypes.func.isRequired, } WithTotalCountContainer.displayName = `withTotalCount(${getDisplayName( WrappedComponent, )})` return connect((state) => { const videoType = fixVideoTypeNaming(state.app.media.video.videoType) const { fetching, total, fetchError } = state.app.media.video( videoType ).options.total return { fetching, total, fetchError } })(WithTotalCountContainer) }
UI组件:
const TotalVideoCount = ({ classes, total, fetching, fetchError }) => { if (fetching) return <LoadingSpinner /> const hasResults = !!total const noResults = fetched && !total const errorOccurred = !!fetchError return ( <Typography variant="h3" className={classes.root} error={!!fetched && !!fetchError} primary={hasResults} soft={noResults || errorOccurred} center > {noResults && 'No Results'} {hasResults && `$${formatTotal(total)}`} {errorOccurred && 'An error occurred.'} Typography> ) }
该组件接收HOC传递给它的所有道具,并根据从道具提供的数据调整的条件显示信息。在一个完美的世界里,这很好。在一个不完美的世界里,这暂时会好起来的。
如果我们回到容器并查看选择器选择其值的方式,我们实际上可能已经设置了一个滴答作响的定时炸弹,等待一个开放的攻击机会:
export const getOverallSelector = (state) => state.app(fixVideoTypeNaming(state.app.media.video.videoType)).options.total .overall export const getSpecificWeekSelector = (state, props) => state.app(fixVideoTypeNaming(state.app.media.video.videoType)).options.weekly( props.date )
在开发任何类型的应用程序时,在开发流程中确保更高置信度和减少错误的常见做法是在中间执行测试以确保应用程序按预期工作。
但是,对于这些代码段,如果未经过测试,如果不及早处理,应用程序将在以后崩盘。
例如,state.app.media.video.videoType是链中的四个级别。如果另一位开发人员在被要求修复应用程序的另一部分并且state.app.media.video变得未定义时意外犯了错误怎么办?该应用程序将崩盘,因为它无法读取未定义的属性videoType。
此外,如果有一个视频类型的另一个拼写错误问题,并且fixVideoTypeNaming没有更新以适应mp3问题,那么应用程序冒着另一个无意识的崩盘风险,除非真正的用户遇到问题,否则没有人能够检测到。那时候,为时已晚。
假设应用程序永远不会遇到像这样的错误,这绝不是一个好习惯。请小心
3.渲染时不小心检查空对象
我在很久以前在有条件渲染组件的黄金时期做的事情是检查数据是否已在对象中填充使用 Object.keys
。如果有数据,那么如果条件通过,组件将继续呈现:
const SomeComponent = ({ children, items = {}, isVisible }) => ( <div> {Object.keys(items).length ? ( <DataTable items={items} /> ) : ( <h2>Data has not been receivedh2> )} div> )
让我们假装我们调用了一些API并在响应中的某个地方接收了作为对象的项目。话虽如此,一开始看起来似乎完全没问题。期望的项目类型是一个对象,因此使用Object.keys是完全没问题的。毕竟,我们确实将项目初始化为空对象作为防御机制,如果出现了一个错误,将其变为假值。
但是我们不应该相信服务器总是返回相同的结构。如果项目将来成为阵列怎么办? Object.keys(items)
不会崩盘,但会返回一个奇怪的输出,如 ("0", "1", "2")
。您认为使用该数据呈现的组件会如何反应?
但这甚至不是最糟糕的部分。片段中最糟糕的部分是,如果在道具中将项目作为空值接收,那么 items
甚至不会启动您提供的默认值
然后你的应用程序会在它开始执行任何其他操作之前崩盘:
"TypeError: Cannot convert undefined or null to object at Function.keys () at yazeyafabu.js:4:45 at https://static.jsbin.com/js/prod/runner-4.1.7.min.js:1:13924 at https://static.jsbin.com/js/prod/runner-4.1.7.min.js:1:10866 "
再次,请小心
4.在渲染之前不小心检查数组是否存在
这可能与#3非常相似,但是数组和对象经常互换使用,因此它们应该拥有自己的部分。
如果你有这样做的习惯:
render() { const { arr } = this.props return ( <div> {arr && arr.map()...} div> ) }
然后确保您至少进行单元测试,以便始终关注该代码或处理 arr
在将它传递给render方法之前正确地提前,否则app会崩盘 arr
成为一个对象字面。当然了 &&
运算符会将其视为真实并尝试.map对象文字,最终会崩盘整个应用程序。
所以请记住这一点。为更大的问题节省您的精力和挫折,值得您特别关注 ;)
5.不使用Linter
如果您在开发应用程序时没有使用任何类型的linter,或者您根本不知道它们是什么,请允许我详细说明它们在开发中的用途。
我用来帮助我开发流程的linter是ESLint,它是一种非常著名的JavaScript linting工具,它允许开发人员在不执行代码的情况下发现代码问题。
这个工具非常有用,它可以充当你的半导师,因为它有助于实时纠正你的错误 – 好像有人在指导你。它甚至描述了为什么你的代码可能不好并建议你应该做些什么来替换它们
这是一个例子:
关于eslint的最酷的事情是,如果你不喜欢某些规则或者不同意其中的一些规则,你可以简单地禁用某些规则,以便它们在你开发时不再显示为linting警告/错误。无论什么让你开心,对吧?
6.渲染列表时的解构
我已经看到过去发生过这种情况,并不总是很容易发现。基本上,如果你有一个项目列表,并且你要为列表中的每个项目渲染一堆组件,那么你的应用程序可能出现的错误是,如果将来有一个项目,其中一个项目在列表中不是您期望的值,如果您的应用程序不知道如何处理值类型,则可能会崩盘。
这是一个例子:
const api = { async getTotalFrogs() { return { data: { result: ( { name: 'bob the frog', tongueWidth: 50, weight: 8 }, { name: 'joe the other frog', tongueWidth: 40, weight: 5 }, { name: 'kelly the last frog', tongueWidth: 20, weight: 2 }, ), }, } }, } const getData = async ({ withTongues = false }) => { try { const response = await api.getTotalFrogs({ withTongues }) return response.data.result } catch (err) { throw err } } const DataList = (props) => { const (items, setItems) = useState(()) const (error, setError) = useState(null) React.useEffect(() => { getData({ withTongues: true }) .then(setItems) .catch(setError) }, ()) return ( <div> {Array.isArray(items) && ( <Header size="tiny" inverted> {items.map(({ name, tongueWidth, weight }) => ( <div style={{ margin: '25px 0' }}> <div>Name: {name}div> <div>Width of their tongue: {tongueWidth}cmdiv> <div>Weight: {weight}lbsdiv> div> ))} Header> )} {error && <Header>You received an error. Do you need a linter?Header>} div> ) }
代码可以完美地运行。现在,如果我们查看api调用而不是返回此:
const api = { async getTotalFrogs() { return { data: { result: ( { name: 'bob the frog', tongueWidth: 50, weight: 8 }, { name: 'joe the other frog', tongueWidth: 40, weight: 5 }, { name: 'kelly the last frog', tongueWidth: 20, weight: 2 }, ), }, } }, }
如果在api客户端发生意外情况并返回此数组时,如何以某种方式处理数据流会出现问题,该怎么办?
const api = { async getTotalFrogs() { return { data: { result: ( { name: 'bob the frog', tongueWidth: 50, weight: 8 }, undefined, { name: 'kelly the last frog', tongueWidth: 20, weight: 2 }, ), }, } }, }
您的应用程序将崩盘,因为它不知道如何处理:
Uncaught TypeError: Cannot read property 'name' of undefined at eval (DataList.js? (sm):65) at Array.map (<anonymous>) at DataList (DataList.js? (sm):64) at renderWithHooks (react-dom.development.js:12938) at updateFunctionComponent (react-dom.development.js:14627)
因此,为了防止您的应用程序崩盘,您可以在每次迭代时设置一个默认对象:
{ items.map(({ name, tongueWidth, weight } = {}) => ( <div style={{ margin: '25px 0' }}> <div>Name: {name}div> <div>Width of their tongue: {tongueWidth}cmdiv> <div>Weight: {weight}lbsdiv> div> )) }
现在,当用户看不到页面崩盘时,您的用户将不必对您的技术和专业知识做出判断:
然而,即使应用程序不再崩盘,我建议进一步处理缺失值,例如为具有类似问题的整个项目返回null,因为它们中没有任何数据。
7.没有充分研究你要实施的内容
我过去犯过的一个重要错误是对我实施的搜索输入过于自信,在游戏中过早地信任我的观点。
这是什么意思?好吧,它不是我过于自信的搜索输入组件。该组件应该是一项简单的任务……事实确实如此。
整个搜索功能发生的问题的真正罪魁祸首是查询中包含的字符。
当我们将关键字作为查询发送到搜索API时,认为用户输入的每个密钥都是有效的并不总是足够的,即使它们出现在键盘上也是如此。
只要100%确定像这样的正则表达式可以正常工作,并避免遗漏任何可能导致应用程序崩盘的无效字符:
const hasInvalidChars = /^.*?(?=(+^#%&$*:<>?/{|}()\)()).*$/g.test( inputValue, )
该示例是最新的,为搜索API建立的正则表达式。
这是以前的情况:
const hasInvalidChars = /^.*?(?=(+^#%&$*:<>?/{|}())()).*$/g.test( inputValue, ) const callApi = async (keywords) => { try { const url = `https://someapi.com/v1/search/?keywords=${keywords}/` return api.searchStuff(url) } catch (error) { throw error } }
你可以看到斜线 /
缺少,这导致应用程序崩盘如果该字符最终通过网络发送到API,请猜猜API认为URL将是什么?
此外,我不会完全信任您在互联网上找到的示例。其中很多都不是经过完全测试的解决方案,而且在正则表达式方面,大多数用例并没有真正的标准。
7.不限制文件输入的大小
限制用户选择的文件大小是一种很好的做法,因为大多数情况下,如果可以以某种方式压缩文件而不会丢失任何明显降低质量的迹象,那么您实际上并不需要一个非常大的文件。
但是有一个更重要的原因,即将尺寸限制到一定限度是一种很好的做法。在我的公司,我们注意到过去的用户偶尔会在上传图片时“冻结”。并非每个人都拥有Alienware 17 R5,因此您必须考虑用户的某些情况。
以下是将文件限制为5 MB(5,000,000字节)限制的示例:
import React, { useState, useEffect } from 'react' const useUploadStuff = () => { const (files, setFiles) = useState(()) // Limit the file sizes here const onChange = (e) => { const arrFiles = Array.from(e.target.files) const filesUnder5mb = arrFiles.filter((file) => { const bytesLimit = 5000000 if (file.size > bytesLimit) { // optionally process some UX about this file size } return file.size < bytesLimit }) setFiles(filesUnder5mb) } useEffect(() => { if (files.length) { // do something with files } }, (files)) return { files, onChange, } } const UploadStuff = () => { const { onChange } = useUploadStuff() return ( <div> <h2 style={{ color: '#fff' }}>Hih2> <div> <input style={{ color: '#fff' }} onChange={onChange} type="file" placeholder="Upload Stuff" multiple /> div> div> ) } export default UploadStuff
你不希望用户在上传文件时上传视频游戏
结论
结束这篇文章的结尾
将会有第2部分,因为我只获得了一半的名单(哎呀)
无论如何,感谢您阅读并确保关注我的未来更新七月四日快乐
找我中等