目录
Hook概念
- Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
- Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数
- Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React
为什么用Hook
1. 更简洁:使用 Hook 可以让你的代码更加简洁和易读。相比于传统的类组件,使用函数组件配合 Hook 可以省去冗余的样板代码和生命周期方法,使代码更加简洁明了。
2. 更容易复用逻辑:Hook 提供了一种将组件逻辑进行复用的方式。通过将相关逻辑封装成一个自定义的 Hook,可以在多个组件中共享该逻辑,达到代码的复用,提高开发效率。
3. 更好的可测试性:由于 Hook 的函数形式,可以更方便地对组件的逻辑进行单元测试。你可以直接针对 Hook 函数进行测试,而不用关心组件的渲染和生命周期方法。
4. 更好的状态管理:Hooks 提供了 `useState` 和 `useReducer` 来管理组件的状态。通过这些钩子,可以让状态逻辑更加清晰和简洁,并且可以使逻辑更加分离,方便管理和追踪状态的变化。
5. 更好的性能优化:使用 Hook 的 `useCallback` 和 `useMemo` 可以帮助你避免不必要的重复计算和重渲染,从而提升性能。
Hook的优点
1. 函数式编程:使用 Hook 可以将组件逻辑封装成可复用的函数,而不用依赖类组件和面向对象编程的概念。这样可以更方便地编写纯粹的函数式代码、遵循单一责任原则,并且可以更容易地进行测试和调试。
2. 代码复用:Hook 的特性使得许多常见的逻辑可以在组件之间进行复用。通过自定义 Hook,可以将一些通用逻辑抽象出来,并在多个组件中共享。这样可以减少重复代码的编写,提高代码的可维护性和可读性。
3. 无需编写类组件:使用 Hook 可以避免编写类组件和相关的生命周期方法。类组件的结构通常会更复杂,包含了 constructor、render、componentDidMount 等生命周期方法,而 Hook 可以将状态逻辑和副作用操作直接写在函数组件中,简化了组件的结构和书写方式。
4. 更灵活的状态管理:通过 useState 和 useReducer 等 Hook,可以更方便地管理组件的状态。相比于传统的 setState 方法,Hook 提供了更直观和简洁的方式来定义和更新状态变量,从而更好地管理组件的状态。
5. 副作用管理:使用 useEffect 和其他相关 Hook,可以更精确地控制副作用操作的执行时机。可以在组件渲染完成之后、依赖发生变化时或组件卸载之前执行一些副作用操作,例如数据获取、订阅和取消订阅等。这样可以提高组件的性能和效率,并且避免了繁琐的手动清理工作。
useEffect
作用
什么是副作用
副作用(Side Effect)指的是函数或表达式执行过程中除了返回值外对其它数据产生的影响。具体来说,副作用就是函数执行的一些附带效果,例如修改了全局变量、文件读写操作、网络请求、控制台输出等。这些副作用会改变函数外部的状态,导致系统的状态变得不确定,从而可能引发一些意想不到的问题。例如,当多个函数共享同一个全局变量时,如果其中一个函数修改了该变量,就会对其他函数产生未知的影响,从而引发难以预测和调试的问题。
在 React 中,副作用通常发生在组件渲染期间或生命周期函数中,例如:
-
修改组件内部的 state、props 或 context
-
发起网络请求获取数据
-
订阅事件、添加事件监听器或重置事件监听器
-
操作本地存储或浏览器的全局对象
-
执行定时器或动画等操作
-
直接操作 DOM 元素
为了处理这些副作用,React 提供了 useEffect
这个 Hook。通过 useEffect
,我们可以在函数组件中执行副作用操作,并在组件更新或销毁时进行清理,以遵循 React 的声明式、可预测和可控制的设计原则。
语法
useEffect是一个回调函数,第一个参数是一个箭头函数,里面编写副作用的具体操作
第二个参数是可选的,是一个数组,用于指定副作用操作中使用到的依赖项。在依赖项发生变化时,useEffect
会再次运行。如果没有依赖项,可以省略这个参数,这时 useEffect
会在每次渲染时都运行
useEffect(() => {
// 在这里处理副作用
return () => {
// 清除副作用函数
}
}, [依赖的状态;空数组表示,不依赖])
使用
在没有任何依赖情况下使用
副作用操作只会在组件首次渲染时执行一次,类似于componentDidMount
。
import React, { useEffect, useState } from 'react'
export default function App() {
const [list, setList] = useState([])
// 当useEffect不依赖与任何数据的时候useEffect的回调函数之后执行一次
useEffect(() => { // 类似与componentDidMount
// 数据请请求
fetch('data.json').then(res => {
return res.json()
}).then(data => {
// 设置list
setList(data);
})
})
return (
<div id="root" className='app-root'>
<ul>
{
list.map(item => (
<li key={ item.id }>{ item.title }</li>
))
}
</ul>
</div>
)
}
在有依赖的情况下使用
// components/List.js
export default function List(props) {
// 封装渲染的函数
const renderList = () => {
return (
props.list.map(item => (
<input key={ item.id } type="checkbox" checked={ item.checked }
onChange={ () => handleState(item.id) } />
))
)
}
// 修改状态
const handleState = id => {
props.changeState(id)
}
return (
<div>{ renderList() }</div>
)
}
// components/Footer.js
import { useEffect, useState } from "react"
export default function Footer(props) {
const [checked, setChecked] = useState(false)
useEffect(() => { // 类似于
setChecked(props.list.every(item => item.checked));
}, [props.list])
return (
<div>
全选:<input type="checkbox" checked={ checked } onChange={ () =>
{} } />
</div>
)
}
import { useState } from 'react';
import List from './components/List';
import Footer from './components/Footer'
export default function App() {
const [ list, setList ] = useState([
{ id: 1, checked: false },
{ id: 2, checked: false },
{ id: 3, checked: false }
]);
// 修改复选框的状态
const changeState = id => {
// 将原数组拷贝一份
const copyList = [...list];
// 根据id获取下标
const idx = copyList.findIndex(item => item.id === id);
// 修改当前的状态
copyList[idx].checked = !copyList[idx].checked;
// 重新设置数组
setList(copyList)
}
return (
<div id="root" className='app-root'>
<List list={ list } changeState={ changeState } />
<Footer list={ list } />
</div>
)
}
清除副作用函数
// App.js
import { useState } from "react"
import Child from "./components/Child"
export default function App() {
const [ flag, setFlag ] = useState(true)
return (
<div>
<h1>我是父组件</h1>
<button onClick={ () => setFlag(false) }>点击销毁子组件</button>
<hr />
{ flag && <Child /> }
</div>
)
}
// components/Child.js
import React, { useEffect } from 'react'
export default function Child() {
useEffect(() => {
window.onresize = () => {
console.log('resize');
}
const timer = setInterval(() => {
console.log('setInterval');
}, 1000)
return () => { // componentWillUnmonnt
// 清除副作用
clearInterval(timer);
window.onresize = null;
}
})
return (
<div>Child</div>
)
}