阅读 1712

「学习笔记」ReactHooks入门

📖前言

本文是本人在学习ReactHooks记录的学习笔记,内容不仅限于文档中的内容,涉及了Hooks源码相关。如果有错误,还请及时指正。

❓什么是Hook?为什么需要Hook?

推荐看一下Dan Abramov的博客

image

  1. 逻辑复用,如果使用高阶组件等特性,较为复杂。
  2. 传统的函数组件无法存储state状态。
  3. Hooks允许在函数组件中,调用React的功能。
  4. 使用自定义Hook可以简化逻辑复用。
  5. Hooks是React的未来
  6. Hooks不是什么魔法。Hooks的设计也与React无关。

⛰️useState

useState可以在函数组件中,添加state Hook。

调用useState会返回一个state变量,以及更新state变量的方法。useState的参数是state变量的初始值,初始值仅在初次渲染时有效

更新state变量的方法,并不会像this.setState一样,合并state。而是替换state变量。

下面是一个简单的例子, 会在页面上渲染count的值,点击setCount的按钮会更新count的值。

image

函数式更新

如果新的state需要之前的state计算得到。可以向useState返回的更新函数中,传递一个函数。函数的参数是前一个state。

image

惰性的初始值

useState的初始值是惰性的,只会在初次渲染组件的时候起作用。如果state的初始值需用通过复杂计算得到,useState的初始值也可以是一个函数,函数的返回值将是useState的初始值。

image

跳过state更新

调用state的更新函数,传入和当前一样的state时。React会跳过子组件的渲染,以及effect的执行。

image

小节

  1. useState的更新是替换,而不是合并。
  2. useState可以接收函数参数,并将函数的返回值作为初始值
  3. useState的更新函数可以接收函数作为参数,函数的参数是前一状态的state值。
  4. 使用当前的值,对state进行更新不会触发渲染。
  5. 可以使用多个useState,声明多个state变量。

🏁useEffect

除了官方文档外,推荐阅读一下Um guia completo para useEffect

useEffect可以让我们在函数组件中执行副作用操作。事件绑定,数据请求,动态修改DOM。

useEffect将会在每一次React渲染之后执行。无论是初次挂载时,还是更新。(当然这种行为我们可以控制)

image

需要清除的effect

在传统的class组件中,通常在componentDidMount中添加对事件的监听。在componentWillUnmount中会清除对事件的监听。 我们需要在不同的生命周期函数中,拆分我们的逻辑。

而effect可以返回一个函数,当react进行清除时, 会执行这个返回的函数。每当执行本次的effect时,都会对上一个effect进行清除。组件卸载时也会执行进行清除。

也就是说,下面的代码中。每一次更新,都会对上一次的effect进行卸载,并执行本次的effect。

image

effect性能优化

每次执行effect,清除上一次effect可能会造成不必要的性能浪费。我们可以通过effect的第二个参数,控制effect的执行。 第二个参数是useEffect的依赖,只有当依赖发生变化时,useEffect才会更新。

image

当我们传递传递一个空数组作为依赖时,会告诉React,effect不依赖任何state或者props。我们可以使用此行为模拟componentDidMount或者componentWillUnmount。

⚠️ 请记住使用空数组的effect和componentDidMount是有差异的

image

🤔️useEffect(fn, [])和componentDidMount的差异有什么差异?

类似的问题,为什么有时候在effect里拿到的是旧的state或prop呢?

image

useEffect会捕获props, state,但是始终是初始的值。 如上图所示,当我们点击button多次,effect在3秒之后的回调,打印的依然是state的初始值。

为什么会这样?

这是因为javascript闭包的机制,函数组件被调用后,函数内部的state由于被内部定时器的回调所依赖,所以没有被垃圾回收机制所清除,而是保存在内存里了。所以等定时器的回掉执行时,打印的是之前闭包中存储的变量。

如何避免拿到旧的prop或者state?

  1. 使用useRef
  2. 查看是否遗漏了依赖, 导致effect没有更新

🤔️如何正确的useEffect中请求数据?

阅读这篇文章是一个不错的选择How to fetch data with React Hooks?

如何请求数据?

image

如何在useEffect中使用async/await?

