乐趣区

关于react.js:几个你必须知道的React错误实践

本文是作者在理论工作教训中总结提炼出的谬误应用 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 可能并不会依照他的预期去工作。
比方一个图片压缩组件:参考 React 实战视频解说:进入学习

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

退出移动版