4.React Hooks
# 1.出几道react hooks面试题
# 1.关于React Hooks
- 可选功能(class组件 vs Hooks)
- 100%向后兼容,没有破坏性改动
- 不会取代class组件,尚无计划要移除class组件
# 2.面试问到React Hooks
- Hooks作为React的一部分,在面试中也只能占一部分时间
- 初学者还是以学好class组件为主,Hooks是加分项
- 学习Hooks的前提,必须学好class组件
# 3.本章的主要内容
- State Hook
- Effect Hook
- 其他 Hook
- 自定义Hook
- 组件逻辑复用
- 规范和注意事项
# 4.面试题
- 为什么会有React Hooks,它解决了哪些问题
- React Hooks如何模拟组件生命周期
- 如何自定义Hook
- React Hooks性能优化
- 使用React Hooks遇到哪些坑
- Hooks相比HOC和Render Prop有哪些优点
# 2.class组件存在哪些问题
# 2.1 回顾React函数组件
//class 组件
class List extends React.Component {
constructor(props) {
super(props)
}
render() {
const { list } = this.props
return <ul>{list.map((item, index){
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
//函数组件
function List(props){
const { list } = this.props
return <ul>{list.map((item, index){
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 2.2 函数组件的特点
- 没有组件实例
- 没有生命周期
- 没有state和setState,只能接收props
# 2.3 class组件的问题
- 大型组件很难拆分和重构,很难测试(即class不易拆分)
- 相同业务逻辑,分散带各个方法中,逻辑混乱
- 复用逻辑变得复杂,如Mixins、HOC、Render Prop
# 2.4 React组件更易用函数表达
- React提倡函数式编程,view = fn(props)
- 函数更灵活,更易拆分,更易测试
- 但函数组件太简单,需要增强能力 — Hooks
# 3.用useState实现state和setState功能
//示例代码
import React, { useState } from 'react'
function ClickCounter(){
//数组的解构
const [count, setCount] = useState(0)
// const arr = useState(0)
// const count = arr[0]
// const setCount = arr[1]
return <div>
<p>你点击了{count}次</p>
<button onClick={ ()=> setCount(count +1)}> 点击 </button>
<div>
}
export default ClickCounter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.1 让函数组件实现state和setState
- 默认函数组件没有state
- 函数组件是一个纯函数,执行完即销毁,无法存储state
- 需要State Hook,即把state功能“钩”到纯函数中
# 3.2 useState使用总结
- useState(initValue)传入初始值,返回数组[state,setState]
- 通过state获取值
- 通过setState(newValue)修改值
# 3.3 Hooks命名规范
- 规定所有的Hooks都use开头,如useXxx
- 自定义Hook也要以use开头
- 非Hooks的地方,尽量不要使用useXxx写法
# 4.用useEffect模拟组件生命周期
# 4.1让函数组件模拟生命周期
- 默认函数组件没有生命周期
- 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
- 使用Effect Hook把生命周期“钩子" 到纯函数中
//模拟class组件的DidMount和DidUpdate
useEffect(() =>{
console.log('在此发送一个ajax请求')
})
//模拟class组件的DidMount
useEffect(() =>{
console.log('加载完了')
},[]) //第二个参数是[] (不依赖于任何state)
//模拟class组件的DidUpdate
useEffect(() =>{
console.log('更新了')
},[count,name])//第二个参数是依赖的state
//模拟class组件的DidMount
useEffect(() =>{
let timeId = window.setInterval(() =>{
console.log(Date.now())
},100)
//返回一个函数
//模拟WillUnMount
return () => {
window.clearInterval(timeId)
}
},[])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 4.2 useEffect使用总结
- 模拟componentDidMount -
useEffect 依赖[]
- 模拟componentDidUpdate -
useEffect无依赖,或者部分依赖如[a,b]形式
- 模拟componentWillUnMount -
useEffect中返回一个函数
# 4.3 useEffect让纯函数有了副作用
- 默认情况下,执行纯函数,输入参数,返回结果,无副作用
- 所谓副作用,即是对函数之外造成影响,如设置全局定时任务
- 而组件需要副作用,所有需要useEffect “钩” 到纯函数中
# 5.用useEffect模拟componentWillUnMount时的注意事项
# 5.1 useEffect返回的函数,模拟componentWillUnMount,但不完全相等
componentDidMount() {
console.log(`开始监听 ${this.props.friendId} 在线状态`)
}
componentWillUnMount(){
console.log(`结束监听 ${this.props.friendId} 在线状态`)
}
//friendId 更新
componentDidUpdate(prevProps) {
console.log(`结束监听 ${prevProps.friendId} 在线状态`)
console.log(`开始监听 ${this.props.friendId} 在线状态`)
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
function FriendStatus({ friendId }) {
const [status, setStatus] = useState(false)
//模拟componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log(`开始监听 ${friendId} 在线状态`)
//【特别注意】
//此处并不完全等同于 componentWillUnMount
//props 发生变化,即更新,也会执行结束监听
//准确的说:返回的函数,会在下一次 effect 执行之前,被执行
return () => {
console.log(`结束监听 ${friendId} 在线状态`)
}
})
return <div>
好友 {friendId} 在线状态:{status.toString()}
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这两段代码的效果是一样的,useEffect 返回的函数,执行效果要看useEffect本身的作用,如果useEffect本身即能模拟``componentDidMount又能模拟
componentDidUpdate,那执行效果相当于
componentWillUnMount和
componentDidUpdate`都会执行
# 5.1 useEffect中返回函数fn
- useEffect第二个参数依赖空数组[],则只在组件销毁时执行返回的函数fn,等于componentWillUnMount
- useEffect无依赖(不传第二个参数)或部分依赖如[a,b],组件更新时也执行fn。即,下一次执行useEffect之前,就会执行fn,无论更新或卸载(相当于同时模拟了
componentWillUnMount
和componentDidUpdate
两个生命周期)
# 5.2 小结
- 函数组件更适合React组件,但需要Hooks增强功能
- useState可实现state和setState
- useEffect可模拟组件主要的生命周期
# 6.useRef和useContext
useRef
import React,{useRef,useEffect} from 'React'
function UseRef(){
const btnRef = useRef(null) //初始值
//不一定时获取DOM节点
//const numRef = useRef(0)
//numRef.current
useEffect(() =>{
console.log(btnRef.current) //DOM节点
},[])
return <div>
<button ref={btnRef}>click</button>
</div>
}
export default UseRef
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 7.useReducer能代替Redux吗
# 7.1 useReducer和redux的区别
- useReducer是useState的代替方案,用于state复杂变化(并不能替代Redux)
- useReducer是单个组件状态管理,组件通讯还需要props
- redux是全局状态管理,多组件共享数据
/*useReducer使用示例*/
import React,{useReducer} from 'React'
const initialState = {count:0}
const reducer = (state,action) => {
switch (action.type) {
case 'increment':
return {count:state.count+1}
case 'decrement':
return {count:state.count-1}
default:
return state
}
}
function App(){
const [state,dispatch] = useReducer(reducer,initialState)
return <div>
count:{state.count}
<button onClick={() => dispatch({type:'increment'})}>increment</button>
<button onClick={() => dispatch({type:'decrement'})}>decrement</button>
}
export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 8.使用useMemo做性能优化
# 8.1使用useMemo做性能优化
import React,{useState} from 'React'
//子组件,点击click按钮,count被修改,子组件虽然没有用count,但是也被更新渲染
function Child({useInfo}){
console.log('Child render...',useInfo)
return <div>
<p>This is Child {useInfo.name} {useInfo.age}</p>
</div>
}
//父组件
function App(){
console.log('Parent render...')
const [count,setCount] = useState(0)
const [name,setName] = useState('双越老师')
const useInfo ={name,age:20}
return <div>
<p>
useMomo demo
<button onClick={() => setCount(count +1)}>click</button>
</p>
<Child userInfo={userInfo}></Child >
</div>
}
export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React,{useState,memo,useMemo} from 'React'
//子组件
// function Child({useInfo}){
// console.log('Child render...',useInfo)
// return <div>
// <p>This is Child {useInfo.name} {useInfo.age}</p>
// </div>
// }
// 类似class PureComponent,对props进行浅层比较
//memo相当于PureComponent
const Child =memo(({useInfo}) => {
console.log('Child render...',useInfo)
return <div>
<p>This is Child {useInfo.name} {useInfo.age}</p>
</div>
})
//父组件
function App(){
console.log('Parent render...')
const [count,setCount] = useState(0)
const [name,setName] = useState('双越老师')
//const useInfo ={name,age:20}
//用useMemo缓存数据,有依赖,name变化时缓存失效
const userInfo = useMemo(() => {
return {name,age:21}
},[name])
return <div>
<p>
useMomo demo
<button onClick={() => setCount(count +1)}>click</button>
</p>
<Child userInfo={userInfo}></Child >
</div>
}
export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 8.2 useMemo使用总结
- React默认会更新所有子组件
- class组件使用SCU和PureComponent做优化
- Hooks中使用useMemo,但优化的原理是相同的
# 9.使用useCallback做性能优化
import React,{useState,memo,useMemo,useCallback} from 'React'
//子组件
//memo相当于PureComponent
const Child =memo(({useInfo,onChange}) => {
console.log('Child render...',useInfo)
return <div>
<p>This is Child {useInfo.name} {useInfo.age}</p>
<input onChange={onChange}></input>
</div>
})
//父组件
function App(){
console.log('Parent render...')
const [count,setCount] = useState(0)
const [name,setName] = useState('双越老师')
//const useInfo ={name,age:20}
//用useMemo缓存数据,有依赖
const userInfo = useMemo(() => {
return {name,age:21}
},[name])
//加了onChange后父组件改变子组件又开始渲染
//function onChange(e){
// console.log(e.target.value)
//}
//用useCallback缓存函数
const onChange = useCallback(e =>{
console.log(e.target.value)
},[])
return <div>
<p>
useMomo demo
<button onClick={() => setCount(count +1)}>click</button>
</p>
<Child userInfo={userInfo} onChange={onChange}></Child >
</div>
}
export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 9.1 useCallback使用总结
- useMemo缓存数据
- useCallback缓存函数
- 两者是React Hooks的常见优化策略
# 10.什么是自定义Hook
- 封装通用的功能
- 开发和使用第三方Hooks
- 自定义Hook带来了无限的扩展性,解耦代码
//封装useAxios作为自定义Hook示例
import React,{useState,useEffect} from 'React'
import axios from 'axios'
//封装axios 发送网络请求的自定义Hook
function useAxios(url){
const [loading,setLoading] = useState(false)
const [data,setData] = useState()
const [error,setError] = useState()
useEffect(() =>{
//利用axios发送网络请求
setLoading(true)
axios.get(url) //发送一个get请求
.then(res => setData(res))
.catch(err => setError(err))
.finally(() => setLoading(false))
},[url])
return [loading,data,error]
}
export default useAxios
//使用
import React from 'React'
import useAxios from './useAxios'
function App(){
const url = 'http://localhost:8090/#/proapplyDetail?id=5d0e34a8711ebae8b9481a73a433f405'
//数组解构
const [loading,data,error] = useAxios(url)
if(loading) return <div>loading...</div>
return error
? <div>{JSON.stringify(error)}</div>
: <div>{JSON.stringify(data)}</div>
}
export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 10.1 总结
- 自定义Hook本质是一个函数,以use开头(重要)
- 内部正常使用useState useEffect获取其他Hooks
- 自定义返回结果,格式不限
# 参考链接-第三方Hooks
https://nikgraf.github.io/react-hooks/ https://github.com/umijs/hooks/
# 11.使用Hooks的两条重要规则
# 11.1 Hooks使用规范
- 再次强调命名规范useXxx
- Hooks使用规范,重要!
# 11.2 关于Hooks的调用顺序
- 只能用于React函数组件和自定义Hook中,其他地方不可以
- 只能用于顶层代码(不能跟在可能被打断的逻辑后面),不能在循环、判断中使用Hooks
- eslint插件
eslint-plugin-react-hooks
可以帮到你
function Teach({ courseName }) {
const [ studentName, setStudentName ] = useState('张三')
//第一种错误用法示范,判断中使用Hooks
if (courseName == '') {
useEffect(() => {
localStorage.setItem('name', studentName)
})
}
//第二种错误用法示范,判断中使用Hooks
for (let i = 0; i < 100; i++) {
const [teacherName, setTeacherName] = useState('李四')
}
//第三种错误用法示范,在可能被打断的逻辑语句后面使用Hooks
if (!courseName) {
return
}
useEffect(() => {
console.log(`${teacherName} 开始讲课,学生是 ${studentName}`)
})
return <div>课程:{courseName},讲师:{teacherName},学生:{studentName} </div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 12.为何Hooks要依赖于调用顺序
- 无论是render还是re-render,Hooks调用顺序必须一致
- 如果Hooks出现在循环、判断里,则无法保证顺序一致
- Hooks严重依赖于调用顺序!重要
/*Hooks调用顺序代码演示*/
import React,{useState,useEffect} from 'react'
function Teach({couseName}){
//函数组件,纯函数,执行完即销毁
//所以,无论组件初始化(render)还是组件更新(re-render)
//都会重新执行一次这个函数,获取最新的组件
//这一点和class组件不一样
//render:初始化state的值 '张三'
//re-render:读取state的值 '张三'
const [studentName,setSudentName] = useState('张三')
//render:初始化state的值 '李四'
//re-render:读取state的值 '李四'
const [teacherName,seTeacherName] = useState('李四')
//if(couseName){
//const [teacherName,seTeacherName] = useState('李四')
//}
//if(couseName){
//useEffect(() => {
// //模拟学生签到
//localStorage.setItem('name',studentName)
//})
//}
//render:添加effect函数
//re-render:替换effect函数(内部的函数也会重新定义)
useEffect(() => {
//模拟学生签到
localStorage.setItem('name',studentName)
})
//render:添加effect函数
//re-render:替换effect函数(内部的函数也会重新定义)
useEffect(() => {
//开始上课
console.log(`${teacherName} 开始上课,学生${studentName}`
})
return <div>
课程:{couseName}
讲师:{teacherName}
学生:{studentName}
</div>
}
export default Teach
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 13.class组件逻辑复用有哪些问题
- Mixins早已废弃
- class组件逻辑复用第一种方法:
高阶组件HOC
- class组件逻辑复用第二种方法:
Render Prop
# 13.1 Mixins的问题
- 变量作用域来源不清
- 属性重名
- Mixins引入过多会导致顺序冲突
# 13.2 高阶组件HOC
- 组件层级嵌套过多,不易渲染,不易调试
- HOC会劫持props,必须严格规范,容易出现疏漏
# 13.3 Render Prop
- 学习本高,不易理解
- 只能传递纯函数,而默认情况下纯函数功能有限
# 14.Hooks组件逻辑复用有哪些好处
- 完全符合Hooks原有规则,没有其他要求,易理解记忆
- 变量作用域明确
- 不会产生组件嵌套
# 15.Hooks使用中的几个注意事项
- useState初始化值,只有第一次有效
- useEffect内部不能修改state
- useEffect可能出现死循环
# 15.1 useState初始化值,只有第一次有效
import React,{useState} from 'react'
//子组件
function Child({userInfo}){
//render:初始化state
//re-render:只恢复初始化的state值,不会再重新设置新的值,只能用setName修改
const [name,setName] = useState(userInfo.name)
return <div>
<p>Child,props name:{userInfo.name} </p>
<p>Child,state name:{name} </p>
</div>
}
function App(){
const [name,setName] = useState('uploadhub')
const userInfo = {name}
return <div>
<div>
Parent
<button onClick={() => setName('uploadhub1')>setName</button>
</div>
<Child userInfo={userInfo} />
</div>
}
export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 15.2 useEffect内部不能修改state
/*演示依赖为空数组[]时,useEffect模拟componentDidMount,里面用到定时器等手段执行setXxxx来改变外层state,但state除了第一次执行后并不随定时器执行setXxx而发生变化的情况*/
import React,{useState,useRef,useEffect} from 'react'
function UseEffectChangeState(){
const [count,setCount] = useState(0)
//模拟componentDidMount
useEffect(() => {
console.log('useEffect...',count)
//定时任务
const timer = setInterval(() => {
console.log('setInterval...',count)
setCount(count+1)
},1000)
//清除定时任务
return () => clearTimeout(timer)
},[])//依赖为[]
//依赖为[]时:re-render不会重新执行effect函数(指useEffect被传入的第一个参数),此时count一直在闭包里,保持不变
//没有依赖:re-render会重新执行effect函数
return <div>count: {count}</div>
}
export default UseEffectChangeState
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 15.2.1 解决方法一:外部自定义变量,不用useState创建的State,不推荐
import React,{useState,useRef,useEffect} from 'react'
function UseEffectChangeState(){
const [count,setCount] = useState(0)
//模拟componentDidMount
let myCount = 0
useEffect(() => {
console.log('useEffect...',count)
//定时任务
const timer = setInterval(() => {
console.log('setInterval...',myCount )
setCount(++myCount )//打破了纯函数的规则,不推荐
},1000)
//清除定时任务
return () => clearTimeout(timer)
},[])//依赖为[]
//依赖为[]时:re-render不会重新执行effect函数
//没有依赖:re-render会重新执行effect函数
return <div>count: {count}</div>
}
export default UseEffectChangeState
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 15.2.2 解决方法二:使用Ref,推荐
import React,{useState,useRef,useEffect} from 'react'
function UseEffectChangeState(){
const [count,setCount] = useState(0)
//模拟componentDidMount
const countRef = useRef(0)
useEffect(() => {
console.log('useEffect...',count)
//定时任务
const timer = setInterval(() => {
console.log('setInterval...',countRef.current)
setCount(++countRef.current)
},1000)
//清除定时任务
return () => clearTimeout(timer)
},[])//依赖为[]
//依赖为[]时:re-render不会重新执行effect函数
//没有依赖:re-render会重新执行effect函数
return <div>count: {count}</div>
}
export default UseEffectChangeState
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 15.3 useEffect可能出现死循环(依赖里有{}、[]等引用类型)
import React,{useState,useEffect} from 'React'
import axios from 'axios'
//封装axios 发送网络请求的自定义Hook
function useAxios(url,config={}){
const [loading,setLoading] = useState(false)
const [data,setData] = useState()
const [error,setError] = useState()
useEffect(() =>{
//利用axios发送网络请求
setLoading(true)
axios.get(url,config) //发送一个get请求
.then(res => setData(res))
.catch(err => setError(err))
.finally(() => setLoading(false))
},[url,config]) //依赖里有{}、[]等引用类型
return [loading,data,error]
}
export default useAxios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 15.3.1 解决方法:解构对应引用类型的变量,再分别进行依赖
/*解决方法示例*/
import React,{useState,useEffect} from 'React'
import axios from 'axios'
//封装axios 发送网络请求的自定义Hook
function useAxios(url,config={}){
const [loading,setLoading] = useState(false)
const [data,setData] = useState()
const [error,setError] = useState()
useEffect(() =>{
//利用axios发送网络请求
setLoading(true)
//先解构赋值,再分别依赖
const {a,b,c} = config
axios.get(url,config) //发送一个get请求
.then(res => setData(res))
.catch(err => setError(err))
.finally(() => setLoading(false))
},[url,a,b,c])
return [loading,data,error]
}
export default useAxios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 15.3.2 出现该现象的原因
- React使用
Object.is()
方法来监测比较useEffect的依赖是否发生改变,而且是一上来就比较一次(因为无论是依赖空数组[]还是部分依赖,useEffect都是先模拟componentDidMount,再判断是否模拟componentDidUpdate)。 - 这个语法对值类型影响不大,但对引用类型的比较却总返回false,而一开始就会比较一次,所以一旦useEffect的依赖中有引用类型的变量就会陷入死循环。
# 16.Hooks面试题解答
# 16.1 为什么要使用Hooks
- 完善函数组件的能力,函数更适合React组件
- 组件逻辑复用,Hooks表现更好
- class复杂组件正在变得费解,不易拆解,不易测试,逻辑混乱
# 16.2 class组件中,相同的逻辑散落在各处
DidMount和DidUpdate中获取数据 DidMount绑定事件,WillUnMount解绑事件 使用Hooks,相同逻辑课分割到一个一个的useEffect中
# 16.3 React Hooks模拟组件生命周期
- 模拟componentDidMount -
useEffect 依赖[]
- 模拟componentDidUpdate -
useEffect无依赖,或者部分依赖如[a,b]形式
- 模拟componentWillUnMount -
useEffect中返回一个函数
# 16.4 useEffect中返回函数fn
- useEffect依赖[],组件销毁时执行fn,等于WillUnMount
- useEffect无依赖或依赖[a,b],组件更新时执行fn
- 即,下一次执行useEffect之前,就会执行fn,无论更新或卸载
# 16.5 如何自定义Hook
请参照第10小节
# 16.6 React Hooks性能优化
- useMemo缓存数据
- useCallback缓存函数
- 相当于class组件的SCU和PureComponent
# 16.7 React Hooks遇到哪些坑?
- useState初始化值,只有第一次有效
- useEffect内部,不能修改state
- useEffect依赖引用类型,会出现死循环
# 16.8 React Hooks做组件逻辑复用的优点
- 完全符合Hooks原有规则,没有其他要求,易理解记忆
- 变量作用域明确
- 不会产生组件嵌套