We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hook 是 React 16.8 的新增特性。它可以让你在不编写 类组件 的情况下使用 state 以及其他的 React 特性。
state
React
状态逻辑复用难
趋向复杂难以维护
this 指向困扰
this
优化类组件的三大问题
函数组件无 this 问题
自定义 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。
count
1
接下来我们使用 useState 来实现上述功能。
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。
class
this.setState
useState 让代码看起来简洁了,但是我们可能会对组件中,直接调用 useState 返回的状态会有些懵。既然 userState 没有传入任何的环境参数,它怎么知道要返回的的是 count 的呢,而且还是这个组件的 count 不是其它组件的 count。
userState
初浅的理解: useState 确实不知道我们要返回的 count,但其实也不需要知道,它只要返回一个变量就行了。数组解构的语法让我们在调用 useState 时可以给 state 变量取不同的名字。
useState 怎么知道要返回当前组件的 state?
因为 JavaScript 是单线程的。在 useState 被调用时,它只能在唯一一个组件的上下文中。
有人可能会问,如果组件内有多个 usreState,那 useState 怎么知道哪一次调用返回哪一个 state 呢?
usreState
这个就是按照第一次运行的次序来顺序来返回的。
接着上面的例子我们在声明一个 useState:
... const [count, setScount] = useState(0) const [name, setName] = useState('小智') ...
然后我们就可以断定,以后APP组件每次渲染的时候,useState 第一次调用一定是返回 count,第二次调用一定是返回 name。不信的话来做个实验:
APP
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 函数。
setCount
setName
为了防止我们使用 useState 不当,React 提供了一个 ESlint 插件帮助我们检查。
通过上述我们知道 useState 有个默认值,因为是默认值,所以在不同的渲染周期去传入不同的值是没有意义的,只有第一次传入的才有效。如下所示:
... const defaultCount = props.defaultCount || 0 const [count, setCount] = useState(defaultCount) ...
state 的默认值是基于 props,在 APP 组件每次渲染的时候 const defaultCount = props.defaultCount || 0 都会运行一次,如果它复杂度比较高的话,那么浪费的资料肯定是可观的。
props
const defaultCount = props.defaultCount || 0
useState 支持传入函数,来延迟初始化:
const [count, setCount] = useState(() => { return props.defaultCount || 0 })
Effect Hook 可以让你在函数组件中执行副作用操作。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。不管你知不知道这些操作,或是"副作用"这个名字,应该都在组件中使用过它们。
Mount 之后 对应 componentDidMount
componentDidMount
Update 之后 对应 componentDidUpdate
componentDidUpdate
Unmount 之前 对应 componentWillUnmount
componentWillUnmount
现在使用 useEffect 就可以覆盖上述的情况。
useEffect
为什么一个 useEffect 就能涵盖 Mount,Update,Unmount 等场景呢。
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 才能实时的更新。
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 内处理,实现关注点分离。
resize
我们在定义一个 useEffect,来看看通过不同参数,第二个参数的不同作用。
... useEffect(() => { console.log('count:', count) }, [count]) ...
第二个参数我们传入 [count], 表示只有 count 的变化时,我才打印 count 值,resize 变化不会打印。
[count]
运行效果如下:
第二个参数的三种形态,undefined,空数组及非空数组,我们都经历过了,但是咱们没有看到过回调函数的执行。
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
运行效果:
假如我们 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,欢迎 加群 互相学习。
https://github.com/qq449245884/xiaozhi
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
什么是 Hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 类组件 的情况下使用
state
以及其他的React
特性。类组件的不足
状态逻辑复用难
趋向复杂难以维护
this 指向困扰
this
Hooks 优势
优化类组件的三大问题
函数组件无 this 问题
自定义 Hook 方便复用状态逻辑
副作用的关注点分离
使用 State Hook
以上代码很好理解,点击按钮让
count
值加1
。接下来我们使用
useState
来实现上述功能。在这里,
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
:然后我们就可以断定,以后
APP
组件每次渲染的时候,useState
第一次调用一定是返回count
,第二次调用一定是返回name
。不信的话来做个实验:首先在外部声明一个 id,当 id为奇数和偶数的时候分别让 useState 调用方式相反,运行会看到有趣的现象。
会发现
count
和name
的取值串了。我们希望给 count 加1
,现在却给 name 加了1
,说明setCount
函数也串成了setName
函数。为了防止我们使用 useState 不当,React 提供了一个 ESlint 插件帮助我们检查。
优化点
通过上述我们知道
useState
有个默认值,因为是默认值,所以在不同的渲染周期去传入不同的值是没有意义的,只有第一次传入的才有效。如下所示:state
的默认值是基于props
,在 APP 组件每次渲染的时候const defaultCount = props.defaultCount || 0
都会运行一次,如果它复杂度比较高的话,那么浪费的资料肯定是可观的。useState
支持传入函数,来延迟初始化:使用 Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。不管你知不知道这些操作,或是"副作用"这个名字,应该都在组件中使用过它们。
副作用的时机
Mount 之后 对应
componentDidMount
Update 之后 对应
componentDidUpdate
Unmount 之前 对应
componentWillUnmount
现在使用
useEffect
就可以覆盖上述的情况。为什么一个
useEffect
就能涵盖Mount,Update,Unmount
等场景呢。useEffect 标准上是在组件每次渲染之后调用,并且会根据自定义状态来决定是否调用还是不调用。
第一次调用就相当于
componentDidMount
,后面的调用相当于componentDidUpdate
。useEffect
还可以返回另一个回调函数,这个函数的执行时机很重要。作用是清除上一次副作用遗留下来的状态。比如一个组件在第三次,第五次,第七次渲染后执行了
useEffect
逻辑,那么回调函数就会在第四次,第六次和第八次渲染之前执行。严格来讲,是在前一次的渲染视图清除之前。如果useEffect
是在第一次调用的,那么它返回的回调函数就只会在组件卸载之前调用了,也就是componentWillUnmount
。举粟说明一下:
上面主要做的就是网页
title
显示count
值,并监听网页大小的变化。这里用到了componentDidMount
,componentDidUpdate
等副作用,因为第一次挂载我们需要把初始值给 title, 当count
变化时,把变化后的值给它title
,这样title
才能实时的更新。注意,我们需要在两个生命周期函数中编写重复的代码。
这边我们容易出错的地方就是在组件结束之后要记住销毁事件的注册,不然会导致资源的泄漏。现在我们把 App 组件的副作用用
useEffect
实现。对于上述代码的第一个
useEffect
,相比类组件,Hooks 不在关心是 mount 还是 update。用useEffect
统一在渲染后调用,就完整追踪了count
的值。对于第二个
useEffect
,我们可以通过返回一个回调函数来注销事件的注册。回调函数在视图被销毁之前触发,销毁的原因有两种:重新渲染和组件卸载。这边有个问题,既然
useEffect
每次渲染后都执行,那我们每次都要绑定和解绑事件吗?当然是完全不需要,只要使用useEffect
第二个参数,并传入一个空数组即可。第二个参数是一个可选的数组参数,只有数组的每一项都不变的情况下,useEffect
才不会执行。第一次渲染之后,useEffect 肯定会执行。由于我们传入的空数组,空数组与空数组是相同的,因此useEffect
只会在第一次执行一次。这也说明我们把
resize
相关的逻辑放在一直写,不在像类组件那样分散在两个不同的生命周期内。同时我们处理 title 的逻辑与 resize 的逻辑分别在两个 useEffect 内处理,实现关注点分离。我们在定义一个 useEffect,来看看通过不同参数,第二个参数的不同作用。
第二个参数我们传入
[count]
, 表示只有 count 的变化时,我才打印count
值,resize
变化不会打印。运行效果如下:
第二个参数的三种形态,
undefined
,空数组及非空数组,我们都经历过了,但是咱们没有看到过回调函数的执行。现在有一种场景就是在组件中访问 Dom 元素,在 Dom元素上绑定事件,在上述的代码中添加以下代码:
新增一个 DOM 元素,在新的
useEffect
中监听span
元素的点击事件。运行效果:
假如我们 span 元素可以被销毁重建,我们看看会发生什么情况,改造一下代码:
运行效果:
可以看出一旦 dom 元素被替换,我们绑定的事件就失效了,所以咱们始终要追踪这个dom 元素的最新状态。
使用
useEffect
,最合适的方式就是使用回调函数来处理了,同时要保证每次渲染后都要重新运行,所以不能给第二次参数设置[]
,改造如下:运行结果:
参考
交流
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered: