使用React hooks,些许又多了不少摸鱼时间
Posted 星期一研究室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用React hooks,些许又多了不少摸鱼时间相关的知识,希望对你有一定的参考价值。
一文详解react-hooks
🎙️前言
相传, react 17
出了一个很强大的功能, 也就是 react hooks
。实际上, react hooks
有点类似与 vue3
的 composition API
,都是为了提升开发效率而出现。
那么,在下面的文章中,将从 0到1
开始,带大家了解 react hooks
,以及一些常用的 API
。
废话不多说,开始 react-hook
之旅叭~
一、📻概述
1、关于React Hooks
React Hooks
是一个可选功能,通常用class
组件 来和它做比较;100%
向后兼容,没有破坏性改动;- 不会取代
class
组件,尚无计划要移除class
组件。
2、认识React Hooks
(1)回顾React函数式组件
我们先来回顾下 class 组件 和 函数组件,具体如下:
class组件:
// class组件
class List extends React.Component
constructor()
super(props)
render()
const List = this.props
return <ul>list.map((item, index) =>
return <li key=item.id>
<span>item.title</span>
</li>
)</ul>
函数组件:
// 函数组件
function List(props)
const list = props
return <ul>list.map((item, index) =>
return <li key=item.id>
<span>item.title</span>
</li>
)</ul>
(2)函数组件的特点
函数组件的特点是:
- 没有组件实例;
- 没有生命周期;
- 没有
state
和setState
,只能接收props
。
(3)class组件的问题
上面我们说到了函数组件是一个纯函数,只能接收 props
,没有任何其他功能。而 class
组件拥有以上功能,但是呢,class
组件会存在以下问题:
- 大型组件很难拆分和重构,很难测试(即
class
不易拆分); - 相同业务逻辑,分散到各个方法中,逻辑混乱;
- 复用逻辑变得复杂,如
Mixins
、HOC
、Render Props
。
因此,有了以上问题的出现,也就有了 React Hooks
。
(4)React 组件
-
React
组件更易于用函数来表达: -
React
提倡函数式编程,即view=fn(props)
; -
函数更灵活,更易拆分,更易测试;
-
但函数组件太简单,需要增强能力 ——因此,有了
React Hooks
。
二、🪕几种 Hooks
1、State Hook🗞️
(1)让函数组件实现state和setState
- 默认函数组件是没有
state
的; - 函数组件是一个纯函数,执行完即销毁,无法存储
store
; - 需要
State Hook
,即把store
功能 “钩” 到纯函数中。
(2)举例阐述
假设我们现在要通过点击按钮,来修改设定的某个值。现在我们用 hooks
中的 useState
来处理。具体代码如下:
import React, useState from 'react'
function ClickCounter()
// 数组的解构
// useState 就是一个 Hook “钩”,最基本的一个 Hook
const [count, setCount] = useState(0) // 传入一个初始值,初始值可以是数值/字符串/数组/对象等类型
const [name, setName] = useState('星期一研究室')
// const arr = useState(0)
// const count = arr[0]
// const setCount = arr[1]
function clickHandler()
setCount(count + 1)
setName(name + '2021')
return <div>
<p>你点击了 count 次 name</p>
<button onClick=clickHandler>点击</button>
</div>
export default ClickCounter
此时浏览器的显示效果如下:
在上面的代码中, count
是一个 state
的值, setCount
是修改 state
的一个函数。同理, name
也是一个 state
的值, setName
是修改 name
值得一个函数。
如果使用 hooks
来修改 state
值的话,那么我们只需要以 const [count, setCount] = useState(0)
这种形式去进行,便可修改最后的值,而不需要像 class
组件那么复杂的去使用。
对于上面这个功能来说,如果使用 class
组件来实现的话,具体代码如下:
import React from 'react'
class ClickCounter extends React.Component
constructor()
super()
// 定义 state
this.state =
count: 0,
name: '星期一研究室'
this.clickHandler = this.clickHandler.bind(this)
render()
return <div>
<p>你点击了 this.state.count 次 this.state.name</p>
<button onClick=this.clickHandler>点击</button>
</div>
clickHandler()
// 修改 state
this.setState(
count: this.state.count + 1,
name: this.state.name + '2021'
)
export default ClickCounter
大家可以看到,如果使用 class
组件来解决的话,那么需要先定义 state
,然后再定义一个函数,再使用 setState
才能去修改值,这样似乎还麻烦了点。
相信到这里,大家已经感受到 hooks
的快乐之处了。
下面,我们来总结关于 useState
的一些知识点。
(3)useState使用总结
useState(xxx)
传入初始值,返回数组[state, setState]
;- 通过
state
获取值; - 通过
setState(xxx)
修改值。
(4)Hooks命名规范
- 规定所有的
Hooks
都要使用use
开头,如useXxx
; - 自定义
Hook
也要以use
开头; - 非
Hooks
的地方,尽量不要使用useXxx
写法,不然容易造成误解。
2、Effect Hook🗞️
(1)让函数组件模拟生命周期
- 默认函数组件没有生命周期;
- 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期;
- 使用
Effect Hook
把生命周期 “钩” 到纯函数中。
(2)举例阐述
同样地,我们用于 useState
相同的例子,来体验下 useEffect
。
我们先在 src|components
下建立一个文件,命名为 LiftCycles.js
。 具体代码如下:
import React, useState, useEffect from 'react'
function LifeCycles()
const [count, setCount] = useState(0)
const [name, setName] = useState('星期一研究室')
// 模拟 class 组件的 DidMount 和 DidUpdate
useEffect(() =>
console.log('在此发送一个 ajax 请求')
)
function clickHandler()
setCount(count + 1)
setName(name + '2020')
return <div>
<p>你点击了 count 次 name</p>
<button onClick=clickHandler>点击</button>
</div>
export default LifeCycles
现在,我们在 App.js
中注册该组件。具体代码如下:
import React, useState from 'react';
import LifeCycles from './components/LifeCycles'
function App()
const [flag, setFlag] = useState(true)
return (
<div
flag && <LifeCycles/>
</div>
);
export default App;
此时,浏览器的显示效果如下:
大家可以看到, useEffect
如果只传入一个函数的话,那么它模拟的是 DidMount
和 DidUpdate
这两个生命周期的功能。每当我们点击一次的时候,浏览器就会打印一次,也就是组件加载和组件更新是在一起进行的。
那如果我们想要让组件加载和组件更新分开实现呢,同样有办法。我们来改造下 LiftCycles.js
的代码。具体代码如下:
import React, useState, useEffect from 'react'
function LifeCycles()
const [count, setCount] = useState(0)
const [name, setName] = useState('星期一研究室')
// 模拟 class 组件的 DidMount
useEffect(() =>
console.log('加载完了')
, []) // 第二个参数是 [] (不依赖于任何 state)
// 模拟 class 组件的 DidUpdate
useEffect(() =>
console.log('更新了')
, [count, name]) // 第二个参数就是依赖的 state
function clickHandler()
setCount(count + 1)
setName(name + '2020')
return <div>
<p>你点击了 count 次 name</p>
<button onClick=clickHandler>点击</button>
</div>
export default LifeCycles
此时浏览器的显示效果如下:
大家可以看到,第一次的时候分别加载和更新了 1
次。等到我们去点击的时候,因为已经加载完了,所以这个时候就是更新了。
那我们来梳理一下, useEffect
在加载和更新的时候,分别是怎么进行处理的?
看上面的代码中我们可以发现,useEffect
还可以接收第二个参数,第二个参数如果空值,那么它不依赖于任何 state
,表示 componentDidMount
这个生命周期。相反,如果第二个参数没有依赖值或者接收依赖于 state 的值这两种情况时,那么它模拟 componentDidUpdate
这个生命周期。
说到这里,可能有的小伙伴还想问,那跟销毁相关的生命周期,又怎么处理呢?我们继续来对 LiftCycles.js
的代码进行改造。具体如下:
import React, useState, useEffect from 'react'
function LifeCycles()
const [count, setCount] = useState(0)
const [name, setName] = useState('星期一研究室')
// 模拟 class 组件的 DidMount
useEffect(() =>
let timerId = window.setInterval(() =>
console.log(Date.now())
, 1000)
// 返回一个函数
// 模拟 WillUnMount
return () =>
window.clearInterval(timerId)
, [])
function clickHandler()
setCount(count + 1)
setName(name + '2020')
return <div>
<p>你点击了 count 次 name</p>
<button onClick=clickHandler>点击</button>
</div>
export default LifeCycles
大家可以看以上代码,在这里,我们通过 return
一个函数,去模拟 componentWillUnMount
这个生命周期,来销毁每一次定时器中执行的任务。
到这里,相信大家对 useEffect
已经有了一定的了解。现在,我们来对 useEffect
做个小结。
(3)useEffect使用总结
- 模拟
componentDidMount - useEffect
依赖[]
; - 模拟
componentDidUpdate - useEffect
无依赖,或者依赖[a, b]
; - 模拟
componentWillUnMount - useEffect
中返回一个函数。
(4)useEffect让纯函数有了副作用
- 默认情况下,执行纯函数的时候,只需要输入参数并返回结果即可,是没有任何副作用的;
- 所谓副作用,就是对函数之外造成影响,如设置全局定时任务;
- 而组件需要副作用,所以需要
useEffect
“钩” 到纯函数中; - 因此,
useEffect
这个副作用并不是一件坏事。
(5)useEffect中返回函数 fn
有一种值得注意的情况是,在上面我们通过返回一个函数来模拟 WillUnMount
,但这个模拟后的结果还不完全等于 WillUnMount
。现在,我们在 src|components
下建立一个文件,命名为 FriendStatus.js
。具体代码如下:
import React, useState, useEffect from 'react'
function FriendStatus( friendId )
const [status, setStatus] = useState(false)
// DidMount 和 DidUpdate
useEffect(() =>
console.log(`开始监听 $friendId 在线状态`)
// 【特别注意】
// 此处并不完全等同于 WillUnMount
// props 发生变化,即更新,也会执行结束监听
// 准确的说:返回的函数,会在下一次 effect 执行之前,被执行
return () =>
console.log(`结束监听 $friendId 在线状态`)
)
return <div>
好友 friendId 在线状态:status.toString()
</div>
export default FriendStatus
App.js
的代码如下:
import React, useState from 'react';
import FriendStatus from './components/FriendStatus'
function App()
const [flag, setFlag] = useState(true)
const [id, setId] = useState(1)
return (
<div>
<div>
<button onClick=() => setFlag(false)>flag = false</button>
<button onClick=() => setId(id + 1)>id++</button>
</div>
flag && <FriendStatus friendId=id/>
</div>
);
export default App;
此时,浏览器的显示效果如下:
大家可以看到,当开始下一个监听时,会先结束掉上一个监听再开始下一个。正如图中所看到的,一开始我们正在监听 id
为 1
的好友,那当我们想要点击 id++
按钮去监听好友 2
时,useEffect
会先去结束掉 1
的状态,再让好友 2
上线。
此时,我们要注意的一个点是,执行结束监听的这个函数,执行的是 DidUpdate
生命周期,而不是 WillUnMount
生命周期。函数在这个时候是属于更新状态而不是销毁状态。
依据以上内容,我们来做个小结。具体如下:
- 当
useEffect
依赖于[]
的时候,在组件销毁时执行return
的函数fn
,等于WillUnMount
生命周期; - 当
useEffect
无依赖或依赖于[a, b]
的时候,组件是在更新时执行return
的函数fn
;即在下一次执行useEffect
之前,就会先执行fn
,此时模拟的是DidUpdate
生命周期。
3、其他Hooks🗞️
上面我们讲了两个比较常用的 hooks
→ useState
和 useEffect
。接下来,我们来了解其他几个比较不常用的 hooks
。
(1)useRef
Ref
是用于对 值的修改 和 DOM 元素的获取 。先来看一段代码:
import React, useRef, useEffect from 'react'
function UseRef()
const btnRef = useRef(null) // 初始值
const numRef = useRef(0)
numRef.current = 2;
useEffect(() =>
console.log(btnRef.current) // DOM 节点
console.log(numRef.current); // 得到值
, [])
return <div>
<button ref=btnRef>click</button>
</div>
export default UseRef
此时,浏览器的显示效果为:
大家可以看到,如果将 btnRef
绑定到 ref=btnRef
上,那么 btnRef.current
获取到的是当前的 DOM
节点。
另外一种情况是,大家定位到 numRef
,如果我们给其进行赋值并修改,那么 numRef.current
最终得到的值是修改后的值。
(2)useContext
很多时候,我们经常会有一些比较静态的属性需要做切换,比如:主题颜色切换。这个时候就需要用到 context
上下文来处理。那在 hook
中,就有 useContext
可以处理这件事情。先来看一段演示代码:
import React, useContext from 'react'
// 主题颜色
const themes =
light:
foreground: '#000',
background: '#eee'
,
dark:
foreground: '#fff',
background: '#222'
// 创建 Context
const ThemeContext = React.createContext(themes.light) // 初始值
function ThemeButton()
const theme = useContext(ThemeContext)
return <button style= background: theme.background, color: theme.foreground >
hello world
</button>
function Toolbar()
return <div>
<ThemeButton></ThemeButton>
</div>
function App()
return <ThemeContext.Provider value=themes.dark>
<Toolbar></Toolbar>
</ThemeContext.Provider>
export default App
此时浏览器的显示效果如下:
现在属于黑色背景的主题。如果我们要切换为白色背景的主题,那么只需要把最底下的代码 value=themes.dark
改为 value=themes.light
即可。
(3)useReducer
在 redux
中,我们会用到 reducer
,但 useReducer
跟 redux
还有一点区别。 useReducer
是对单个组件进行状态
先来看一段代码:
import React, useReducer from 'react'
const initialState = count: 0 以上是关于使用React hooks,些许又多了不少摸鱼时间的主要内容,如果未能解决你的问题,请参考以下文章