1.Hooks概念理解
1. 什么是hooks
让你在不编写class的情况下使用state以及其他的React特性。
Hooks的本质:一套能够使函数组件更强大,更灵活的“钩子”
React体系里组件分为 类组件 和 函数组件
函数组件是有利于逻辑拆分与重用的组件表达形式。而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以从react v16.8开始,Hooks应运而生。
注意点:
- 有了hooks之后,为了兼容老版本,class类组件并没有被移除,都可以使用
- 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
- hooks只能在函数组件中使用
2.Hooks解决了什么问题
Hooks的出现解决了2个问题 1. 组件的状态逻辑复用 2.class组件自身的问题
-
组件的逻辑复用
在hooks出现之前,react先后尝试了 mixins混入,HOC高阶组件,render-props等模式,但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等
-
class组件自身的问题
class组件大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的组件。
3. Hooks优势
- 告别难以理解的class
- 解决业务逻辑难以拆分的问题
- 使状态逻辑复用变得更加简单
- 函数组件在设计思想上,更加契合React的理念
- 一套能够使函数组件更强大,更灵活的“钩子”
2.useState
1.基础使用
1.作用:
useState为函数组件提供状态(state)。
2.使用步骤:
- 导入
useState
函数· - 调用
useState
函数,并传入状态的初始值(必须在函数组件中) - 从
useState
函数的返回值中,拿到状态和修改状态的方法 - 在JSX中展示状态
- 调用修改状态的方法更新状态
3.代码实现:
返回值为:当前state以及更新state的函数
import { useState } from 'react'
function App() {
// 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
// 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
const [count, setCount] = useState(0)
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
点击按钮后,会看见state改变
总结:
- 允许你在React函数组件中添加state的Hook。
- 返回值为:包含当前state以及更新state的函数的数组。
- const [count, setCount] = useState(0)
- count是state变量,接受0;用来初始化state
- 首次渲染,将初始值给到count;后续只要调用了setCount,就会将最新值给到count
- setCount是改变state的函数,只要调用setCount一次,整个App中的代码都会执行
- useState函数可以执行多次,每调用一次为函数组件提供一个状态
- 注意:整个只能在函数组件中使用
2. 状态的读取和修改
1.读取状态
该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用
2.修改状态
- setCount是一个函数,参数表示
最新的状态值
- 调用该函数后,将使用新值替换旧值
- 修改状态后,由于状态发生变化,会引起视图变化
3.代码实现:
1.userState传过来的参数,作为count的初始值
2.[count,setCount]:是一个解构赋值,useState返回值是一个数组
3.名字可以自定义,保持语义化
4.顺序不可以换,第一参数是数据状态,第二参数是修改数据的方法
5.setCount函数:用来修改count,依然保持不能直接修改原值,还是生成一个新值替换原值
setCount(基于原值计算得到的新值)
6.count和setCount是一对:是绑在一起的setCount只能用于修改对应的count值
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
注意事项:
修改状态的时候,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型
3.组件的更新过程
函数组件使用 useState hook 后的执行过程,以及状态值的变化
- 组件第一次渲染
- 从头开始执行该组件中的代码逻辑
- 调用
useState(0)
将传入的参数作为状态初始值,即:0 - 渲染组件,此时,获取到的状态 count 值为: 0
- 组件第二次渲染
- 点击按钮,调用
setCount(count + 1)
修改状态,因为状态发生改变,所以,该组件会重新渲染 - 组件重新渲染时,会再次执行该组件中的代码逻辑
- 再次调用
useState(0)
,此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1 - 再次渲染组件,此时,获取到的状态 count 值为:1
- 点击按钮,调用
注意:useState 的初始值(参数)只会在组件第一次渲染时生效。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值
import { useState } from 'react'
// 首次渲染:组件内部的代码会被执行一次
// 更新渲染:setCount都会更新:
// 1.app组件会再次渲染,这个函数会再次执行
// 2.useState再次执行,得到的是新的count值,不是0,而是修改后的1
// 模板会用新值渲染
function App() {
// count:数据状态
// setCount:修改count的函数(专有函数)
const [count, setCount] = useState(0)
// 在这里可以进行打印测试
console.log(count)
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
4. 使用规则
1.useState函数可以执行多次
每次执行互相独立,每调用一次为函数组件提供一个状态
function List(){
// 以字符串为初始值
const [name, setName] = useState('cp')
// 以数组为初始值
const [list,setList] = useState([])
}
2.useState注意事项
1.只能出现在函数组件中
2.不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook)
//错误写法
let num = 1
function List(){
num++
if(num / 2 === 0){
const [name, setName] = useState('cp')
}
const [list,setList] = useState([])
}
// 俩个hook的顺序不是固定的,这是不可以的!!!
3.可以通过开发者工具component查看hooks状态
4.只能在React的函数组件中调用Hook,不要在其他的js函数中调用(还有自定义Hook可以调用Hook)
3.useEffect
1. 理解函数副作用
1.什么是副作用
- 副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。
- 对于 React 组件来说,主作用就是根据数据(state/props)去渲染 UI,除此之外都是副作用(比如:数据获取,订阅或者手动修改 DOM)
2.常见的副作用
- 数据请求 ajax发送
- 手动修改dom
- localstorage操作
3.Effect Hook
useEffect函数的作用就是为react函数组件提供副作用处理的。
useEffect
就是一个 Effect Hook,给函数组件增加了操作副作用的能力。- 它跟 class 组件中的
componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。
2. 基础使用
1.作用
为react函数组件提供副作用处理
2.使用步骤
- 导入
useEffect
函数 - 调用
useEffect
函数,并传入回调函数 - 在回调函数中编写副作用处理(dom操作)
- 修改数据状态(当我们通过修改状态更新组件时,副组件也会不断执行)
- 检测副作用是否生效
3.代码实现
在修改数据后,把count值放到页面标题中
import { useEffect, useState } from 'react'
function App() {
const [count, setCount] = useState(0)
useEffect(()=>{
// dom操作
// 定义副作用
document.title = `当前已点击了${count}次`
document.title=count
})
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
案例:
定义state变量count,让页面的标题为count,并且随着count一起变化
使用类组件
class App extends React.Component {
state = { count: 0 }
changeCount = () => {
this.setState({ count: this.state.count + 1 })
}
componentDidMount () {
document.title = this.state.count
}
componentDidUpdate () {
document.title = this.state.count
}
render () {
return (
<div>
<h2>{this.state.count}</h2>
<button onClick={this.changeCount}>count+1</button>
</div>
)}}
点击+1后,页面的值变了,但是页面标题没有改变。跟生命周期有关 ,因为生命周期只会执行一次,在页面渲染完毕后,componentDidMount会执行一次,然后就不会执行了,标题就一直等于0
所以,在页面改变后,还要触发一个生命周期函数componentDidUpdate
//当页面发生更新后,就会触发这个生命函数
componentDidUpdate () {
document.title = this.state.count
}
在class中,需要在两个生命周期函数中编写重复的代码, 会臃肿。
使用函数组件
import { useEffect, useState } from 'react'
function App () {
const [count, setCount] = useState(0)
//组件在首次渲染的时候执行一次
//当组件修改状态更新组件的时候,副作用不断执行
useEffect(() => {
console.log(111)
// 定义副作用
document.title = count
})
return (
<div>
<h2>{count}</h2>
<button onClick={() => { setCount(count + 1) }}>{count}</button>
</div>
)}
会执行两次,因为 <React.StrictMode>,会执行完一次后,在执行一次,用来检查组件的语法
页面首次渲染执行一次,然后每次更新都会执行。
3.依赖项控制执行时机
- useEffect--> 告诉 React 组件需要在渲染后执行某些操作。
- React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。
- Hook使用了js的闭包机制
1. 不添加依赖项
组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
- 组件初始渲染
- 组件更新 (不管是哪个状态引起的更新)
useEffect(()=>{
console.log('副作用执行了')
})
- 不添加依赖项:首次渲染的时候执行一次,更新状态的时候不断执行。
2. 添加空数组
组件只在首次渲染时执行一次。
useEffect(()=>{
console.log('副作用执行了')
},[])
- 第二个参数依赖项 [ ]:控制执行时机。
- 依赖项为空数组,则只有首次页面渲染的时候执行一次。
3. 添加特定依赖项
副作用函数在首次渲染时执行,在依赖项发生变化时重新执行
添加特定依赖项, 依赖项改变多少次,则执行多少次。
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('zs')
useEffect(() => {
console.log('副作用执行了')
}, [count])
return (
<>
<button onClick={() => { setCount(count + 1) }}>{count}</button>
<button onClick={() => { setName('cp') }}>{name}</button>
</>
)
}
注意事项
useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
// 有count和name,所以都要添加到依赖项数组中
useEffect(() => {
// dom操作
// 定义副作用
document.title = `当前已点击了${count}次`
document.title = count
console.log(name)
}, [count, name])
// 此时:初始化+count/name被修改时都会执行副作用函数
某种意义上,hook的出现,就是想不用生命周期概念也可以写业务代码
总结:
依赖项:控制副作用的执行时机
1.默认状态(无依赖项)
组件初始化时,先执行一次,等到每次数据修改组件更新再次执行(后续组件更新几次执行几次)
2.添加一个空数组依赖项
组件初始化的时候,执行一次,后续不再执行。
3.依赖特定项
组件初始化时,执行一次,依赖的特定项发送变化会再次执行 (后续依赖项改变几次执行几次)
阶段小练习 - 自定义hook
需求描述**:自定义一个hook函数,实现获取滚动距离Y
const [y] = useWindowScroll()
1.创建hooks/useWindowScroll.js,填入以下内容
import { useState } from "react"
export function useWindowScroll () {
// geuy赋值,有固定的写法:setY
const [y, setY] = useState(0)
// 在滚动行为发生时,不断获取滚动值,然后交给y
window.addEventListener('scroll', () => {
const h = document.documentElement.scrollTop
setY(h)
})
return [y]
}
2.在App.js中使用此函数
import { useWindowScroll } from './hooks/useWindowSroll'
function App () {
const [y] = useWindowScroll()
return (
<div style={{ height: '12000px' }}>
{y}
</div>
)
}
export default App
需求描述: 自定义hook函数,可以自动同步到本地LocalStorage
const [message, setMessage] = useLocalStorage(key,defaultValue)
- message可以通过自定义传入默认初始值
- 每次修改message数据的时候 都会自动往本地同步一份
1.创建useLocalStorage.js
import { useEffect, useState } from 'react'
export function useLocalStorage (key, defaultValue) {
const [message, setMessage] = useState(defaultValue)
// 每次只要message变化 就会自动同步到本地ls
useEffect(() => {
// 当数据变化时,往里面存入一份
window.localStorage.setItem(key, message)
}, [message, key])
return [message, setMessage]
}
2.导入到App.js中使用
import { useWindowScroll } from './hooks/useWindowSroll'
import { useLocalStorage } from './hooks/useLocalStorage'
function App () {
const [y] = useWindowScroll()
const [message, setMessage] = useLocalStorage('hook-key', 'zz')
// 5秒后。从zz变成cp->Application的Key
setTimeout(() => {
setMessage('cp')
}, 5000)
return (
<div style={{ height: '12000px' }}>
{y}
{message}
</div>
)
}
export default App