80. Hooks各个击破

Hooks

Screen Shot 2021-12-20 at 5.00.41 AM

useState

使用状态

注意事项1:不可局部更新

如果state是一个对象,能否部分setState?

答案是不行,请看示例代码

https://codesandbox.io/s/jirengudemo-t16xl?file=/src/App.js:0-365

jrg-demo01

会出现像图中这种,只copy了name,而没有copy age,因为因为setState不会帮我们合并属性

如何解决?

那么useReducer会合并属性吗?妈的,也不会!

注意事项2:地址要变

setState(obj)如果obj地址不变,那么React就认为数据没有变化

useState续

useState接受函数

该函数返回初始state,且只执行一次

setState接受函数

https://codesandbox.io/s/hopeful-dubinsky-m8gnt?file=/src/index.js:290-350

什么时候用这种方式?

如果你能接受这种方式,应该优先使用这种形式

useReducer

用来践行Flux/Redux的思想

开代码,分4步走

  1. 创建初始值initialState

  2. 创建所有操作reducer(state,action)

  3. 传给useReducer,得到读和写API

  4. 调用写({type:'操作类型})

总的来说useReducer是useState的复杂版

示例代码

一个用useReducer的表单例子

用useReducer代替redux

步骤

  1. 将数据集中在一个store对象

  2. 将所有操作集中在reducer

  3. 创建一个Context

  4. 创建对数据的读写API

  5. 将第四部的内容放到第三部的Context

  6. 用Context.Provider将Context提供给所有组件

  7. 各个组件用useContext获取读写API

例子:

如何模块化?其实很简单

useContext

上下文

全局变量是全局的上下文。

上下文是局部的全局变量。

使用方法

之前的例子已经介绍过了。

  1. 使用C = createContext(initial)创建上下文。

  2. 使用<C.provider>圈定作用域。

  3. 在作用域内使用useContext(C)来使用上下文。

代码示例:https://codesandbox.io/s/loving-tesla-l47snp?file=/src/App.js:0-626

注意事项

useContext不是响应式的,你在一个模块将C里面的值改变,另一个模块,不会感知到这个变化。

useEffect

副作用(API名字叫得不好。)

useEffect就是在更新后执行某个操作

对环境的改变记为副作用,如修改document.title

但我们不一定非要把副作用放在useEffect里

实际上叫做afterRender更好,每次render后运行。

用途

作为componentDidMount使用,[]做第二个参数,表示只在第一次更新后执行,其他时候不执行。

作为componentDidUpdate使用,可指定依赖

https://codesandbox.io/s/useeffect-z6g8jl?file=/src/App.js

作为componentWillUnmount使用,通过return

以上三种用途可同时存在。

componentwillunmount

特点

如果同时存在多个useEffect,会按照出现次序执行。

useLayoutEffect

当中途需要改变dom的时候,(中途截胡),但是一般很少用到

https://codesandbox.io/s/uselayouteffect-gb1pqx?file=/src/App.js

布局副作用

useEffect在浏览器 渲染完成后执行。

例子

useLayoutEffect 在浏览器渲染前执行。

通过时间点来侧面证明(此处要画图说明)。

特点

useLayoutEffect总是比useEffect先执行

useLayoutEffect里的任务最好影响了Layout

经验

为了用户体验,优先使用useEffect(优先渲染)

useMemo

https://codesandbox.io/s/usememo-7f0qfj?file=/src/App.js

一句话总结:就是在相应组件的props变化的时候才执行,否则不执行。

要理解React.useMemo需要先讲React.memo,React默认有多余的render。

讲代码中的Child用React.memo(Child)代替。

如果props不变,就没有必要再次执行一个函数组件。

最终效果:代码。

memo可以改写成下面这样

但是这玩意儿有一个bug。

添加了监听函数之后,1秒破功。

因为App 运行时会再次执行第12行,生成新的函数,新旧函数虽然功能一样,但是地址不一样,怎么办,用useMemo

特点

第一个参数是()=>value,见定义

第二个参数是依赖[m,n]。

只有当依赖变化时,才会计算出value

如果依赖不变,那么就重用之前的value

这不就是vue2的computed吗

注意

如果你的value是个函数,那么你就要写成useMemo(()=>(x)=>console.log(x))。

这是一个返回函数的函数。

是不是很难用?于是就有了useCallback。

useCallback

useCallback是useMemo的语法糖

用法

useCallback(x => log(x), [m])等价于useMemo(()=>x =>log(x),[m])

useRef

目的

  • 如果你需要一个值,在组件不断render时保持不变。

  • 初始化: const count = useRef(0)

  • 读取: count.current

  • 为什么需要current?

  • 为了保证两次useRef是同一个值(只有引用能做到)

  • 此处需要画图解释

延伸

  • 看看Vue3的ref

  • 初始化: const count = ref(0)

  • 读取:count.value

  • 不同点: 当count.value变化时,Vue3会自动render

useRef就是用来控制全局变量

useEffect是每次更新之后需要做的操作

下面这段代码用来记录一共更新了几次。

.current不会更新UI,如果需要render,要手动添加一个setN,调用这个setN(Math.random) 就可以更新UI。

useRef能做到变化时自动render吗?

不能,为什么不能,因为这不符合React的理念,React的理念是UI=f(data),你如果想要这个功能,完全可以自己加,监听ref,当ref.current变化时,调用setX即可。

不想自己加

那你就用Vue3吧,Vue3帮你加好了。

总结

useState/useReducer(useState的复杂版)n每次都变

useMemo/useCallback [m]的时候fn会变

useRef 永远不变

forwardRef

总结:forwardRef就是让你在函数组件上加了一个参数,然后就可以使用ref,class组件默认就可以用ref。

讲了useRef就不得不讲一下它了。

基本用法:让函数组件Button2支持ref

代码一:props无法传递ref属性

代码二:实现ref的传递

https://codesandbox.io/s/forwardref-18q9d9?file=/src/App.js:0-403

代码三: 两次ref传递得到button的引用。https://codesandbox.io/s/amazing-snow-9f5g3 这段代码是实现拖拽组件

useRef

可以用来引用DOM对象。

也可以用来引用普通对象。

forwardRef

由于props不包含ref,所以需要forwardRef。

为什么props不包含ref呢?因为大部分时候不需要。

useImperativeHandle

名字起得稀烂

应该叫做setRef

代码:https://codesandbox.io/s/useimperativehandle-ye0d4q?file=/src/App.js

不用useImperativeHandle的代码

用了useImperativeHandle的代码。

分析

用于自定义ref的属性。

自定义Hook

react里面最牛逼的功能。可以添加增删改查的功能,类似于把接口暴露给使用者,使用者只要知道怎么用即可,不用管内部怎么运作的。自定义Hooks的时候可以使用Context,reducer,这样都没有必要使用redux。方方建议在自己的项目中尽量使用自定义Hook。

封装数据操作

简单例子

https://codesandbox.io/s/wizardly-tesla-sy077

贴心例子

https://codesandbox.io/s/recursing-darkness-zuo4b

分析

你还可以在自定义Hook里使用Context。

useState只说了不能在if里,没说不能在函数里运行,只要这个函数在函数组件里运行即可。

stale Closure

过时的闭包,参考文章链接https://dmitripavlutin.com/react-hooks-stale-closures/

image-20220403234634817

总结

React Hooks

  • useState状态

  • useEffect副作用=======>useLayoutEffect

    • useEffect就是afterRender

    • useLayoutEffect就是比useEffect提前一点点,很少用的原因是会影响渲染效果,特殊情况下会用

  • useContext上下文

    • 就是开放一个读写接口给整个页面用

  • Redux useReducer

    • useReducer是专门给Redux的用户设计的,可以不用redux

  • useMemo记忆=======>useCallback回调

    • useMemo要和React.memo配合使用

    • 如果useMemo不好用,可以使用useCallback

  • useRef引用 ======>useImperativeHandle

    • useRef就是保持一个量不变

    • forwardRef不是hook

    • useImperativeHandle 就是setRef,在支持ref的时候可以自定义ref

  • 自定义Hook =====> useDebugValue

    • useDebugValue用的比较少,自己看文档

方方提到最常用的React Hooks

  • useState

  • useReducer

  • useMemo

用得比较少的React Hooks

  • useRef

  • useContext

方方推荐的JS书籍:

你不知道的JavaScripthttps://u1lib.org/book/3635107/5efe33

最后更新于

这有帮助吗?