本文是 React 系列的第二篇前端
想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!react
Hook 是 React 16.8 的新增特性。它可让你在不编写 类组件 的状况下使用 state
以及其余的 React
特性。git
状态逻辑复用难github
趋向复杂难以维护npm
this 指向困扰数组
this
优化类组件的三大问题dom
函数组件无 this 问题函数
自定义 Hook 方便复用状态逻辑post
反作用的关注点分离学习
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 可让你在函数组件中执行反作用操做。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于反作用。无论你知不知道这些操做,或是"反作用"这个名字,应该都在组件中使用过它们。
Mount 以后 对应 componentDidMount
Update 以后 对应 componentDidUpdate
Unmount 以前 对应 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);
}
})
复制代码
运行结果:
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。