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

今天,我们将发布有关开发React应用程序时编写干净代码的材料的第二部分。 这里有一些有用的提示。



阅读第一部分

8.将重复的元素转换为组件


将重复元素转换为适合重用的组件可以称为此类元素的“组件化”。

每个开发人员都有自己编写重复的React代码的原因。 这可能是蓄意采取的措施,也可能是意外事故。

无论应用程序中出现相同代码片段的原因是什么,程序员都应该考虑如何改善这种情况。

例如,如果某人没有摆脱重复的习惯,那么它们很可能会一次又一次地出现在他的项目中。 这个人是什么样的团队成员? 它只会使同事的未来生活变得复杂,当他们遇到重复的代码时,他们会感到困惑。 如果必须编辑类似的代码片段,他们将获得特殊的“礼物”。

看下面的示例,然后考虑如何改进它:

const SomeComponent = () => (   <Body noBottom>     <Header center>Title</Header>     <Divider />     <Background grey>       <Section height={500}>         <Grid spacing={16} container>           <Grid xs={12} sm={6} item>             <div className={classes.groupsHeader}>               <Header center>Groups</Header>             </div>           </Grid>           <Grid xs={12} sm={6} item>             <div>               <img src={photos.groups} alt="" className={classes.img} />             </div>           </Grid>         </Grid>       </Section>     </Background>     <div>       <Section height={500}>         <Grid spacing={16} container>           <Grid xs={12} sm={6} item>             <div className={classes.labsHeader}>               <Header center>Labs</Header>             </div>           </Grid>           <Grid xs={12} sm={6} item>             <div>               <img src={photos.labs} alt="" className={classes.img} />             </div>           </Grid>         </Grid>       </Section>     </div>   </Body> ) 

如果现在需要将网格参数从xs={12} sm={6}更改为xs={12} sm={4} ,那么此任务将不会特别令人满意。 事实是,为此您必须在四个位置编辑代码。

组件方法的优点在于,它允许您解决与上述类似的问题,只在一个位置更改代码。 在我们的案例中,这种变化将在使用网格的所有地方得到体现:

 const SomeComponent = ({ classes, xs = 12, sm = 6, md, lg }) => {  const BodySection = ({ header, src }) => {    const gridSizes = { xs, sm, md, lg }    return (      <Section height={500}>        <Grid spacing={16} container>          <Grid {...gridSizes} item>            <div className={classes.groupsHeader}>              <Header center>{header}</Header>            </div>          </Grid>          <Grid {...gridSizes} item>            <div>              <img src={src} alt="" className={classes.img} />            </div>          </Grid>        </Grid>      </Section>    )  }  return (    <Body noBottom>      <Header center>Title</Header>      <Divider />      <Background grey>        <BodySection header="Groups" src={photos.groups} />      </Background>      <div>        <BodySection header="Labs" src={photos.labs} />      </div>    </Body>  ) } 

即使是这里展示的最低级别的代码转换,也使该代码在阅读和支持方面更加方便。 同时,他是解决分配给他的任务的完全适当的方法。

9.力求使组件尽可能简单。


在销售应用程序上工作时,有时我遇到的不是追求组件简单性的需求,而是避免组件过于复杂的情况的需求。

这是不必要复杂的组件示例。 它由ConfirmAvailability.js表示:

 import React from 'react' import Grid from '@material-ui/core/Grid' import Typography from '@material-ui/core/Typography' import MenuItem from '@material-ui/core/MenuItem' import Select from '@material-ui/core/Select' import Time from 'util/time' /** *     .    ,     , ,  ,  * ,    .        .     -     . * * :     Date().getTimezoneOffset().     : *   1.      . *   2.     UTC -     ,   UTC   .  ,       *     UTC. */ export default class TimeZonePicker extends React.Component {  state = {    time: new Date(),    offset: -(new Date().getTimezoneOffset() / 60),  }  componentDidMount() {    this.props.setOffset(this.state.offset)  }  handleChange = (event) => {    const d = new Date()    d.setTime(      d.getTime() +        d.getTimezoneOffset() * 60 * 1000 +        event.target.value * 3600 * 1000,    )    this.setState({      time: d,      offset: event.target.value,    })    this.props.setOffset(event.target.value)  }  render() {    const timezones = []    for (let i = -12; i <= 14; i++) {      timezones.push(        <MenuItem key={i} value={i}>          {i > 0 ? '+' : null}          {i}        </MenuItem>,      )    }    return (      <React.Fragment>        <Grid container justify="space-between">          <div>            <Typography>Current time</Typography>            <Typography variant="h6" gutterBottom>              {Time.formatTime(this.state.time)}            </Typography>          </div>          <div>            <Typography>Set timezone</Typography>            <Select value={this.state.offset} onChange={this.handleChange}>              {timezones}            </Select>          </div>        </Grid>      </React.Fragment>    )  } } 

该组件被认为是一种简单的机制,但是由于它包含高度相关的逻辑,因此它负责解决几个问题。 在编写此代码时,React钩子尚未发布,但React包含了诸如高阶组件和渲染道具之类的技术。 这意味着我们可以简单地使用这些模式之一来简化组件。 这将使我们能够演示一种在不更改现有功能的情况下简化组件的方法。

以前,所有代码都存储在一个文件中。 现在我们将其分为两个文件。 这是第一个文件SelectTimeZone.js的内容:

 import React from 'react' /** *     .    ,     , ,  ,  * ,    .        .     -     . * * :     Date().getTimezoneOffset().     : *   1.      . *   2.     UTC -     ,   UTC   .  ,       *     UTC. */ class SelectTimeZone extends React.Component {  state = {    time: new Date(),    offset: -(new Date().getTimezoneOffset() / 60),  }  componentDidMount() {    this.props.setOffset(this.state.offset)  }  handleChange = (event) => {    const d = new Date()    d.setTime(      d.getTime() +        d.getTimezoneOffset() * 60 * 1000 +        event.target.value * 3600 * 1000,    )    this.setState({      time: d,      offset: event.target.value,    })    this.props.setOffset(event.target.value)  }  getTimeZones = () => {    const timezones = []    for (let i = -12; i <= 14; i++) {      timezones.push(        <MenuItem key={i} value={i}>          {i > 0 ? '+' : null}          {i}        </MenuItem>,      )    }    return timezones  }  render() {    return this.props.render({      ...this.state,      getTimeZones: this.getTimeZones,    })  } } 

这是第二个文件的样子TimeZonePicker.js

 import React from 'react' import Grid from '@material-ui/core/Grid' import Typography from '@material-ui/core/Typography' import MenuItem from '@material-ui/core/MenuItem' import Select from '@material-ui/core/Select' import Time from 'util/time' const TimeZonePicker = () => (  <SelectTimeZone    render={({ time, offset, getTimeZones, handleChange }) => (      <Grid container justify="space-between">        <div>          <Typography>Current time</Typography>          <Typography variant="h6" gutterBottom>            {Time.formatTime(time)}          </Typography>        </div>        <div>          <Typography>Set timezone</Typography>          <Select value={offset} onChange={handleChange}>            {getTimeZones()}          </Select>        </div>      </Grid>    )}  /> ) export default TimeZonePicker 

经过处理后,项目代码比以前干净得多。 我们从组件的表示部分提取了逻辑。 现在,此外,该项目的单元测试将大大简化。

10.使useState复杂时使用useReducer


您必须在项目中处理的状态片段越多, useState的使用就越复杂。

