关于前端:几个你必须知道的React错误实践

33次阅读

共计 6260 个字符,预计需要花费 16 分钟才能阅读完成。

本文是作者在理论工作教训中总结提炼出的谬误应用 React 的一些形式,心愿可能帮忙你解脱这些雷同的谬误。

1. Props 透传

props 透传是将单个 props 从父组件向下多层传递的做法。
现实状态下,props 不应该超过两层。
当咱们抉择多层传递时,会导致一些性能问题,这也让 React 官网比拟头疼。
props 透传会导致不必要的从新渲染。因为 React 组件总会在 props 发生变化时从新渲染,而那些不须要 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'  // 整个包导入

import _map from 'lodash/map' // 只导入须要的包

为了保障良好的用户体验度,咱们应该让 FCP 放弃在 1.8 秒以内,所以咱们须要简化代码体积。
古代的打包工具都有摇树性能,应用各种形式来放大和压缩咱们用于生产的代码,比方 webpack。然而在有些状况下它不能很好的去处无用的代码,咱们最好晓得那些代码应该被打包,而不是仅仅依附打包工具来尝试修复咱们的代码问题。
当初的 JavaScript 曾经经验了屡次重大更新,领有了十分多的新性能。在过来咱们须要应用 lodash 这类库来实现这些性能,然而当初 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>
}

将组件和逻辑拆散,有两个益处:

  1. 关注分离点。
  2. 重用业务逻辑。

4. 每次渲染的反复工作

即便你是经验丰富的 React 新手,可能依然做不到对渲染这件事齐全理解。
渲染是常常产生并且很多时候是出其不意的。
这是应用 React 编写组件的外围准则之一,在编写 React 组件时应该牢记在心。
同时意味着,在渲染组件的时候会从新执行某些逻辑。
React 提供了 useMemo 和 useCallback 两个 Hook,如果应用切当,这些 Hook 能够缓存计算结果或者函数,来缩小不必要的反复渲染,最终进步性能。

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>)
}

下面的例子是一个我的项目列表的展现,其中须要通过某些条件来过滤列表,最终展现给用户。这种数据过滤在前端中是不可避免的,所以咱们能够应用 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 中使用率最高的两个 Hook 之一。
然而令很多人困惑的是,useState 可能并不会依照他的预期去工作。
比方一个图片压缩组件:

function Compress() {const [files, setFiles] = useState([])
  const handleChange = (newFiles) => {api(newFiles).then((res)=>{const cloneFiles = [...files]// 这里的 file 始终是[]
      cloneFiles.map(// 一些逻辑...)
      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]// 这里的 file 是最新的
        return cloneFiles.map(// 一些逻辑...)
      })
    })
  }
  return <input type="upload" multiple onChange={handleChange}/>
}

起因在于函数是基于以后闭包应用的状态。然而状态更新后,会触发渲染,并创立新的上下文,而不会影响之前的闭包。
所以要让程序依照预期执行,必须应用上面的语法:

setFiles(oldFiles => [...oldFiles, ...res.data])

7. 布尔运算符的谬误应用

大多数状况下咱们都会应用布尔值来管制页面上某些元素的渲染,这是十分失常的事件。
除此之外还有几种其余形式来解决这种逻辑,最罕用的是 && 运算符,这也齐全是 JavaScript 的性能,但有时它会有一些意想不到的结果。

const total = 0

const Component = () => total && ` 商品总数: ${total}`

当咱们须要展现商品数量时,如果数量为 0,那么只会展现 0,而不是商品总数:0。
起因是 JavaScript 会将 0
所以最好不要依赖 JavaScript 的布尔值虚实比拟。

参考 前端进阶面试题具体解答
正确的形式如下:

const total = 0

const Component = () => {
  const hasItem = total > 0
  return hasItem && ` 商品总数: ${total}`
}

8. 到处应用三元表达式进行条件渲染

三元表达式是一个十分简洁的语法,在简短的代码中十分令人满意。所以很多人喜爱在 React 中应用三元表达式来渲染组件。
然而它的问题在于难以扩大,在最简略的三元表达式中没什么问题,可一旦多个三元表达式组合到一起,就造成了难以浏览的超大型组件。

import React, {useMemo} from 'react'

const VIPExample = ({vipLevel}) => {
  return (<div>
      会员零碎      {vipLevel === 0 ? (        <button> 开明 VIP</button>) : vipLevel === 1 ? (<p> 尊敬的青铜 VIP,您的特权有 3 项:...</p>) : vipLevel === 2 ? (<p>...</p>) : <p>...</p>}      </div>)
}

这种代码没有功能性上的谬误,然而在可读性方面做得很差。
解决它的方法有两种。
第一种是应用条件判断代替三元表达式。

import React, {useMemo} from 'react'

const VIPDetail = (vipLevel) => {if(vipLevel === 0) return <button> 开明 VIP</button>
  if(vipLevel === 1) return <p> 尊敬的青铜 VIP,您的特权有 3 项:...</p>
  // ...
}

const VIPExample = ({vipLevel}) => {
  return (<div>
      会员零碎      {VIPDetail(vipLevel)}      </div>)
}

如果每个分支中的组件比较复杂,咱们更进一步,咱们应用形象来封装组件。

import React, {useMemo} from 'react'

const VIPZeroDetail = ({vipLevel}) => {if(vipLevel !== 0) return null
  return <button> 开明 VIP</button>
}

const VIPOneDetail = ({vipLevel}) => {if(vipLevel !== 1) return null
  return <p> 尊敬的青铜 VIP,您的特权有 3 项:...</p>
}

// ...

const VIP = ({vipLevel}) => {
  return <>
      <VIPZeroDetail vipLevel={vipLevel} />
      <VIPOneDetail vipLevel={vipLevel} />
      <!-->...<-->
    </>
}const VIPExample = ({vipLevel}) => {  return (<div>
      会员零碎      <VIP vipLevel={vipLevel} />
    </div>)}

大多数状况下应用条件判断的形式就够用了。应用形象封装组件的形式有个毛病,就是组件太过于散乱,同步逻辑比拟麻烦。

9. 不定义 propTypes 或者不解构 props

React 的大多数货色和 JavaScript 简直是一样的。React 的 props 也只是 JavaScript 中的对象,这也就意味着咱们能够在对象中传递许多不同的值,而组件很难晓得它们。
这样组件在应用 props 时就变得比拟麻烦。
很多人喜爱这么拜访 props。

const Example = (props) => {
  return <div>
    <h1>{props.title}</h1>
    <p>{props.content}</p>
  </div>
}

在不应用 TypeScript 或者不定义 propsTypes 的状况下,咱们能够随便应用 props.xxx 的形式来拜访 props。
为了解决这个问题,咱们能够抉择应用 TypeScript 为组件的 props 申明类型。
如果你没有应用 TypeScript,那么能够应用 propTypes。
同时倡议将 props 以解构的形式应用。

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 的一个终点,心愿可能帮你躲避很多开发过程中容易呈现的谬误。

正文完
 0