(十三)react hooks

202 阅读11分钟

@[TOC](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 nikgraf.github.io/react-hooks… 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原有规则,没有其他要求,易理解记忆 变量作用域明确 不会产生组件嵌套