如果已经使用过 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. Git 相关问题分享,git reset与git revert的区别?

    1.如果我在git add 后想要撤销操作,该怎么做? 使用 git rm --cache [文件名/ *] 或者 git reset HEAD, 为什么这个命令也会有效果呢,实际上reset将 HE ...

  2. Swagger2 最全注解说明

    原文链接:https://blog.csdn.net/xiaojin21cen/article/details/78654652 文章目录1.swagger2 注解整体说明2.@Api:请求类的说明3 ...

  3. boostrap3 bootstrap-datetimepicker.min.js设置中文语言

    问题 bootstrap3中使用bootstrap-datetimepicker遇到设置中文语言的问题 解决办法 bootstrap-datetimepicker在使用的时候要先引入momentjs中 ...

  4. Nginx server name配置子域名二级域名

    绑定子域名到不同目录(子站) 网站的目录结构为 /var/www/html: ├── fx └── blog└── photo html为nginx的默认网站目录. sudo vi /etc/ngin ...

  5. Python——五分钟带你弄懂迭代器与生成器,夯实代码能力

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是周一Python专题,给大家带来的是Python当中生成器和迭代器的使用. 我当初第一次学到迭代器和生成器的时候,并没有太在意,只是觉 ...

  6. java的权限控制

    java有三个权限的关键字:public.private.protected public:对任何人都可以使用. private:只有类的创建者和内部的方法可以使用,它的导出类也不可以访问. prot ...

  7. JavaScript的类数组

    类数组对象啊,被人问到它跟真正的数组对象有什么差别啊?说不上来就老埋汰了,只知道函数的arguments对象是个类数组对象,也有length属性,其他呢?干货奉上: 首先先说说数组吧: 1,当有新的元 ...

  8. 深入理解yield from语法

    本文目录 为什么要使用协程 yield from的用法详解 为什么要使用yield from . 为什么要使用协程# 在上一篇中,我们从生成器的基本认识与使用,成功过渡到了协程. 但一定有许多人,只知 ...

  9. Docker 技术系列之安装Docker Desktop for Mac

    终于要进入到Docker技术系列了,感谢大家的持续关注. 为什么要选择Docker?因为Docker 轻巧快速,提供了可行.经济.高效的替代方案.举个例子,安装Nginx,Mysql,Redis等常用 ...

  10. [译]ABP框架v2.3.0已经发布!

    在新冠病毒的日子里,我们发布了ABP框架v2.3, 这篇文章将说明本次发布新增内容和过去的两周我们做了什么. 关于新冠病毒和我们的团队 关于冠状病毒的状况我们很难过.在Volosoft的团队,我们有不 ...