- 团队再出一篇必读文档,必须要求每位同学,先读再写。
因此便有了此文。
本文主要讲两大点:
-
写hooks前的硬性要求;
-
写hooks常见的几个注意点。
硬性要求
1. 必须完整阅读一次React Hooks官方文档
英文文档:https://reactjs.org/docs/hooks-intro.html
中文文档:https://zh-hans.reactjs.org/docs/hooks-intro.html
其中重点必看hooks: useState、useReducer、useEffect、useCallback、useMemo
另外推荐阅读:
- Dan的《useEffect完全指南》:
https://link.zhihu.com/?target=https%3A//overreacted.io/zh-hans/a-complete-guide-to-useeffect/
- 衍良同学的《React Hooks完全上手指南》
https://zhuanlan.zhihu.com/p/92211533
2. 工程必须引入lint插件,并开启相应规则
lint插件:https://www.npmjs.com/package/eslint-plugin-react-hooks
必开规则:
{
“plugins”: [“react-hooks”],
“rules”: {
“react-hooks/rules-of-hooks”: “error”,
“react-hooks/exhaustive-deps”: “warn”
}
}
其中, react-hooks/exhaustive-deps
至少warn,也可以是error。建议全新的工程直接配"error",历史工程配"warn"。
切记,本条是硬性条件。
如果你的工程,当前没开启hooks lint rule,请不要编写任何hooks代码。如果你CR代码时,发现对方前端工程,没有开启相应规则,并且提交了hooks代码,请不要合并。该要求适应于任何一个React前端工程。
这两条规则会避免我们踩坑。虽然对于hooks新手,这个过程可能会比较“痛苦”。不过,如果你觉得这两个规则对你编写代码造成了困扰,那说明你还未完全掌握hooks。
如果对于某些场景,确实不需要「exhaustive-deps」,可在代码处加:
// eslint-disable-next-line react-hooks/exhaustive-deps
切记只能禁本处代码,不能偷懒把整个文件都禁了。
3. 如若有发现hooks相关lint导致的warning,不要全局autofix
除了hooks外,正常的lint基本不会改变代码逻辑,只是调整编写规范。但是hooks的lint规则不同,exhaustive-deps
的变化会导致代码逻辑发生变化,这极容易引发线上问题,所以对于hooks的waning,请不要做全局autofix操作。除非保证每处逻辑都做到了充分回归。
另外公司内部有个小姐姐补充道:eslint-plugin-react-hooks 从2.4.0版本开始,已经取消了 exhaustive-deps
的autofix。所以,请尽量升级工程的lint插件至最新版,减少出错风险。
然后建议开启vscode的「autofix on save」。未来无论是什么问题,能把error与warning 尽量遏制在最开始的开发阶段,保证自测跟测试时就是符合规则的代码。
常见注意点
依赖问题
依赖与闭包问题是一定要开启exhaustive-deps
的核心原因。最常见的错误即:mount时绑定事件,后续状态更新出错。
错误代码示例:(此处用addEventListener做onclick绑定,只是为了方便说明情况)
function ErrorDemo() {
const [count, setCount] = useState(0);
const dom = useRef(null);
useEffect(() => {
dom.current.addEventListener(‘click’, () => setCount(count + 1));
}, []);
return
}
这段代码的初始想法是:每当用户点击dom,count就加1。理想中的效果是一直点,一直加。但实际效果是 {count} 到「1」以后就加不上了。
我们来梳理一下, useEffect(fn, [])
代表只会在mount时触发。也即是首次render时,fn执行一次,绑定了点击事件,点击触发 setCount(count + 1)
。乍一想,count还是那个count,肯定会一直加上去呀,当然现实在啪啪打脸。
状态变更 触发 页面渲染的本质是什么?本质就是 ui = fn(props, state, context)
。props、内部状态、上下文的变更,都会导致渲染函数(此处就是ErrorDemo)的重新执行,然后返回新的view。
那现在问题来了, ErrorDemo
这个函数执行了多次,第一次函数内部的 count
跟后面几次的 count
会有关系吗?这么一想,感觉又应该没有关系了。那为什么 第二次又知道 count 是1,而不是0了呢?第一次的 setCount
跟后面的是同一个函数吗?这背后涉及到hooks的一些底层原理,也关系到了为什么hooks的声明需要声明在函数顶部,不允许在条件语句中声明。在这里就不多讲了。
结论是:每次 count
都是重新声明的变量,指向一个全新的数据;每次的 setCount
虽然是重新声明的,但指向的是同一个引用。
回到正题,我们知道了每次render,内部的count其实都是全新的一个变量。那我们绑定的点击事件方法,也即:setCount(count + 1)
,这里的count,其实指的一直是首次render时的那个count,所以一直是0 ,因此 setCount,一直是设置count为1。
那这个问题怎么解?
首先,应该遵守前面的硬性要求,必须要加lint规则,并开启autofix on save。然后就会发现,其实这个 effect
是依赖 count
的。autofix 会帮你自动补上依赖,代码变成这样:
useEffect(() => {
dom.current.addEventListener(‘click’, () => setCount(count + 1));
}, [count]);
那这样肯定就不对了,相当于每次count变化,都会重新绑定一次事件。所以对于事件的绑定,或者类似的场景,有几种思路,我按我的常规处理优先级排列:
思路1:消除依赖
在这个场景里,很简单,我们主要利用 setCount
的另一个用法 functional updates。这样写就好了:
() => setCount(prevCount => ++prevCount)
,不用关心什么新的旧的、什么闭包,省心省事。
思路2:重新绑定事件
那如果我们这个事件就是要消费这个count怎么办?比如这样:
dom.current.addEventListener(‘click’, () => {
console.log(count);
setCount(prevCount => ++prevCount);
});
我们不必执着于一定只在mount时执行一次。也可以每次重新render前移除事件,render后绑定事件即可。这里利用useEffect的特性,具体可以自己看文档:
useEffect(() => {
const $dom = dom.current;
const event = () => {
console.log(count);
setCount(prev => ++prev);
};
$dom.addEventListener(‘click’, event);
return () => $dom.removeEventListener(‘click’, event);
}, [count]);
思路3:如果嫌这样开销大,或者编写麻烦,也可以用 useRef
其实用 useRef
也挺麻烦的,我个人不太喜欢这样操作,但也能解决问题,代码如下:
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
dom.current.addEventListener(‘click’, () => {
console.log(countRef.current);
setCount(prevCount => {
const newCount = ++prevCount;
countRef.current = newCount;
return newCount;
});
});
}, []);
useCallback与useMemo
这两个api,其实概念上还是很好理解的,一个是「缓存函数」, 一个是缓存「函数的返回值」。但我们经常会懒得用,甚至有的时候会用错。
从上面依赖问题我们其实可以知道,hooks对「有没有变化」这个点其实很敏感。如果一个effect内部使用了某数据或者方法。若我们依赖项不加上它,那很容易由于闭包问题,导致数据或方法,都不是我们理想中的那个它。如果我们加上它,很可能又会由于他们的变动,导致effect疯狂的执行。真实开发的话,大家应该会经常遇到这种问题。
所以,在此建议:
-
在组件内部,那些会成为其他useEffect依赖项的方法,建议用
useCallback
包裹,或者直接编写在引用它的useEffect中。 -
己所不欲勿施于人,如果你的function会作为props传递给子组件,请一定要使用
useCallback
包裹,对于子组件来说,如果每次render都会导致你传递的函数发生变化,可能会对它造成非常大的困扰。同时也不利于react做渲染优化。
不过还有一种场景,大家很容易忽视,而且还很容易将useCallback与useMemo混淆,典型场景就是:节流防抖。
举个例子:
function BadDemo() {
const [count, setCount] = useState(1);
const handleClick = debounce(() => {
setCount(c => ++c);
}, 1000);
return
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
完整版面试题资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了。
回顾项目
往往在面试时,面试官根据你简历中的项目由点及面地展开问答,所以请对你做过的最好的项目进行回顾和反思。回顾你做过的工作和项目中最复杂的部分,反思你是如何完成这个最复杂的部分的。
面试官会重点问你最复杂的部分的实现方法和如何优化。重点要思考如何优化,即使你项目中没有对那部分进行优化,你也应该预先思考有什么优化的方案。如果这部分答好了,会给面试官留下很不错的印象。
重点在于基础知识
这里指的基础知识包括:前端基础知识和学科基础知识。
前端基础知识:html/css/js 的核心知识,其中 js 的核心知识尤为重要。比如执行上下文、变量对象/活动对象(VO/AO)、作用域链、this 指向、原型链等。
学科基础知识:数据结构、计算机网络、算法等知识。你可能会想前端不需要算法,那你可能就错了,在大公司面试,面试官同样会看重学生这些学科基础知识。
你可能发现了我没有提到React/Vue
这些框架的知识,这里得说一说,大公司不会过度的关注这方面框架的知识,他们往往更加考察学生的基础。
这里我的建议是,如果你至少使用或掌握其中一门框架,那是最好的,可以去刷刷相关框架的面试题,这样在面试过程中即使被问到了,也可以回答个 7788。如果你没有使用过框架,那也不需要太担心,把重点放在基础知识和学科基础知识之上,有其余精力的话可以去看看主流框架的核心思想。
重点在于基础知识
这里指的基础知识包括:前端基础知识和学科基础知识。
前端基础知识:html/css/js 的核心知识,其中 js 的核心知识尤为重要。比如执行上下文、变量对象/活动对象(VO/AO)、作用域链、this 指向、原型链等。
学科基础知识:数据结构、计算机网络、算法等知识。你可能会想前端不需要算法,那你可能就错了,在大公司面试,面试官同样会看重学生这些学科基础知识。
你可能发现了我没有提到React/Vue
这些框架的知识,这里得说一说,大公司不会过度的关注这方面框架的知识,他们往往更加考察学生的基础。
这里我的建议是,如果你至少使用或掌握其中一门框架,那是最好的,可以去刷刷相关框架的面试题,这样在面试过程中即使被问到了,也可以回答个 7788。如果你没有使用过框架,那也不需要太担心,把重点放在基础知识和学科基础知识之上,有其余精力的话可以去看看主流框架的核心思想。