编写简洁的React代码的14条技巧。 第一部分

编写干净的代码是一项技能,在程序员的职业生涯的某个阶段成为必需。 当程序员试图找到他的第一份工作时,这项技能尤其重要。 从本质上讲,这就是使开发人员成为团队合作者的原因,并且可以“填充”面试或帮助他成功通过。 雇主在做出人事决定时,会查看其潜在雇员编写的代码。 程序员不仅应该理解程序员编写的代码,还应该理解人们。



该材料是我们今天发布的翻译的第一部分,提供了为React应用程序编写简洁代码的技巧。 这些技巧的相关性越高,则应用其中所述原则的项目规模越大。 在小型项目中,您可能无需应用这些原理就可以做到。 在确定每种特定情况下需要什么时,值得以常识为指导。

1.变形特性


破坏属性(在React English术语中称为“ props”)是使代码更整洁并提高其支持能力的好方法。 事实是,这使您可以清楚地表达或声明实体使用的内容(例如React组件)。 但是,此方法不会强迫开发人员深入研究组件的实现,以找出与之关联的属性的组成。

解构属性还允许程序员设置其默认值。 这很常见:

import React from 'react' import Button from 'components/Button' const MyComponent = ({ placeholder = '', style, ...otherProps }) => {   return (     <Button       type="button"       style={{         border: `1px solid ${placeholder ? 'salmon' : '#333'}`,         ...style,       }}       {...otherProps}     >       Click Me     </Button>   ) } export default MyComponent 

我能够找到的在JavaScript中使用解构的最令人愉快的结果之一是,它允许您支持各种参数选项。

例如,我们有一个函数authenticate ,该函数将用于认证用户的token作为参数。 后来有必要使其接受jwt_token实体。 这种需求是由服务器响应的结构更改引起的。 由于使用了解构,因此您可以轻松组织对这两个参数的支持,而不必处理更改大多数功能代码的需要:

 //   async function authenticate({ user_id, token }) {  try {    const response = await axios.post('https://someapi.com/v1/auth/', {      user_id,      token,    })    console.log(response)    return response.data  } catch (error) {    console.error(error)    throw error  } } //   async function authenticate({ user_id, jwt_token, token = jwt_token }) {  try {    const response = await axios.post('https://someapi.com/v1/auth/', {      user_id,      token,    })    console.log(response)    return response.data  } catch (error) {    console.error(error)    throw error  } } 

当代码到达token时,将评估jwt_token 。 结果,如果jwt_token证明是有效的令牌,并且token实体证明是undefined ,则jwt_token值将落入token 。 如果在token中已经存在一些不被JS规则错误的值(即某个真实令牌),那么在token中将仅仅是已经存在的值。

2.将组件文件放在经过深思熟虑的文件夹结构中


看一下以下目录结构:

  • src

    • 组成部分
    • Breadcrumb.js
    • CollapsedSeparator.js
  • 输入值

    • index.js
    • Input.js
    • utils.js
    • focusManager.js


    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typography.js

面包屑可能包含分隔符。 CollapsedSeparator组件导入到Breadcrumb.js文件中。 这使我们知道,在所讨论的项目的实施中它们是相互联系的。 但是,不拥有此信息的人可能会建议BreadcrumbCollapsedSeparator是一对完全独立的组件,它们不以任何方式相互连接。 特别是-如果CollapsedSeparator没有明确迹象表明该组件与Breadcrumb组件相关联。 例如,在这些符号中,组件名称中可能使用了Breadcrumb前缀,这可以将名称转换为类似BreadcrumbCollapsedSeparator.js的名称。

由于我们知道BreadcrumbCollapsedSeparator是相互关联的,因此我们可能想知道为什么它们没有放置在单独的文件夹中,例如InputCard 。 通过这样做,我们可以开始对为什么项目材料具有这种结构进行各种假设。 假设您在这里可以考虑将这些组件放置在项目的顶层,以帮助他们快速找到这些组件,并照顾那些将与该项目一起工作的人员。 结果,对于新开发人员而言,项目各部分之间的关​​系看起来相当模糊。 使用干净的代码编写技术应具有完全相反的效果。 关键是,由于有了他们,新开发人员才有机会阅读别人的代码并立即掌握情况的本质。

