(十三)react hooks

react hooks

出几道react hooks面试题

关于React Hooks
可选功能(class组件 vs Hooks)
100%向后兼容,没有破坏性改动
不会取代class组件,尚无计划要移除class组件

面试问到React Hooks
Hooks作为React的一部分,在面试中也只能占一部分时间
初学者还是以学好class组件为主,Hooks是加分项
学习Hooks的前提,必须学好class组件

本章的主要内容
State Hook
Effect Hook
其他 Hook
自定义Hook
组件逻辑复用
规范和注意事项

面试题
为什么会有React Hooks,它解决了哪些问题
React Hooks如何模拟组件生命周期
如何自定义Hook
React Hooks性能优化
使用React Hooks遇到哪些坑
Hooks相比HOC和Render Prop有哪些优点

class组件存在哪些问题

认识React Hooks
回顾React函数组件
在这里插入图片描述
State Hook
Effect Hook

函数组件的特点
没有组件实例
没有生命周期
没有state和setState,只能接收props

class组件的问题
大型组件很难拆分和重构,很难测试(即class不易拆分)
相同业务逻辑,分散带各个方法中,逻辑混乱
复用逻辑变得复杂,如Mixins、HOC、Render Prop

React组件更易用函数表达
React提倡函数式编程,view = fn(props)
函数更灵活,更易拆分,更易测试
但函数组件太简单,需要增强能力 — Hooks

用useState实现state和setState功能

在这里插入图片描述

让函数组件实现state和setState
默认函数组件没有state
函数组件是一个纯函数,执行完即销毁,无法存储state
需要State Hook,即把state功能“钩”到纯函数中

useState使用总结
useState(0)传入初始值,返回数组[state,setState]
通过state获取值
通过setState(1)修改值

Hooks命名规范
规定所有的Hooks都use开头,如useXxx
自定义Hook也要以use开头
非Hooks的地方,尽量不要使用useXxx写法

用useEffect模拟组件生命周期

让函数组件模拟生命周期
默认函数组件没有生命周期
函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
使用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)
	}
},[])

useEffect使用总结
模拟componentDidMount - useEffect 依赖[]
模拟componentDidUpdate - useEffect无依赖,或者依赖[a,b]
模拟componentWillUnMount - useEffect中返回一个函数

useEffect让纯函数有了副作用
默认情况下,执行纯函数,输入参数,返回结果,无副作用
所谓副作用,即是对函数之外造成影响,如设置全局定时任务
而组件需要副作用,所有需要useEffect “钩” 到纯函数中

用useEffect模拟WillUnMount时的注意事项

【注意】模拟WillUnMount,但不完全相等
在这里插入图片描述
在这里插入图片描述
上两张图的效果是一样的,useEffect 返回的函数执行要看useEffect本身的作用起作用,如果useEffect本身即能模拟componentDidMount又能模拟componentDidUpdate,那componentWillUnMount和componentDidUpdate之前都会执行

useEffect中返回函数fn
useEffect依赖[],组件销毁时执行fn,等于WillUnMount
useEffect无依赖或依赖[a,b],组件更新时执行fn
即,下一次执行useEffect之前,就会执行fn,无论更新或卸载

小结
函数组件更适合React组件,但需要Hooks增强功能
useState可实现state和setState
useEffect可模拟组件主要的生命周期

其他Hooks

useRef
useContext
useReducer
useMemo
useCallback

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

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
 

useReducer能代替redux吗

在这里插入图片描述

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

useReducer和redux的区别
useReducer是useState的代替方案,用于state复杂变化
useReducer是单个组件状态管理,组件通讯还需要props
redux是全局状态管理,多组件共享数据

使用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

使用useMemo做性能优化

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

useMemo使用总结
React默认会更新所有子组件
class组件使用SCU和PureComponent做优化
Hooks中使用useMemo,但优化的原理是相同的

使用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

useCallback使用总结
useMemo缓存数据
useCallback缓存函数
两者是React Hooks的常见优化策略

什么是自定义Hook

自定义Hook
封装通用的功能
开发和使用第三方Hooks
自定义Hook带来了无限的扩展性,解耦代码
代码演示useAxios

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

总结
本质是一个函数,以use开头(重要)
内部正常使用useState useEffect获取其他Hooks
自定义返回结果,格式不限
第三方Hooks
https://nikgraf.github.io/react-hooks/
https://github.com/umijs/hooks/

使用Hooks的两条重要规则

Hooks使用规范
再次强调命名规范useXxx
Hooks使用规范,重要!
关于Hooks的调用顺序
只能用于React函数组件和自定义Hook中,其他地方不可以
只能用于顶层代码,不能在循环、判断中使用Hooks
eslint插件 eslint-plugin-react-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
 

