如果已经使用过 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. 对HTML语义化的一些理解和记录

    什么是HTML语义化 说HTML语义化就要先说说HTML到底负责的什么?下面摘自维基百科: 超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页 ...

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

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

  3. NetAnalyzer笔记 之 十一 打造自己的协议分析语言(1)初衷与语法构想

    回头看看NetAnalyzer开发系文档上次一篇竟然是2016年,老脸一红.不过这几年墨云成功过的讨到一个温柔贤淑的老婆,有了一个幸福的家庭,去年9月又有了一个大胖儿子,想想也就释然了^_^ 其实这几 ...

  4. 必备技能七、Vuex

    这段时间一直在用vue写项目,vuex在项目中也会依葫芦画瓢使用,但是总有一种朦朦胧胧的感觉.于是决定彻底搞懂它. 看了一下午的官方文档,以及资料,才发现vuex so easy! 作为一个圈子中的人 ...

  5. 写react项目要注意的事项

    1,className一定是大写字母开头,例如:App-logo,App,App-header. 2,有关react元素的更新,唯一办法是创建新元素,然后重新将其传入ReactDOM.render() ...

  6. JavaScript的函数(一)

    ,1,在javascript中,函数即对象.函数里面的参数可以是个函数,例如: data.sort(function(a,b){return a-b;}) 函数的返回值,return语句导致函数停止执 ...

  7. SpringFactoriesLoader解析

    一.SpringFactoriesLoader 介绍 1.1 SpringFactoriesLoader 简介 SpringFactoriesLoader 工厂加载机制是 Spring 内部提供的一个 ...

  8. 查看chrome插件源码

    简介 想查看chrome插件的源码,就需要找到chrome插件安装的位置,接着再文件夹下查找此插件的id. mac cd ~/Library/Application Support/Google/Ch ...

  9. 开源项目在闲鱼、b 站上被倒卖?这是什么骚操作?

    起因 - 又是一封邮件 2020 年 3 月 2 日,收到了一封邮件,对,这次故事的起因又是一封邮件,和上次写个bug被国家信息安全漏洞共享平台抓到了一样. 这是一条评论通知邮件,一开始我以为只是正常 ...

  10. C#制作密码文本框

    2020-03-14 每日一例第7天 1.新建窗体windowform,修改text值: 2.两个按钮后台代码: private void button1_Click(object sender, E ...