如果在示例中使用经过深思熟虑的目录结构,则会得到如下所示的内容:

  • src

    • 组成部分
  • 面包屑

    • index.js
    • Breadcrumb.js
    • CollapsedSeparator.js
  • 输入值

    • index.js
    • Input.js
    • utils.js
    • focusManager.js


    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typography.js

现在,与创建Breadcrumb组件相关联的组件数量无关紧要。 只要它们的文件与Breadcrumb.js位于同一目录中,我们就会知道它们与Breadcrumb组件相关联:

  • src

    • 组成部分
  • 面包屑

    • index.js
    • Breadcrumb.js
    • CollapsedSeparator.js
    • Expander.js
    • BreadcrumbText.js
    • Breadcrumbhothotog.js
    • Breadcrumbfishes.js
    • Breadcrumbleftft.js
    • Breadcrumbhead.js
    • Breadcrumbaddict.js
    • Breadcrumbdragon0814.js
    • Breadcrumbcontext.js
  • 输入值

    • index.js
    • Input.js
    • utils.js
    • focusManager.js


    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typography.js

这是在代码中使用类似结构的方式:

 import React from 'react' import Breadcrumb, {  CollapsedSeparator,  Expander,  BreadcrumbText,  BreadcrumbHotdog,  BreadcrumbFishes,  BreadcrumbLeftOvers,  BreadcrumbHead,  BreadcrumbAddict,  BreadcrumbDragon0814, } from '../../../../../../../../../../components/Breadcrumb' const withBreadcrumbHotdog = (WrappedComponent) => (props) => (  <WrappedComponent BreadcrumbHotdog={BreadcrumbHotdog} {...props} /> ) const WorldOfBreadcrumbs = ({  BreadcrumbHotdog: BreadcrumbHotdogComponent, }) => {  const [hasFishes, setHasFishes] = React.useState(false)  return (    <BreadcrumbDragon0814      hasFishes={hasFishes}      render={(results) => (        <BreadcrumbFishes>          {({ breadcrumbFishes }) => (            <BreadcrumbLeftOvers.Provider>              <BreadcrumbHotdogComponent>                <Expander>                  <BreadcrumbText>                    <BreadcrumbAddict>                      <pre>                        <code>{JSON.stringify(results, null, 2)}</code>                      </pre>                    </BreadcrumbAddict>                  </BreadcrumbText>                </Expander>                {hasFishes                  ? breadcrumbFishes.map((fish) => (                      <>                        {fish}                        <CollapsedSeparator />                      </>                    ))                  : null}              </BreadcrumbHotdogComponent>            </BreadcrumbLeftOvers.Provider>          )}        </BreadcrumbFishes>      )}    />  ) } export default withBreadcrumbHotdog(WorldOfBreadcrumbs) 

3.使用标准命名约定命名组件


在命名组件时使用某些标准可以使非项目作者轻松阅读该项目的代码。

例如, 高阶组件 (HOC)的名称通常以前缀。 许多开发人员已经习惯了以下组件名称:

 import React from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' import getDisplayName from 'utils/getDisplayName' const withFreeMoney = (WrappedComponent) => {  class WithFreeMoney extends React.Component {    giveFreeMoney() {      return 50000    }    render() {      return (        <WrappedComponent          additionalMoney={[            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),          ]}          {...this.props}        />      )    }  }  WithFreeMoney.displayName = `withFreeMoney(${getDisplayName(    WrappedComponent,  )}$)`  hoistNonReactStatics(WithFreeMoney, WrappedComponent)  return WithFreeMoney } export default withFreeMoney 

假设有人决定退出此做法并执行以下操作:

 import React from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' import getDisplayName from 'utils/getDisplayName' const useFreeMoney = (WrappedComponent) => {  class WithFreeMoney extends React.Component {    giveFreeMoney() {      return 50000    }    render() {      return (        <WrappedComponent          additionalMoney={[            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),          ]}          {...this.props}        />      )    }  }  WithFreeMoney.displayName = `useFreeMoney(${getDisplayName(    WrappedComponent,  )}$)`  hoistNonReactStatics(WithFreeMoney, WrappedComponent)  return WithFreeMoney } export default useFreeMoney 

