React Hooks
- 什么是Hooks?
- UseState()
- useReducer()
- useContext()
- useEffect()
- useRef()
- 自定义钩子函数
- React Hooks中可以对性能进行优化的函数
- useMemo()
- useCallback()
- useMemo()和useCallback()的区别
什么是Hooks?
首先:React的组件创建方式,一种是类组件,一种是纯函数组件。
React团队认为组件的最佳写法应该是函数,而不是类。
但是纯函数组件有着类组件不具备的特点:
- 纯函数组件没有状态
- 纯函数组件没有生命周期
- 纯函数组件没有
this
这就注定,纯函数组件只能做UI展示的功能,如果涉及到状态的管理与切换,我们就必须得用类组件或者redux,但是在简单的页面中使用类组件或者redux会使代码显得很重。
因此,React团队设计了React hooks(钩子)。
React Hooks的意思是:组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码“钩”进来。
四种常用的钩子:
- useState()
- useReducer()
- useContext()
- useEffect()
- useRef()
UseState()
我们知道,纯函数组件没有状态,useState()用于为函数组件引入状态。
举例:
state是一个普通变量:
//引入状态钩子useState()
import React,{useState} from 'react'
import './App.css';
function App() {
//useState创造一个状态,赋值一个初始值,当前赋值的初始值为0
//count是一个变量,此变量的值指向当前状态的值 相当于this.state
//setcount是一个函数,此函数可以修改状态的值 相当于this.Setstate
const [count,setCount] = useState(0)
const addCount = ()=>{
setCount(count+1)
}
return (
<div className="App">
<div>{count}</div>
<button onClick = {addCount}>点击加1</button>
</div>
);
}
export default App;
state是一个对象:
setState()不会局部更新
意思是,如果state是一个对象,不能部分setState,所以我们使用…user
将原来的内容复制过来,再加上要修改的内容,相当于将前面的内容覆盖。
import React,{useState} from 'react'
import './App.css'
function App(){
const [user,setUser]=useState({age:'11',name:'Bob'})
const handerClick=()=>{
setUser({
...user,
name:'jack'
})
}
return (
<div className='App'>
<h1>{user.name}</h1>
<h1>{user.age}</h1>
<button onClick={handerClick}>
Click
</button>
</div>
)
}
export default App;
改变前:
改变后:
useReducer()
useState() 的替代方案,用于包含多种状态,或者下一个 state 依赖于之前的 state,实现函数组件的状态管理。
基本原理是通过用户在页面中发起action, 从而通过reducer方法来改变state, 从而实现页面和状态的通信。
举例:
点击加1,点击减1
//实现点击改变状态
import React,{useReducer} from 'react'
import './App.css';
function App(){
//useReducer(),state表示状态,action表示相关操作
const reducer = (state,action)=>{
if (action.type === 'add') {
return {
...state,
count: state.count + 1,
}
}else if (action.type === 'jian') {
return {
...state,
count: state.count - 1,
}
} else {
return state
}
}
const addCount=()=>{
dispatch({
type:'add'
})
}
const min=()=>{
dispatch({
type:'jian'
})
}
const [state,dispatch] = useReducer(reducer,{count:0})
return(
<div>
<div>{state.count}</div>
<button onClick={addCount}>点击加1</button>
<button onClick={min}>点击减1</button>
</div>
)
}
export default App;
useContext()
useContext()用于在组件之间共享状态,而不必显式地通过组件树的逐层传递 props。
实现步骤:
- 使用
createContext
创建Context对象 - 在顶层组件通过
provider
提供数据 - 在底层组件通过
useContext
函数获取数据
//引入状态钩子useState()
import React,{useContext} from 'react'
import './App.css';
function App(){
//通过createContext来创建上下文
const AppContext = React.createContext()
const Achild = ()=>{
//在子组件中通过useContext来获取数据
const {name1} = useContext(AppContext)
return(
<div>
这是组件A,使用的name值是:{name1}
</div>
)
}
const Bchild = ()=>{
//在子组件中通过useContext(Context句柄)来获取数据
const {name2} = useContext(AppContext)
return(
<div>
这是组件B,使用的name值是:{name2}
</div>
)
}
return (
//AppContext.Provider数据共享组件,来确定共享范围,通过value来分发内容
<AppContext.Provider value={{name1:'jack',name2:'Bob'}}>
<Achild></Achild>
<Bchild></Bchild>
</AppContext.Provider>
);
}
export default App;
useEffect()
useEffect()可以检测数据更新 。,可以用来更好的处理副作用,比如异步请求等。
useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect()的依赖项。
只要数组发生改变,useEffect()就会执行。
当第二项省略不填时,useEffect()会在每次组件渲染时执行,这一点类似于componentDidMount。
useEffect回调在dom渲染完毕之后执行 和vue里边的Watch效果比较像,但是执行时机是不同的 watch一开始就执行了
举例:
第二个参数省略时:
import React,{useState,useEffect} from 'react'
import './App.css';
function App(){
const [loading,setLoading] = useState(true)
//相当于componentDidMount
//useEffect()第二个参数未填
useEffect(()=>{
setTimeout(()=>{
setLoading(false)
},3000)
})
//loadling为true时显示Loading... 3秒后loading变成了false,显示内容加载完毕
return (
loading?<div>Loading</div>:<div>内容加载完毕</div>
)
}
export default App;
第二个参数存在时:
name改变时会触发useEffect()函数,
import React,{useState,useEffect} from 'react'
import './App.css';
function AsyncPage({name}){
const [loading,setLoading] = useState()
const [person,setPerson] = useState({})
//useEffect()函数在组件初始化执行一次,之后,name改变时才会执行
//组件渲染时,两秒后从Loading变为Bob
//name改变时,先从Bob变为Loading,两秒后变为指定名字
useEffect(()=>{
setTimeout(()=>{
setLoading(false)
setPerson({name})
},2000)
},[name])
return(
loading?<div>Loading...</div>:<div>{person.name}</div>
)
}
function App(){
const [name,setName] = useState('Bob')
const changeName = (name)=>{
setName(name)
}
return (
<div>
<AsyncPage name = {name}/>
<button onClick = {()=>changeName('Jack')}>将名字改为jack</button>
<button onClick = {()=>changeName('Tom')}>将名字改为Tom</button>
</div>
)
}
export default App;
useEffect()返回一个函数:
当useEffect()返回一个函数时,该函数会在组件卸载时执行。
举例:
当点击switch时,组件被卸载,定时器被清除,控制台不再打印。
import React,{useEffect,useState} from 'react'
import './App.css';
function Test (){
useEffect(()=>{
let timer = setInterval(()=>{
console.log('定时器正在执行')
},1000)
return ()=>{
//清除定时器
clearInterval(timer)
}
},[])
return(
<div>this is test</div>
)
}
function App(){
const [flag,setFlag] = useState(true)
return (
<div>
{flag?<Test/>:null}
<button onClick={()=>{setFlag(false)}}>switch</button>
</div>
)
}
export default App;
useRef()
用于在函数组件中获取真实的DOM元素对象或者是组件实例。(因为函数组件没有实例,所以这里的获取组件实例指的是获取类组件实例)
使用步骤:
- 导入useRef()函数
- 执行useRef()函数并传入null,返回值为一个对象,内部有一个current属性存放拿到的dom对象(组件实例)
- 通过ref绑定要获取的元素或者组件实例。
举例:
获取dom和组件实例,可以看到结果在控制台打印了出来
import React,{useEffect, useRef} from 'react'
import './App.css';
//组件实例 类组件(函数组件没有实例)
//dom对象 标签
class Test extends React.Component{
render(){
return (
<div>我是类组件</div>
)
}
}
function App(){
const testRef = useRef(null)
const h1Ref = useRef(null)
//useEffect回调在dom渲染完毕之后执行
//和vue里边的Watch效果比较像,但是执行时机是不同的 watch一开始就执行了
useEffect(()=>{
console.log(testRef.current)
console.log(h1Ref.current)
},[])
return(
<div>
{/* 获取类组件实例 */}
<Test ref={testRef}/>
{/* 获取DOM对象 */}
<h1 ref={h1Ref}>this is h1</h1>
</div>
)
}
export default App;
自定义钩子函数
根据自己的业务需求,自行封装一个钩子函数以供自己使用。
举例:自定义一个获取表单数据的钩子函数
import React,{useState} from 'react'
import './App.css';
// 自定义hook(use开头)
// 重用受控表单创建state和onChange方法逻辑
/**
*
* @param {string | number} initialValue 初始默认值
* @returns
*/
//获取表单数据
const useInput = (initialValue) => {
const [value, setValue] = useState(initialValue)
return {
value,
onChange: e => setValue(e.target.value)
}
}
// 表单组件
const App = () => {
const username = useInput('admin')
const password = useInput('')
const onSubmit = (e) => {
//阻止默认事件发生
e.preventDefault()
// 获取表单值
console.log(username.value, password.value);
}
return (
<form onSubmit={onSubmit} >
<input type="text" {...username} />
<input type="password" {...password} />
<button type="submit">提交</button>
</form>
);
}
export default App;
React Hooks中可以对性能进行优化的函数
useMemo()
具有缓存作用,有助于避免在每次渲染时都进行高开销的计算。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把创建函数和依赖项数组作为参数传入useMemo,当某个依赖改变时才会重新执行useMemo()函数。
如果没有提供依赖项数组,useMemo()每次渲染时都会重新执行useMemo()函数。
举例:
useMemo()监听count的值,当count的值改变时,newValue会更新。
import { useState, useMemo} from 'react';
export default () => {
const [count, setCount] = useState(0)
const [num, setNum] = useState(0)
const newValue = useMemo(()=>{
console.log(`count 值为${count}`)
console.log(`num 值为 ${num}`)
return count+num
},[count])
return(
<div>
<h1>{count}</h1>
<button onClick={()=>{setCount(count+1)}}>count + 1</button>
<hr/>
<h1>{num}</h1>
<button onClick={()=>{setNum(num+1)}}>Num + 1</button>
<hr/>
<h2>{newValue}</h2>
</div>
)
}
点击5次num+1,num变为5,虽然newValue仍然为0,但是num=5已经被缓存了;点击count+1,他会计算count此时的值1与num缓存的值5的和,最终结果newValue为6。
结果如图:
useCallback()
useCallback
可以说是 useMemo
的语法糖,能用 useCallback
实现的,都可以使用 useMemo
, 常用于react的性能优化
与useMemo()一样,依赖数组改变时才会重新执行useCallback()函数。
如果没有依赖数组,每次渲染都会重新执行useCallback()函数。
const memoizedCallback = useCallback(() => {doSomething(a, b)},[a, b]);
举例:
和上述useMemo()的效果一样,区别是useCallback()调用newValue时是:newValue()
import React,{ useState, useCallback} from 'react';
function App(){
const [count, setCount] = useState(0)
const [num, setNum] = useState(0)
const newValue = useCallback(()=>{
console.log(`count 值为${count}`)
console.log(`num 值为 ${num}`)
return count+num;
},[count])
return(
<div>
<h1>{count}</h1>
<button onClick={()=>{setCount(count+1)}}>count + 1</button>
<hr/>
<h1>{num}</h1>
<button onClick={()=>{setNum(num+1)}}>Num + 1</button>
<hr/>
{/* 调用useCallback 返回的值 */}
<h2>{newValue()}</h2>
</div>
)
}
export default App;
useMemo()和useCallback()的区别
useMemo()返回缓存的变量(memoized)
useCallback()返回缓存的函数