使用 React Hooks 的 5 条建议
Hooks 越来越成为体验React之美的新潮方式。本文为您提供了一些有关如何正确使用 Hooks 的建议,以及详细的解释和见解。
建议 #1:适当使用 Hook
当 Hooks 刚出来的时候,React 社区的人气一直很高。钩子很酷!挂钩很流行!Hooks 是 React 的未来!
我的一些同事甚至试图将他们的项目代码库迁移到 Hooks 的幻想世界。不过要小心!如果您不了解,请不要使用 HooksReact 团队发明 Hooks 的动机. 请至少花 30 分钟阅读并深入思考。
在这里总结一下,我可以给你 2 个 Hooks 存在的理由:
从组件中提取状态逻辑,以便可以独立测试并轻松重用
提供 class的替代方案,因为这 充满了困惑和惊喜。
我承认Hooks尽到了自己的职责,值得表扬。我真的很喜欢胡克斯!但我的建议是,如果你对上面提到的这些原因没有任何特别的问题,并且如果你的代码已经简洁有效,那么就没有必要用 React Hooks 重写你所有的组件,除非你真的想尝试炒作。
建议 #2:React Hooks 不会取代 Redux
钩子很棒,但没有。
Eric Elliott 写了一篇很棒的文章这里解释为什么 Hooks 不是Redux的绝对替代品。
Hooks 是一组 API(或函数),可帮助您使用状态和其他 React 功能。同时,Redux 是一种架构,您可以使用它来组织代码并以可预测的方式控制流程。
在谈论 Redux 时,我们不仅仅提到状态管理库,我们还提到了一个巨大的 Redux 生态系统,其中有大量的开源库和强大的工具来帮助你处理副作用、不可变性、持久性……
此外,您可以将 Redux 与 Hooks 一起使用。因此,与其重新发明轮子,不如利用两者来实现更大的利益。
建议 #3:避免状态突变
类似于 class组件中的this.setState ,不要直接修改state:
const [ user , setUser ] = useState ({name: "Jane"}) //错误的用户.name = "John" // 不会重新渲染// 因为user的引用仍然是同一个setUser ( user )
相反,总是创建一个新状态:
const [ user , setUser ] = useState ({name: "Jane"}) // 将触发重新渲染setUser ({... user , name: "Jane"})
建议 4:了解依赖关系数组的工作原理
许多 React 开发人员习惯于组件生命周期,尤其是componentDidMount 和 componentDidUpdate。所以在学习 Hooks 时,他们的第一个问题通常是:“如何将这段代码运行一次?”。
换句话说,如何让 useEffect在渲染 后只运行一次 (比如 componentDidMount)。答案是 dependencies array。
这是您作为第二个参数传递给useEffect的数组:
useEffect(() => { }), depArr )
Dependencies 数组是用于浅比较以确定何时重新运行效果的值列表。我们可以想象这样一个伪代码:
// 这旨在说明这个想法。// 不是 useEffect 的确切实现。// React 使用的实际比较算法是 Object.is lastDeps = null useEffect = (func, deps) => {
如果(!部门)
返回函数()
如果(!lastDeps){
lastDeps = [...deps] // 浅拷贝
返回函数()
}
for (i = 0; i < deps.length; ++i)
if (lastDeps[i] !== deps[i]) // 浅比较
返回函数()}
看看这个useEffect的伪实现,你可以更好地理解为什么以及什么时候 useEffect 会在下面的代码中重新运行:
// 每次渲染后运行useEffect (() => {
console.log('运行效果')}) // 在第一次渲染后只运行一次useEffect (() => {
console.log('运行效果')}, [])
在下面的代码中, useEffect 执行引用相等性检查,因此它无法确定 用户的任何属性 是否已更改。
const [ user , setUser ] = useState ({name: "Jane"}) // 在第一次渲染后运行,并在用户更改时重新运行useEffect (() => {
console.log('run effect') // 在这里执行效果}, [ user ]) ... // 在某处按下按钮onPress = () => {
setUser({名称:“简”})}
尽管 名称 保持相同的值“Jane”,但这将触发重新渲染并重新运行效果函数。
建议 #5:了解 useCallback 和 useMemo
如果将函数作为回调传递给子组件,则应使用useCallback记住该函数。
如果您的数据计算成本很高,您应该使用useMemo将其记忆。
示例:生成并显示一个随机数。
没有 useCallback :
每当您单击 RandomButton时 ,它都会调用 setNumber ,这会触发Container 上的重新渲染 (请参阅日志)
Container
重新渲染时 ,它会启动一个新函数,然后将此函数传递给 as prop。randomize
RandomButton
RandomButton
收到一个新道具并决定重新渲染(见日志)。
常量容器= () => {
const [数字, setNumber ] = useState (0)
const randomize = () => setNumber (Math.random()) console.log("渲染容器")
返回 (
<>
{数字}
< RandomButton randomize ={ randomize }/> ) } const RandomButton = ({ randomize }) => { console.log("render RandomButton") returnrandomize}/> }
现在,使用 useCallback 和 React.Memo:
Container
重新渲染时 ,检查其依赖项数组以确定是返回最后一个记忆函数还是创建一个新函数。useCallback
但在这种情况下,我们提供了一个空数组,这意味着 useCallback 将始终返回相同的 随机化 函数。
当将 randomize 函数作为 prop 传递给 RandomButton时,在React.Memo的帮助下 ,它检查并发现 randomize prop 是相同的,因此无需重新渲染(见日志)。
常量容器= () => {
const [数字, setNumber ] = useState (0)
const randomize = useCallback (() => setNumber (Math.random()), [] )
console.log("渲染容器")
返回 (
<>
{数字}
< RandomButton randomize ={ randomize }/> ) } const RandomButton = React.memo (({ randomize }) => { console.log("render RandomButton") returnrandomize}/> })
在这种特殊情况下,我们将一个空数组传递给 useCallback ,因为它的记忆函数 () => setNumber(Math.random()) 不需要任何额外信息即可执行。
但是如果我们想在随机化函数中使用 数字 状态怎么办 ,就像这样:
() => setNumber(数字+ Math.random())
在这种情况下, 随机化 取决于 数字 状态才能正确执行。所以我们 在useCallback中将number指定 为依赖项 :
const随机化= useCallback (
() => setNumber ( number + Math.random()), [number] )
但是,这仍然可能不是一个完美的解决方案,因为当 randomize 调用 setNumber时,它会用 一个新值 更新number ,然后useCallback 检查它的依赖项是否已更改并创建一个新的 randomize函数,导致 再次 不必要地重新渲染 RandomButton .
为了处理这个困境,我们可以使用功能更新. 这在使用先前状态计算新状态时特别有用。
一个完美的解决方案如下:
const随机化= useCallback (
() => setNumber ( number + Math.random()), [number] )