这是功能完善的JavaScript代码。 从技术角度来说,这里的名称是正确的。 但是use前缀是习惯在其他情况下使用的,即在命名React hooks时 。 因此,如果某人编写了计划展示给其他人的程序,则应注意实体的名称。 对于某些人要求查看其代码并帮助他解决问题的情况,尤其如此。 事实是,很可能读取某人代码的人已经习惯了某种实体命名方案。

与普遍接受的标准的差异使得难以理解他人的代码。

4.避免布尔陷阱


如果某些输出取决于某些原始逻辑值,并且根据对这些值的分析做出某些决定,则程序员应格外谨慎。 这暗示了代码质量差。 这迫使开发人员阅读用于实现组件或其他机制的代码,以准确了解这些机制的确切结果。

假设我们创建了一个Typography组件,它可以接受以下选项: 'h1''h2''h3''h4''h5 ', 'h6''title''subheading'

如果以以下形式将选项传递给组件,它将对组件的输出产生什么确切的影响?

 const App = () => (  <Typography color="primary" align="center" subheading title>    Welcome to my bio  </Typography> ) 

那些对React(或者说JavaScript)有经验的人可能已经假设title选项将由于系统的工作方式而subheading选项。 最后一个选项将覆盖第一个。

但是这里的问题是,如果不研究代码,我们将无法确切说明title选项或subheading选项将应用到什么程度。

例如:

 .title {  font-size: 1.2rem;  font-weight: 500;  text-transform: uppercase; } .subheading {  font-size: 1.1rem;  font-weight: 400;  text-transform: none !important; } 

即使title获胜,CSS text-transform: uppercase规则也不适用。 这是由于text-transform: none !important的更高的特异性text-transform: none !importantsubheading中存在text-transform: none !important规则。 如果您在这种情况下不谨慎,则调试此类样式错误可能会变得非常困难。 特别是-如果代码在控制台中未显示某些警告或错误消息。 这会使组件的签名复杂化。

解决此问题的一种可能的方法是使用Typography版组件的更干净的版本:

 const App = () => <Typography variant="title">Welcome to my bio</Typography> 

这是Typography组件代码:

 import React from 'react' import cx from 'classnames' import styles from './styles.css' const Typography = ({  children,  color = '#333',  align = 'left',  variant,  ...otherProps }) => {  return (    <div      className={cx({        [styles.h1]: variant === 'h1',        [styles.h2]: variant === 'h2',        [styles.h3]: variant === 'h3',        [styles.h4]: variant === 'h4',        [styles.h5]: variant === 'h5',        [styles.h6]: variant === 'h6',        [styles.title]: variant === 'title',        [styles.subheading]: variant === 'subheading',      })}    >      {children}    </div>  ) } 

现在,当在App组件中传递给Typography组件variant="title" ,可以确定只有title会影响该组件的输出。 这使我们不必分析组件代码即可了解该组件的外观。

要使用属性,可以使用简单的if/else

 let result if (variant === 'h1') result = styles.h1 else if (variant === 'h2') result = styles.h2 else if (variant === 'h3') result = styles.h3 else if (variant === 'h4') result = styles.h4 else if (variant === 'h5') result = styles.h5 else if (variant === 'h6') result = styles.h6 else if (variant === 'title') result = styles.title else if (variant === 'subheading') result = styles.subheading 

但是这种方法的主要优势在于,您可以简单地使用以下简洁的单行设计并结束它:

 const result = styles[variant] 

5.使用箭头功能


箭头函数代表在JavaScript中声明函数的简洁明了的机制(在这种情况下,谈论箭头函数相对于函数表达式的优势会更正确)。

但是,在某些情况下,开发人员不使用箭头函数代替函数表达式。 例如,当有必要组织职能提升时。

React以类似的方式使用这些概念。 但是,如果程序员对提高函数不感兴趣,那么在我看来,使用箭头函数的语法是有意义的:

 //     function Gallery({ title, images = [], ...otherProps }) {  return (    <CarouselContext.Provider>      <Carousel>        {images.map((src, index) => (          <img align="center" src={src} key={`img_${index}`} />        ))}      </Carousel>    </CarouselContext.Provider>  ) } //         const Gallery = ({ title, images = [], ...otherProps }) => (  <CarouselContext.Provider>    <Carousel>      {images.map((src, index) => (        <img align="center" src={src} key={`img_${index}`} />      ))}    </Carousel>  </CarouselContext.Provider> ) 