async函数默认会返回一个Promise对象,而useEffect中,只允许什么都不返回或者返回一个清除函数。控制台中,会触发如下警告 **Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect.. **

解决方案如下👇

image

小节

  1. 可以使用多个useEffect分离逻辑。在class组件中,我们通常会在componentDidMount中,混杂添加事件绑定,请求数据等多种无关的逻辑。
  2. 如果只需要useEffect在加载时执行一次,或者卸载组件时执行一次,可以向useEffect传递一个空数组。
  3. useEffect不支持直接使用async函数
  4. 如果在useEffect中形成了闭包,将会拿到旧的props,或者state。(这个与Hook无关,与函数组件本身的特性相关)
  5. useEffect返回的函数,清除函数。会在上一个effect被清除时调用。

📰Hook规则

  1. 只在最顶层使用Hook,不在判断,循环语句中使用Hook
  2. 只在React函数中使用Hook

🤔️为什么Hook高度依赖执行顺序?

我们借助preact的源码进行分析,在preact中Hook的存储在组件的私有属性__hooks._list的数组中。读取和存储都依赖currentIndex的指针,如果hook的执行顺序改变,currentIndex也会被改变,获取的hook可能是完成错误的。

image

⚙️自定义Hook

  1. 自定义Hook必须以use开头
  2. 自定义Hook只是逻辑复用,其中的state是不会共享的。
  3. 自定义Hook内部可以调用其他Hook。
  4. 避免过早的拆分抽象逻辑

下面是一个自定义Hook的🌰

💡自定义数据获取Hook

我们通过ajax请求表哥数据时,很多逻辑都是通用的。比如loading的状态的处理,错误信息的处理,翻页的处理。我们可以把这些逻辑抽象成一个公共的Hook。不同的api,作为自定义Hook的参数。

下面是一个数据请求自定义Hook的例子:

image

如何使用?从自定义Hook向外暴露一些state,和setState。当page发生改变时,会重新请求数据。

image

🚀其他API

useContext

接收一个context对象,并返回当前的context的值。useContext可以订阅context的变化。但是仍然需要上层组件使用<MyContext.Provider>来为下层组件提供context。

image

useReducer

useReducer接收三个参数,reducer函数,initialArg初始值,init惰性初始值函数。reducer函数和Redux的reducer类似,接收state,以及action。返回更新后的state。如果传入三个参数,init(initialArg)将作为初始值。

useReducer在复杂场景下比useState更适用。

指定初始state

useReducer的第二个参数可以指定初始的state

惰性初始化

如果指定useReducer的第三个参数,useReducer的初始值会被设置为init(initialArg)

image

跳过dispatch

如果ReducerHook的返回的state与当前state相同,React将跳过子组件的渲染及effect的执行。

🌰例子

在自定义Hook的例子中,使用了多个useState进行状态管理,当出现大量状态时,useState会使得逻辑变得很复杂。我们现在可以使用useReducer管理多个state,也可向子组件传递dispatch,而不是回调函数。

下面是将自定义Hook中,请求数据自定义Hook的例子,改造成useReducer的方法。

image

useCallback

useCallback接收回调函数和依赖数组作为参数。useCallback会返回memoized函数。当依赖项改变的时候,会返回的新的memoized函数。

image

useMemo

useMemo和useCallback类似。useMemo会返回memoized值。当依赖项改变时,会重新计算memoized值。

image

🌟useRef

useRef除了获取dom节点的功能外,useRef的current属性,可以方便保存任何可变值。useRef每一次渲染时,都会返回同一个ref对象。

设想下面👇这种情况, 组件重新渲染时,由于timer发生了变化,我们会永远无法清除定时器。

image

如果想要在更新后,依然可以清除定时器,可以将timer,保存到useRef的current属性上

image

useLayoutEffect

image
(红圈中是同步的操作)

useLayoutEffect和useEffect类似,但是不同的是:

  • useEffect,使用useEffect不会阻塞浏览器的重绘
  • useLayoutEffect, 使用useLayoutEffect,会阻塞浏览器的重绘。如果你需要手动的修改Dom,推荐使用useLayoutEffect。因为如果在useEffect中更新Dom,useEffect不会阻塞重绘,用户可能会看到因为更新导致的闪烁,

image

🚗参考

关注下面的标签,发现更多相似文章
评论