例如,这可能看起来像这样:

 import React from 'react' import axios from 'axios' const useFrogs = () => {  const [fetching, setFetching] = React.useState(false)  const [fetched, setFetched] = React.useState(false)  const [fetchError, setFetchError] = React.useState(null)  const [timedOut, setTimedOut] = React.useState(false)  const [frogs, setFrogs] = React.useState(null)  const [params, setParams] = React.useState({ limit: 50 })  const timedOutRef = React.useRef()  function updateParams(newParams) {    if (newParams != undefined) {      setParams(newParams)    } else {      console.warn(        'You tried to update state.params but the parameters were null or undefined',      )    }  }  function formatFrogs(newFrogs) {    const formattedFrogs = newFrogs.reduce((acc, frog) => {      const { name, age, size, children } = frog      if (!(name in acc)) {        acc[name] = {          age,          size,          children: children.map((child) => ({            name: child.name,            age: child.age,            size: child.size,          })),        }      }      return acc    }, {})    return formattedFrogs  }  function addFrog(name, frog) {    const nextFrogs = {      ...frogs,      [name]: frog,    }    setFrogs(nextFrogs)  }  function removeFrog(name) {    const nextFrogs = { ...frogs }    if (name in nextFrogs) delete nextFrogs[name]    setFrogs(nextFrogs)  }  React.useEffect(() => {    if (frogs === null) {      if (timedOutRef.current) clearTimeout(timedOutRef.current)      setFetching(true)      timedOutRef.current = setTimeout(() => {        setTimedOut(true)      }, 20000)      axios        .get('https://somefrogsaspi.com/api/v1/frogs_list/', { params })        .then((response) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          setFetching(false)          setFetched(true)          if (timedOut) setTimedOut(false)          if (fetchError) setFetchError(null)          setFrogs(formatFrogs(response.data))        })        .catch((error) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          console.error(error)          setFetching(false)          if (timedOut) setTimedOut(false)          setFetchError(error)        })    }  }, [])  return {    fetching,    fetched,    fetchError,    timedOut,    frogs,    params,    addFrog,    removeFrog,  } } export default useFrogs 

如果您将此代码转换为useReducer ,则使用所有这些功能将变得更加方便:

 import React from 'react' import axios from 'axios' const initialFetchState = {  fetching: false  fetched: false  fetchError: null  timedOut: false } const initialState = {  ...initialFetchState,  frogs: null  params: { limit: 50 } } const reducer = (state, action) => {  switch (action.type) {    case 'fetching':      return { ...state, ...initialFetchState, fetching: true }    case 'fetched':      return { ...state, ...initialFetchState, fetched: true, frogs: action.frogs }    case 'fetch-error':      return { ...state, ...initialFetchState, fetchError: action.error }    case 'set-timed-out':      return { ...state, ...initialFetchState, timedOut: true }    case 'set-frogs':      return { ...state, ...initialFetchState, fetched: true, frogs: action.frogs }    case 'add-frog':      return { ...state, frogs: { ...state.frogs, [action.name]: action.frog }}    case 'remove-frog': {      const nextFrogs = { ...state.frogs }      if (action.name in nextFrogs) delete nextFrogs[action.name]      return { ...state, frogs: nextFrogs }    }    case 'set-params':      return { ...state, params: { ...state.params, ...action.params } }      default:        return state  } } const useFrogs = () => {  const [state, dispatch] = React.useReducer(reducer, initialState)  const timedOutRef = React.useRef()  function updateParams(params) {    if (newParams != undefined) {      dispatch({ type: 'set-params', params })    } else {      console.warn(        'You tried to update state.params but the parameters were null or undefined',      )    }  }  function formatFrogs(newFrogs) {    const formattedFrogs = newFrogs.reduce((acc, frog) => {      const { name, age, size, children } = frog      if (!(name in acc)) {        acc[name] = {          age,          size,          children: children.map((child) => ({            name: child.name,            age: child.age,            size: child.size,          })),        }      }      return acc    }, {})    return formattedFrogs  }  function addFrog(name, frog) {    dispatch({ type: 'add-frog', name, frog })  }  function removeFrog(name) {    dispatch({ type: 'remove-frog', name })  }  React.useEffect(() => {    if (frogs === null) {      if (timedOutRef.current) clearTimeout(timedOutRef.current)      timedOutRef.current = setTimeout(() => {        setTimedOut(true)      }, 20000)      axios        .get('https://somefrogsaspi.com/api/v1/frogs_list/', { params })        .then((response) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          const frogs = formatFrogs(response.data)          dispatch({ type: 'set-frogs', frogs })        })        .catch((error) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          console.error(error)          dispatch({ type: 'fetch-error', error })        })    }  }, [])  return {    fetching,    fetched,    fetchError,    timedOut,    frogs,    params,    addFrog,    removeFrog,  } } export default useFrogs 