应该注意的是,在分析此示例时,很难看到箭头功能的优势。 当涉及到简单的单行设计时,它们的美就得到了充分体现:

 //     function GalleryPage(props) {  return <Gallery {...props} /> } //         const GalleryPage = (props) => <Gallery {...props} /> 

我相信这种单线设计将吸引所有人。

6.将独立功能放在自己的钩子之外


我已经看到一些程序员是如何在自己的钩子中声明函数的,但是这些钩子并不需要这些函数。 这种稍微“夸大”挂钩代码并使它的阅读复杂化。 由于以下事实导致阅读困难,因为它的读者可能会开始询问有关钩子是否真正取决于其内部功能的问题。 如果不是这种情况,最好将功能移到挂钩之外。 这将使代码阅读器清楚了解钩子所依赖的内容以及不依赖的内容。

这是一个例子:

 import React from 'react' const initialState = {  initiated: false,  images: [], } const reducer = (state, action) => {  switch (action.type) {    case 'initiated':      return { ...state, initiated: true }    case 'set-images':      return { ...state, images: action.images }    default:      return state  } } const usePhotosList = ({ imagesList = [] }) => {  const [state, dispatch] = React.useReducer(reducer, initialState)  const removeFalseyImages = (images = []) =>    images.reduce((acc, img) => (img ? [...acc, img] : acc), [])  React.useEffect(() => {    const images = removeFalseyImages(imagesList)    dispatch({ type: 'initiated' })    dispatch({ type: 'set-images', images })  }, [])  return {    ...state,  } } export default usePhotosList 

如果我们分析这段代码,我们可以了解到removeFalseyImages函数实际上并不是必须存在于钩子内部;它不会与其状态交互,这意味着可以将其放置在钩子外部并且可以从钩子中调用而没有任何问题。

7.编写代码时要保持一致


对于使用JavaScript进行编程的人员,通常建议使用一致的代码编写方法。

对于React,值得关注使用以下设计的一致方法:

  1. 进出口团队。
  2. 组件,挂钩,高级组件,类的命名。

在导入和导出组件时,有时会使用类似于以下内容的东西:

 import App from './App' export { default as Breadcrumb } from './Breadcrumb' export default App 

但是我也喜欢语法:

 export { default } from './App' export { default as Breadcrumb } from './Breadcrumb' 

无论程序员选择什么,他都应该在他创建的每个项目中始终使用它。 这简化了该程序员的工作,并简化了其他人对其代码的阅读。

遵守实体的命名约定非常重要。

例如,如果某人给该钩子命名为useApp ,则其他钩子的名称以类似的方式构造-使用use前缀,这一点很重要。 例如,使用此方法的另一个钩子的名称可能类似于useController

如果您不遵守此规则,那么最后一个项目的代码可能会变成这样:

 //  #1 const useApp = ({ data: dataProp = null }) => {  const [data, setData] = React.useState(dataProp)  React.useEffect(() => {    setData(data)  }, [])  return {    data,  } } //  #2 const basicController = ({ device: deviceProp }) => {  const [device, setDevice] = React.useState(deviceProp)  React.useEffect(() => {    if (!device && deviceProp) {      setDevice(deviceProp === 'mobile' ? 'mobile' : 'desktop')    }  }, [deviceProp])  return {    device,  } } 

这些钩子的导入如下所示:

 import React from 'react' import useApp from './useApp' import basicController from './basicController' const App = () => {  const app = useApp()  const controller = basicController()  return (    <div>      {controller.errors.map((errorMsg) => (        <div>{errorMsg}</div>      ))}    </div>  ) } export default App 

乍看之下, basicController是一个钩子(与useApp相同)是完全不明显的。 这迫使开发人员阅读其导入内容的实现代码。 这样做仅是为了了解开发人员正在处理的内容。 如果我们始终遵循相同的实体命名策略,那么就不会出现这种情况。 一切一目了然:

 const app = useApp() const controller = useBasicController() 

待续...

亲爱的读者们! 您如何在React项目中处理实体命名?

Source: https://habr.com/ru/post/zh-CN465811/


All Articles