如果已经使用过 Hook,相信你一定回不去了,这种用函数的方式去编写有状态组件简直太爽啦。

如果还没使用过 Hook,那你要赶紧升级你的 React(v16.8+),投入 Hook 的怀抱吧。

至于 Hook 的好处这里就不多说了,上一篇已经讲过了——React Hook上车(一)

Hook 虽好,操作不当可是容易翻车的哦。

下面,我们就来聊聊在使用过程中可能遇到的坑吧......

useState

useState 只在组件首次渲染的时候执行

坑:useState的初始值,只在第一次有效

证据:

当点击按钮修改name的值的时候,我发现在Child组件,是收到了,但是并没有通过useState赋值给name!

const Child = ({data}) =>{
console.log('child render...', data) // 每次更新都会执行
const [name, setName] = useState(data) // 只会在首次渲染组件时执行
return (
<div>
<div>child</div>
<div>{name} --- {data}</div>
</div>
);
} const Hook =()=>{
console.log('Hook render...')
const [name, setName] = useState('rose')
return(
<div>
<div>
{count}
</div>
<button onClick={()=>setName('jack')}>update name </button>
<Child data={name}/>
</div>
)
}

想在第一次 render 前执行的代码放 useState() 里面

上面我们已经知道了useState()只会在第一次渲染的时候才执行,那么这有什么实用价值吗?答案:可以把第一次 render 前执行的代码放入其中。

例如:

const instance = useRef(null);
useState(() => {
instance.current = 'initial value';
});

类似 class component 里的constructorcomponentWillMount

useState 里数据必须为 immutable

啥?你还不知道 immutable 是个啥?甩手就是两个链接:Immutable.js 了解一下Immutable 详解及在 React 实践

什么是 Immutable Data?

首先,你要知道 JavaScript 中的对象一般是可变的(Mutable Data),因为使用了引用赋值。

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

虽然 class component 的 state 也提倡使用 immutable data,但不是强制的,因为只要调用了setState就会触发更新。

但是使用useState时,如果在更新函数里传入同一个对象将无法触发更新

证据:

const [list, setList] = useState([2,32,1,534,44]);
return (
<>
<ol>
{list.map(v => <li key={v}>{v}</li>)}
</ol>
<button
onClick={() => {
// bad:这样无法触发更新!
setList(list.sort((a, b) => a - b));
// good:必须传入一个新的对象!
setList(list.slice().sort((a, b) => a - b));
}}
>sort</button>
</>
)

useState 过时的闭包

之前就说过,Hook 产生问题时,90%都是闭包引起的。下面就来看一下这个诡异的bug:

function DelayCount() {
const [count, setCount] = useState(0); function handleClickAsync() {
setTimeout(function delay() {
setCount(count + 1); // 问题所在:此时的 count 为5s前的count!!!
}, 5000);
} function handleClickSync() {
setCount(count + 1);
} return (
<div>
{count}
<button onClick={handleClickAsync}>异步加1</button>
<button onClick={handleClickSync}>同步加1</button>
</div>
);
}

点击“异步加1”按键,然后立即点击“同步加1”按钮。你会惊奇的发现,count 只更新到 1。

这是因为 delay() 是一个过时的闭包。

来看看这个过程发生了什么:

  1. 初始渲染:count 值为 0。
  2. 点击“异步加1”按钮,delay() 闭包捕获 count 的值 0,setTimeout() 5秒后调用 delay()。
  3. 点击“同步加1”按钮,handleClickSync() 调用 setCount(0 + 1) 将 count 的值设置为 1,组件重新渲染。
  4. 5秒之后,setTimeout() 执行 delay() 。但是 delay() 中闭包保存 count 的值是初始渲染的值 0,所以调用 setState(0 + 1),结果count保持为 1。

每次 render 都会产生新的闭包。delay() 是一个过时的闭包,它使用在5s前捕获的过时的 count 变量。

为了解决这个问题,可以使用函数方法来更新 count 状态:

