80. Hooks各个击破
Hooks

useState
使用状态
注意事项1:不可局部更新
如果state是一个对象,能否部分setState?
答案是不行,请看示例代码
https://codesandbox.io/s/jirengudemo-t16xl?file=/src/App.js:0-365

会出现像图中这种,只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步走
创建初始值initialState
创建所有操作reducer(state,action)
传给useReducer,得到读和写API
调用写({type:'操作类型})
总的来说useReducer是useState的复杂版
示例代码
一个用useReducer的表单例子
用useReducer代替redux
步骤
将数据集中在一个store对象
将所有操作集中在reducer
创建一个Context
创建对数据的读写API
将第四部的内容放到第三部的Context
用Context.Provider将Context提供给所有组件
各个组件用useContext获取读写API
例子:
如何模块化?其实很简单
useContext
上下文
全局变量是全局的上下文。
上下文是局部的全局变量。
使用方法
之前的例子已经介绍过了。
使用C = createContext(initial)创建上下文。
使用<C.provider>圈定作用域。
在作用域内使用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/

总结
React Hooks
useState状态useEffect副作用=======>useLayoutEffectuseEffect就是afterRender
useLayoutEffect就是比useEffect提前一点点,很少用的原因是会影响渲染效果,特殊情况下会用
useContext上下文就是开放一个读写接口给整个页面用
ReduxuseReduceruseReducer是专门给Redux的用户设计的,可以不用redux
useMemo记忆=======>useCallback回调useMemo要和React.memo配合使用如果useMemo不好用,可以使用useCallback
useRef引用 ======>useImperativeHandleuseRef就是保持一个量不变forwardRef不是hookuseImperativeHandle就是setRef,在支持ref的时候可以自定义ref
自定义Hook =====>
useDebugValueuseDebugValue用的比较少,自己看文档
方方提到最常用的React Hooks
useStateuseReduceruseMemo
用得比较少的React Hooks
useRefuseContext
方方推荐的JS书籍:
你不知道的JavaScripthttps://u1lib.org/book/3635107/5efe33
最后更新于
这有帮助吗?