依赖项过多问题
a. 去掉不必要的依赖
b. 将 Hook 拆分为更小的单元,每个 Hook 依赖于各自的依赖数组
c. 合并相关的 state,将多个依赖值聚合为一个
d. 通过 setState 回调函数获取最新的 state,以减少外部依赖
e. 通过 ref 来读取可变变量的值,不过需要注意控制修改它的途径
useEffect 和生命周期函数不做类比,useEffect 单纯用作处理副作用
A "side effect" is anything that affects something outside the scope of the function being executed. These can be, say, a network request, which has your code communicating with a third party (and thus making the request, causing logs to be recorded, caches to be saved or updated, all sorts of effects that are outside the function.
受控组件:利用props,去修改内部的state
import React, { useState, useEffect } from 'react';
上次的一次搞定前端四大手写在知乎上收获了500多个赞,简直让我受宠若惊。今天就趁热打铁,写一下一次搞定七大 React Hooks,一方面是为了复习下 React,另一方面是跟小伙伴们分享一些我学习 React Hooks 时的心得体会。由于水平有限,目前只能从 React Hooks 的基本使用方法和使用要点上做些分享,关于 Hooks 的原理上的探究日后再做更新。
让我们先来看道字节面试题,题目是实现一个自定义的 Hook,实现点击切换状态。
七大 Hooks 都有哪些
useState
状态useEffect
钩子,还有它的兄弟useLayoutEffect
useContext
上下文useReducer
代替 ReduxuseMemo
缓存,还有它的小弟useCallback
useRef
引用自定义 Hook
混合useState
基本语法:
const [X, setX] = React.useState(X的初始值)
简单示例:
我们会发现,点击按钮之后,age 消失了,而我们明明只改了 name 呀,为什么呢?
简单来说就是前后是两个完全不相关的对象。
展开讲的话 React 在数据变化时会创建新的虚拟 DOM 对象,然后将这个虚拟 DOM 对象跟原虚拟 DOM 进行一个 DOM Diff,得到一个最小的变化过程 Patch,并把这个 Patch 渲染到页面上,Diff 的时候发现新对象没有 age 这个属性,于是就把它删除了。
于是在使用 useState 的时候我们需要注意两个地方:
setUser({...user, name: 'Janye'})
useEffect
useEffect
的作用主要是用来解决函数组件如何像类组件一样使用生命周期钩子的问题。它有三个使用场景:
[]
这里给一个最简单的例子:
那么它跟它的兄弟
useLayoutEffect
有什么区别呢?useEffect
在浏览器渲染完成后执行,useLayoutEffect
在浏览器渲染前执行,useLayoutEffect
总是比useEffect
先执行。那么为了用户体验(先渲染就能先看到),通常我们应该先用
useEffect
。useContext
如果我们想在组件之间共享状态的话,可以使用
useContext
。它的使用可以分为三个步骤:
C = createContext(initial)
创建上下文<C.provider>
圈定作用域useContext(C)
来使用上下文简单示例:
useReducer
如果要一句话解释
useReducer
的话,它是用来代替 Redux 的,或者说,是一个加强版的useState
。使用上来说,一共有四步:
写({type: '操作类型'})
这里给一个基本的示例:
useMemo
基本语法:
useMemo(回调函数, [依赖])
类似与 Vue 的计算属性 computed,useMemo 具有缓存,依赖改变才重新渲染的功能。
跟它的小弟
useCallback
的唯一区别是:useMemo
可以缓存所有对象,useCallback
只能缓存函数。useCallback(x => log(x), [m])
等价于useMemo(() => x => log(x), [m])
useRef
主要作用是创建一个数据的引用,并让这个数据在 render 过程中始终保持不变。
基本语法:
const count = useRef(0)
,读取用count.current
用法这里给大家参考一下我封装 Echarts 时的例子:
自定义 Hook
可以理解为我们可以把上面的 Hook 按照实际的需求混合起来,封装成一个函数,给一个简单示例:
Hooks 的一些最佳实践分享
useState
useEffect
interface Props { value: number, onChange: (num: number) => any }
export default function Counter({ value, onChange }: Props) { const [count, setCount] = useState(0);
useEffect(() => { value && setCount(value); }, [value]);
return [
{count}
; } ``` 在 useCount 中,increase 会随着 count 的变化而被重新创建。但是 increase 被重新创建之后, useEffect 并不会再次执行,所以 useEffect 中取到的 increase 永远都是首次创建时的 increase 。而首次创建时 count 的值为 0,因此无论点击多少次, count 的值永远都是 1。 解决方案: 1)通过 setState 回调,让函数不依赖外部变量 ``` export const useCount = () => { const [count, setCount] = useState(0); const [increase, decrease] = useMemo(() => { const increase = () => { setCount((latestCount) => latestCount + 1); }; const decrease = () => { setCount((latestCount) => latestCount - 1); }; return [increase, decrease]; }, []); // 保持依赖数组为空,这样 increase 和 decrease 方法都只会被创建一次 return [count, increase, decrease]; }; ``` 2)通过 ref 来保存可变变量 ``` export const useCount = () => { const [count, setCount] = useState(0); const countRef = useRef(count); useEffect(() => { countRef.current = count; }); const [increase, decrease] = useMemo(() => { const increase = () => { setCount(countRef.current + 1); }; const decrease = () => { setCount(countRef.current - 1); }; return [increase, decrease]; }, []); // 保持依赖数组为空,这样 increase 和 decrease 方法都只会被创建一次 return [count, increase, decrease]; }; ``` ### 自定义 Hook 1. 编写自定义 Hook 时,返回值一定要保持引用的一致性 ```js function Example() { const data = useData(); const [dataChanged, setDataChanged] = useState(false); useEffect(() => { setDataChanged((prevDataChanged) => !prevDataChanged); // 当 data 发生变化时,调用 setState。 // 如果 data 值相同而引用不同,就可能会产生非预期的结果。 }, [data]); console.log(dataChanged); return