function DelayCount() {
const [count, setCount] = useState(0); function handleClickAsync() {
setTimeout(function delay() {
setCount(count => count + 1); // 重点:setCount传入的回调函数用的是最新的 state!!!
}, 5000);
} function handleClickSync() {
setCount(count + 1);
} return (
<div>
{count}
<button onClick={handleClickAsync}>异步加1</button>
<button onClick={handleClickSync}>同步加1</button>
</div>
);
}

关于 Hook 中的闭包:

useEffectuseMemouseCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state、props)。所以每一次这三种 Hook 的执行,反映的也都是当时的状态,无法获取最新的状态。对于这种情况,应该使用 ref 来访问。

useEffect

如何在 useEffect 中使用 async

上一篇文章中我们提到过:useEffect的 callback 函数要么返回一个能清除副作用的函数,要么就不返回任何内容。

而 async 函数返回的是 Promise 对象,那我们要怎么在 useEffect 的callback 中使用 async 呢?

最简单的方法是IIFE(自执行函数):

useEffect(() => {
(async () => {
await fetchSomething();
})();
}, []);

useEffect 死循环

  1. useEffect 在传入第二个参数时一定注意:第二个参数不能为引用类型,会造成死循环。
    比如:[]===[] 为false,所以会造成 useEffect 会一直不停的渲染。
  2. useEffect 的 callback 函数中改变的 state 一定不能在该 useEffect 的依赖数组中。比如:useEffect(()=>{ setCount(count); }, [count]);依赖 count,callback 中又 setCount(count)。

推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

函数作为依赖的时候死循环

有时候,我们需要将函数作为依赖项传入依赖数组中,例如:

// 子组件
let Child = React.memo((props) => {
useEffect(() => {
props.onChange(props.id)
}, [props.onChange, props.id]); return (
<div>{props.id}</div>
);
}); // 父组件
let Parent = () => {
let [id, setId] = useState(0);
let [count, setCount] = useState(0);
const onChange = (id) => {
// coding
setCount(id);
} return (
<div>
{count}
<Child onChange={onChange} id={id} /> // 重点:这里有性能问题!!!
</div>
);
};

代码中重点位置,每次父组件render,onChange引用值肯定会变。因此,子组件Child必定会render,子组件触发useEffect,从而再次触发父组件render....循环往复,这就会造成死循环。下面我们来优化一下:

// 子组件
let Child = React.memo((props) => {
useEffect(() => {
props.onChange(props.id)
}, [props.onChange, props.id]);
return (
<div>{props.id}</div>
);
}); // 父组件
let Parent = () => {
let [id, setId] = useState(0);
let [count, setCount] = useState(0);
const onChange = useCallback(() => { // 重点:通过useCallback包裹一层即可达到缓存函数的目的
// coding
}, [id]); // id 为依赖值
return (
<div>
{count}
<Child onChange={onChange} id={id} /> // 重点:这个onChange在每次父组件render都会改变!
</div>
);
};

useCallback将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useEffect 里面拿不到最新的props和state

useEffect里面使用到的 state 的值, 固定在了useEffect内部,不会被改变,除非useEffect刷新,重新固定 state 的值。

useRef保存任何可变化的值,.current属性总是取最新的值。就是相当于全局作用域,一处被修改,其他地方全更新...