尽管这种方法可能不比使用useState ,但是通过查看代码可以看出,新代码更易于维护。 这是由于以下事实:使用useReducer程序员不必担心钩子不同部分的状态更新,因为所有这些操作都在reducer一个位置定义。

在使用useState的代码版本中,除了编写逻辑外,我们还需要在挂钩内声明函数,以便弄清楚状态的下一部分应该是什么。 使用useReducer您不必这样做。 相反,所有内容都归入reducer函数。 我们只需要触发适当类型的操作即可,实际上,这就是我们需要担心的。

11.在有争议的情况下使用函数声明


使用此建议的一个很好的例子是创建一个useEffect清理useEffect

 React.useEffect(() => {  setMounted(true)  return () => {    setMounted(false)  } }, []) 

经验丰富的React开发人员知道返回函数的作用,他将很容易理解该代码。 但是,如果您认为对useEffect不太熟悉的useEffect会阅读此代码,则最好在代码中尽可能清楚地表达其意图。 它是关于使用可以赋予有意义名称的函数声明。 例如,此代码可以这样重写:

 React.useEffect(() => {  setMounted(true)  return function cleanup() {    setMounted(false)  } }, []) 

这种方法使我们可以清楚地描述返回函数所扮演的角色。

12.使用更漂亮


Prettier帮助个人开发人员和团队维护一致且一致的代码格式化方法。 该工具有助于节省时间和精力。 通过减少讨论程序样式的原因,它简化了代码审查。 Prettier还鼓励程序员使用简洁的代码编写技术。 此工具应用的规则是可编辑的。 结果,事实证明每个人都可以根据需要自定义它。

13.努力使用速记来声明片段


该建议的实质可以在以下两个示例中表达。

这是片段声明的简化版本:

 const App = () => (  <>    <FrogsTable />    <FrogsGallery />  </> ) 

这是完整版本:

 const App = () => (  <React.Fragment>    <FrogsTable />    <FrogsGallery />  </React.Fragment> ) 

14.编写代码时,请遵循元素的特定放置顺序。


编写代码时,我更喜欢按一定顺序排列一些命令。 例如,我在导入文件时执行此操作(这里的唯一例外是仅导入react ):

 import React from 'react' import { useSelector } from 'react-redux' import styled from 'styled-components' import FrogsGallery from './FrogsGallery' import FrogsTable from './FrogsTable' import Stations from './Stations' import * as errorHelpers from '../utils/errorHelpers' import * as utils from '../utils/' 

查看此代码,可能有人认为这里没有遵守特殊顺序。 毕竟,导入的实体甚至没有按字母顺序排序。 但是,按字母顺序排列某些内容只是我使用的命令排序方案的一部分。

为了追求项目代码的纯粹性,我按照以下顺序使用以下规则:

  1. 导入React。
  2. 导入库(按字母顺序)。
  3. 从项目中导入实体的绝对命令(按字母顺序)。
  4. 相对导入命令(按字母顺序)。
  5. 格式为import * as命令。
  6. 格式为import './<some file>.<some ext>'

这就是我更喜欢组织变量的方式。 说-对象的属性:

 const character = (function() {  return {    cry() {      //    },    eat() {      //    },    hop() {      //    },    jump() {      //    },    punch() {      //    },    run() {      //    },    scratch() {      //    },    scream() {      //    },    sleep() {      //    },    walk() {      //    },    yawn() {      //    },  } })() 

如果您在编写代码时遵循某些对实体进行排序的规则-这将对其纯度产生有益的影响。

总结


我们为您提供了编写干净的React应用程序代码的技巧。 我们希望您在其中找到对您有用的东西。

亲爱的读者们! 您将对本文介绍的提示添加哪些建议?

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


All Articles