内容简介:本文是 React 系列的第二篇Hook 是 React 16.8 的新增特性。它可以让你在不编写优化类组件的三大问题
本文是 React 系列的第二篇
想阅读更多优质文章请 猛戳GitHub博客 ,一年百来篇优质文章等着你!
什么是 Hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 类组件 的情况下使用 state
以及其他的 React
特性。
类组件的不足
状态逻辑复用难
- 缺少复用机制
- 渲染属性和高阶组件导致层级冗余
趋向复杂难以维护
- 生命周期函数混杂不相干逻辑
- 相干逻辑分散在不同生命周期
this 指向困扰
this
Hooks 优势
优化类组件的三大问题
- 函数组件无 this 问题
- 自定义 Hook 方便复用状态逻辑
- 副作用的关注点分离
使用 State Hook
import React, {Component} from 'react' class App extends Component { state = { count: 0 }; render() { const {count} = this.state; return ( <button type="button" onClick={() => { this.setState({ count: count + 1 }) }} >Click({count})</button> ) } } export default App;
以上代码很好理解,点击按钮让 count
值加 1
。
接下来我们使用 useState
来实现上述功能。
import React, {useState} from 'react' function App () { const [count, setCount] = useState(0) return ( <button type="button" onClick={() => {setCount(count + 1) }} >Click({count})</button> ) }
在这里, useState
就是一个 Hook。通过在函数组件里调用它来给组件添加一些内部 state
,React 会在重复渲染时保留这个 state
。
useState
会返回一对值 :当前状态 和一个让你更新它的函数。你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class
组件的 this.setState
,但是它不会把新的 state
和旧的 state
进行合并。 useState
唯一的参数就是初始 state
。
useState
让代码看起来简洁了,但是我们可能会对组件中,直接调用 useState
返回的状态会有些懵。既然 userState
没有传入任何的环境参数,它怎么知道要返回的的是 count
的呢,而且还是这个组件的 count
不是其它组件的 count
。
初浅的理解: useState
确实不知道我们要返回的 count
,但其实也不需要知道,它只要返回一个变量就行了。 数组解构 的语法让我们在调用 useState
时可以给 state
变量取不同的名字。
useState
怎么知道要返回当前组件的 state
?
因为 JavaScript 是单线程的。在 useState
被调用时,它只能在唯一一个组件的上下文中。
有人可能会问,如果组件内有多个 usreState
,那 useState
怎么知道哪一次调用返回哪一个 state
呢?
这个就是按照第一次运行的次序来顺序来返回的。
接着上面的例子我们在声明一个 useState
:
... const [count, setScount] = useState(0) const [name, setName] = useState('小智') ...
然后我们就可以断定,以后 APP
组件每次渲染的时候, useState
第一次调用一定是返回 count
,第二次调用一定是返回 name
。不信的话来做个实验:
let id = 0 function App () { let name,setName; let count,setCount; id += 1; if (id & 1) { // 奇数 [count, setCount] = useState(0) [name, setName] = useState('小智') } else { // 偶数 [name, setName] = useState('小智') [count, setCount] = useState(0) } return ( <button type="button" onClick={() => {setCount(count + 1) }} > Click({count}), name ({name}) </button> ) }
首先在外部声明一个 id,当 id为奇数和偶数的时候分别让 useState 调用方式相反,运行会看到有趣的现象。
当前版本如果写的顺序不一致就会报错。
会发现 count
和 name
的取值串了。我们希望给 count 加 1
,现在却给 name 加了 1
,说明 setCount
函数也串成了 setName
函数。
为了防止我们使用 useState 不当,React 提供了一个 ESlint 插件帮助我们检查。
优化点
通过上述我们知道 useState
有个默认值,因为是默认值,所以在不同的渲染周期去传入不同的值是没有意义的,只有第一次传入的才有效。如下所示:
... const defaultCount = props.defaultCount || 0 const [count, setCount] = useState(defaultCount) ...
state
的默认值是基于 props
,在 APP 组件每次渲染的时候 const defaultCount = props.defaultCount || 0
都会运行一次,如果它复杂度比较高的话,那么浪费的资料肯定是可观的。
useState
支持传入函数,来延迟初始化:
const [count, setCount] = useState(() => { return props.defaultCount || 0 })
使用 Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。不管你知不知道这些操作,或是"副作用"这个名字,应该都在组件中使用过它们。
副作用的时机
componentDidMount componentDidUpdate componentWillUnmount
现在使用 useEffect
就可以覆盖上述的情况。
为什么一个 useEffect
就能涵盖 Mount,Update,Unmount
等场景呢。
useEffect 标准上是在组件每次渲染之后调用,并且会根据自定义状态来决定是否调用还是不调用。
第一次调用就相当于 componentDidMount
,后面的调用相当于 componentDidUpdate
。 useEffect
还可以返回另一个回调函数,这个函数的执行时机很重要。作用是清除上一次副作用遗留下来的状态。
比如一个组件在第三次,第五次,第七次渲染后执行了 useEffect
逻辑,那么回调函数就会在第四次,第六次和第八次渲染之前执行。严格来讲,是在前一次的渲染视图清除之前。如果 useEffect
是在第一次调用的,那么它返回的回调函数就只会在组件卸载之前调用了,也就是
componentWillUnmount
。
如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount
, componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
举粟说明一下:
class App extends Component { state = { count: 0, size: { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } }; onResize = () => { this.setState({ size: { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } }) } componentDidMount () { document.title = this.state.count; window.addEventListener('resize', this.onResize, false) } componentWillMount () { window.removeEventListener('resize', this.onResize, false) } componentDidUpdate () { document.title = this.state.count; } render() { const {count, size} = this.state; return ( <button type="button" onClick={() => {this.setState({count: count + 1})}} > Click({count}) size: {size.width}x{size.height} </button> ) } }
上面主要做的就是网页 title
显示 count
值,并监听网页大小的变化。这里用到了 componentDidMount
, componentDidUpdate
等副作用,因为第一次挂载我们需要把初始值给 title, 当 count
变化时,把变化后的值给它 title
,这样 title
才能实时的更新。
注意,我们需要在两个生命周期函数中编写重复的代码。
这边我们容易出错的地方就是在组件结束之后要 记住销毁事件的注册 ,不然会导致资源的泄漏。现在我们把 App 组件的副作用用 useEffect
实现。
function App (props) { const [count, setCount] = useState(0); const [size, setSize] = useState({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }); const onResize = () => { setSize({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } ) } useEffect(() => { document.title = count; }) useEffect(() => { window.addEventListener('resize', onResize, false); return () => { window.removeEventListener('resize', onResize, false) } }, []) return ( <button type="button" onClick={() => {setCount(count + 1) }} > Click({count}) size: {size.width}x{size.height} </button> ) }
对于上述代码的第一个 useEffect
,相比类组件,Hooks 不在关心是 mount 还是 update。用 useEffect
统一在渲染后调用,就完整追踪了 count
的值。
对于第二个 useEffect
,我们可以通过返回一个回调函数来注销事件的注册。回调函数在视图被销毁之前触发,销毁的原因有两种: 重新渲染和组件卸载 。
这边有个问题,既然 useEffect
每次渲染后都执行,那我们每次都要绑定和解绑事件吗?当然是完全不需要,只要使用 useEffect
第二个参数,并传入一个空数组即可。第二个参数是一个可选的数组参数,只有数组的每一项都不变的情况下, useEffect
才不会执行。第一次渲染之后,useEffect 肯定会执行。由于我们传入的空数组,空数组与空数组是相同的,因此 useEffect
只会在第一次执行一次。
这也说明我们把 resize
相关的逻辑放在一直写,不在像类组件那样分散在两个不同的生命周期内。同时我们处理 title 的逻辑与 resize 的逻辑分别在两个 useEffect 内处理,实现关注点分离。
我们在定义一个 useEffect,来看看通过不同参数,第二个参数的不同作用。
... useEffect(() => { console.log('count:', count) }, [count]) ...
第二个参数我们传入 [count]
, 表示只有 count 的变化时,我才打印 count
值, resize
变化不会打印。
运行效果如下:
第二个参数的三种形态, undefined
,空数组及非空数组,我们都经历过了,但是咱们没有看到过回调函数的执行。
现在有一种场景就是在组件中访问 Dom 元素,在 Dom元素上绑定事件,在上述的代码中添加以下代码:
... const onClick = () => { console.log('click'); } useEffect(() => { document.querySelector('#size').addEventListener('click', onClick, false); },[]) return ( <div> ... <span id="size">size: {size.width}x{size.height}</span> </div> )
新增一个 DOM 元素,在新的 useEffect
中监听 span
元素的点击事件。
运行效果:
假如我们 span 元素可以被销毁重建,我们看看会发生什么情况,改造一下代码:
return ( <div> ... </button> { count%2 ? <span id="size">我是span</span> : <p id='size'>我是p</p> } </div>
运行效果:
可以看出一旦 dom 元素被替换,我们绑定的事件就失效了,所以咱们始终要追踪这个dom 元素的最新状态。
使用 useEffect
,最合适的方式就是使用回调函数来处理了,同时要保证每次渲染后都要重新运行,所以不能给第二次参数设置 []
,改造如下:
useEffect(() => { document.querySelector('#size').addEventListener('click', onClick, false); return () => { document.querySelector('#size').removeEventListener('click', onClick, false); } })
运行结果:
参考
- React 官方文档
- 《React劲爆新特性Hooks 重构去哪儿网》
交流
- React 官方文档
- 《React劲爆新特性Hooks 重构去哪儿网》
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
https://github.com/qq44924588...
我是小智,公众号「大迁世界」作者, 对前端技术保持学习爱好者。我会经常分享自己所学所看的干货 ,在进阶的路上,共勉!
关注公众号,后台回复 福利 ,即可看到福利,你懂的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Fetch 的实例讲解
- javaScript 常用 API 实例讲解
- C++ 运算符重载讲解与经典实例
- React 新特性 Hooks 讲解及实例(三)
- php通过pecl方式安装扩展的实例讲解
- PHP运用foreach神奇的转换数组(实例讲解)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。