function Example() {
const [count, setCount] = useState(0);
const latestCount = useRef(count); useEffect(() => {
// Set the mutable latest value
latestCount.current = count; setTimeout(() => {
// Read the mutable latest value
console.log(`You clicked ${latestCount.current} times`);
}, 3000);
});

总结

以上只是收集了一部分工作中可能会遇到的坑,大致分为2种:

  1. 闭包引起的 state 值过期
  2. 依赖值监听问题导致死循环

以后遇到其他的问题会继续补充...

参考:

react hooks踩坑记录

使用 JS 及 React Hook 时需要注意过时闭包的坑(文中有解决方法)

终于搞懂react hooks了!!!!

React Hook挖坑的更多相关文章

  1. [React] Detect user activity with a custom useIdle React Hook

    If the user hasn't used your application for a few minutes, you may want to log them out of the appl ...

  2. 使用React Hook后的一些体会

    一.前言 距离React Hook发布已经有一段时间了,笔者在之前也一直在等待机会来尝试一下Hook,这个尝试不是像文档中介绍的可以先在已有项目中的小组件和新组件上尝试,而是尝试用Hook的方式构建整 ...

  3. React Hook 学习

    1.官方文档 https://react.docschina.org/docs/hooks-intro.html 2.阮一峰 reactHook http://www.ruanyifeng.com/b ...

  4. React Hook:使用 useEffect

    React Hook:使用 useEffect 一.描述 二.需要清理的副作用 1.在 class 组件中 2.使用 effect Hook 的示例 1.useEffect 做了什么? 2.为什么在组 ...

  5. GraphQL + React Apollo + React Hook + Express + Mongodb 大型前后端分离项目实战之后端(19 个视频)

    GraphQL + React Apollo + React Hook + Express + Mongodb 大型前后端分离项目实战之后端(19 个视频) GraphQL + React Apoll ...

  6. GraphQL + React Apollo + React Hook 大型项目实战(32 个视频)

    GraphQL + React Apollo + React Hook 大型项目实战(32 个视频) GraphQL + React Apollo + React Hook 大型项目实战 #1 介绍「 ...

  7. React Hook上车

    React Hook 是 v16.8 的新功能,自诞生以来,受到广泛的好评,在 React 版本更新中具有里程碑的意义.现在都2020年了,再不上车 React Hook 就真的 out 了... H ...

  8. 【译】值得推荐的十大React Hook 库

    十大React Hook库 原文地址:https://dev.to/bornfightcompany/top-10-react-hook-libraries-4065 原文作者:Juraj Pavlo ...

  9. React Hook 入门使用

    React Hook 是什么 1.没有比官网说的更好的 HOOK 1. React Hook 官方 2. 用我们自己的话说,它是一个钩子函数,用来处理组件间的状态的一个方法,暂时理解为一个高阶函数吧. ...

随机推荐

  1. 基于 Redis 实现 CAS 操作

    基于 Redis 实现 CAS 操作 Intro 在 .NET 里并发情况下我们可以使用 Interlocked.CompareExchange 来实现 CAS (Compare And Swap) ...

  2. 数据分析你需要知道的操作:ETL和ELT

    如果您接触过数据仓库, 您可能会使用 ETL (Extract. Transform. Load) 或 ELT ( Extract.Load. Transform) 将您的数据从不同的来源提取到数据仓 ...

  3. python之迭代器 生成器 枚举 常用内置函数 递归

    迭代器 迭代器对象:有__next__()方法的对象是迭代器对象,迭代器对象依赖__next__()方法进行依次取值 with open('text.txt','rb',) as f: res = f ...

  4. CSS3新单位vw,vh,vmin,vmax详解

    1,vw,vh,vmin,vmax是由视窗Viewport大小来决定的,单位1,代表1%,是一种相对单位,只要是为响应式适配视窗的一种解决方案: vw:view width(视窗宽度)的百分比,1vw ...

  5. vue中数据请求的三种方法

    注意请求可能存在跨域问题,需要去配置好 这三种建议使用axios 1.resource Vue 要实现异步加载需要使用到 vue-resource 库. Vue.js 2.0 版本推荐使用 axios ...

  6. Design Patterns | 02 什么样的代码是好代码

    目录 01 - 什么是好的代码? 02 - 评价代码的标准有哪些 2.1 可维护性(maintainability) 2.2 可读性(readability) 2.3 可扩展性(extensibili ...

  7. js 面向对象 模拟日历

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. ajax 瀑布流 demo

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  9. js随机背景色 并显示色号

    今天重新看了一般原生js教程,看到一个例子 是点击按钮改变背景色. 我就改进了一下 点击按钮换一个颜色 并把色号给显示出来 <!DOCTYPE html><html><h ...

  10. Cisco二层交换机命令

    1.二层交换机基本配置 Switch >Switch >enable   # 进入特权模式 Switch#configure terminal   # 进入全局配置模式 Switch(co ...