Hooks调用顺序必须保持一致
无论是render还是re-render,Hooks调用顺序必须一致
如果Hooks出现在循环、判断里,则无法保证顺序一致
Hooks严重依赖于调用顺序!重要

class组件逻辑复用有哪些问题

React Hooks组件逻辑复用
回顾class组件的逻辑复用
使用Hooks做组件逻辑复用

class组件逻辑复用
Mixins早已废弃
高阶组件HOC
Render Prop

Mixins的问题
变量作用域来源不清
属性重名
Mixins引入过多会导致顺序冲突

高阶组件HOC
组件层级嵌套过多,不易渲染,不易调试
HOC会劫持props,必须严格规范,容易出现疏漏

Render Prop
学习本高,不易理解
只能传递纯函数,而默认情况下纯函数功能有限

Hooks组件逻辑复用有哪些好处

使用React Hooks做组件逻辑复用代码演示
在这里插入图片描述

在这里插入图片描述

Hooks做组件逻辑复用的好处
完全符合Hooks原有规则,没有其他要求,易理解记忆
变量作用域明确
不会产生组件嵌套

Hooks使用中的几个注意事项

React Hooks注意事项
useState初始化值,只有第一次有效
useEffect内部不能修改state
useEffect可能出现死循环

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>Child,state name:{name}
	</div>
}
function App(){
	const [name,setName] = useState('双越')
	const userInfo = {name}
	return <div>
		<div>
			Parent &nbsp;
			<button onClick={() => setName('慕课网')>setName</button>
		</div>
		<Child userInfo={userInfo} />
	</div>
}
export default App
 

点击setName按钮后
在这里插入图片描述

useEffect内部不能修改state

import React,{useState,useRef,useEffect} from 'react'
function UseEffectChangeState(){
	const [count,setCount] = useState(0)
	//模拟DidMount
	useEffect(() => {
		console.log('useEffect...',count)
		
		//定时任务
		const timer = setInterval(() => {
			console.log('setInterval...',count)
			setCount(count+1)
		},1000)
		//清除定时任务
		return () => clearTimeout(timer)
	},[])//依赖为[]
	
	//依赖为[]时:re-render不会重新执行effect函数
	//没有依赖:re-render会重新执行effect函数
	
	return <div>count: {count}</div>
}
export default UseEffectChangeState
 

在这里插入图片描述
解决方法一:

import React,{useState,useRef,useEffect} from 'react'
function UseEffectChangeState(){
	const [count,setCount] = useState(0)
	//模拟DidMount
	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
 

在这里插入图片描述
解决方法二:

import React,{useState,useRef,useEffect} from 'react'
function UseEffectChangeState(){
	const [count,setCount] = useState(0)
	//模拟DidMount
	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
 

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]) //依赖里有{}、[]引用类型
	 //解决办法:将config进行解构
	 return [loading,data,error]
}
export default useAxios

Hooks面试题解答

本章的主要内容
State Hook
Effect Hook
其他Hook
自定义Hook
组件逻辑复用
规范和注意事项

为什么要使用Hooks
完善函数组件的能力,函数更适合React组件
组件逻辑复用,Hooks表现更好
class复杂组件正在变得费解,不易拆解,不易测试,逻辑混乱

class组件中,相同的逻辑散落在各处
DidMount和DidUpdate中获取数据
DidMount绑定事件,WillUnMount解绑事件
使用Hooks,相同逻辑课分割到一个一个的useEffect中

React Hooks模拟组件生命周期
模拟componentDidMount - useEffect 依赖[]
模拟componentDidUpdate - useEffect无依赖,或者依赖[a,b]
模拟componentWillUnMount - useEffect中返回一个函数

useEffect中返回函数fn
useEffect依赖[],组件销毁时执行fn,等于WillUnMount
useEffect无依赖或依赖[a,b],组件更新时执行fn
即,下一次执行useEffect之前,就会执行fn,无论更新或卸载

如何自定义Hook
在这里插入图片描述
React Hooks性能优化
useMemo缓存数据
useCallback缓存函数
相当于class组件的SCU和PureComponent

React Hooks遇到哪些坑?
useState初始化值,只有第一次有效
useEffect内部,不能修改state
useEffect依赖引用类型,会出现死循环

React Hooks做组件逻辑复用的优点
完全符合Hooks原有规则,没有其他要求,易理解记忆
变量作用域明确
不会产生组件嵌套


版权声明:本文为lj1530562965原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。