React Hooks 是什么?
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
React Hooks 有哪些?
1.基础 Hook
- useState
定义状态和更改状态
作用:返回一个 state,以及更新 state 的函数。
useState案例:
import React,{useState} from 'react'
export default function Hello() {
// 状态 更新状态的方法 定义初始值
const [count,setCount]=useState(0)
const [flag,setFlag]=useState(true)
return (
<div>
<h3>计数</h3>
<button onClick={()=>{setCount(prevCount=>prevCount+1)}}>+</button>//prevCount就是上一次的值
<p>{count}</p>
<hr/>
<h3>开关</h3>
<button onClick={()=>{setFlag(false)}}>修改状态</button>
<p>灯泡{flag ? '亮' : '暗'}</p>
</div>
)
}
-
useEffect
1.相当于是componentDidMount / componentDidUpdate / componentWillUnmount 结合
作用:- 数据请求
- 真实dom操作
- 清除无用变量和无用事件
2.写法有三种
useEffect案例:
src/components/Hello.js
import React,{useEffect,useState} from 'react'
export default function Hello() {
const [count,setCount] = useState(0)
const [msg,setMsg] = useState('zhang')
//! 相当于componentDidMount钩子的作用
useEffect(() => {//初始化定义状态的时候必定会执行
console.log('useEffect')
document.querySelector('p').style.background = 'red'
},[])
//! componentDidMount + componentDidUpdate + watch
useEffect(() => {
console.log('useEffect')
},[count,msg])
//! useEffect后面没有跟第二个参数,那么只要数据改变,它就触发
useEffect(() => {
console.log('useEffect')
})
//! componentDidMount + componentWillUnmount
useEffect(() => {
return () => {
console.log('Hello组件被销毁了')
}
})
return (
<div>
<p> 123 </p>
<button onClick={() => {setCount(preCount=>preCount + 1)}}> + </button>
<p> {count} </p>
<hr/>
<button onClick={() => {setMsg('eason')}}>修改msg </button>
<p> {msg} </p>
</div>
)
}
属性的监听应用场景:控制页面的头部、底部是否存在及显示
补充:
useEffect 的回调参数返回的是一个清除副作用的 clean-up 函数。因此无法返回 Promise,更无法使用 async/await
解决方案:
1.再包装一层 async 函数,置于 useEffect 的回调函数中,变相使用 async/await
const fetchAPI =async()=> {
let response = await fetch('api/data')
response = await res.json()
setData(response)
}
useEffect(() => {
fetchAPI();
}, []);
2.useEffect中异步函数采用IIFE写法
useEffect(() => {
(async () => {
const { data } = await getStorage('xxx-xxx-xxx');
if(data){
console.log(data)
}
})();
}, [type, success]);
- useContext
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
作用:用于跨组件通信
useContext案例:
src/context/index.js
import { createContext } from 'react'
export const moneyContext = createContext(0)
App.js
import React from 'react'
import Father from './components/Father'
import {moneyContext} from './context'
export default function App() {
const money = 30000
return (
<div>
<moneyContext.Provider value={money}>
<Father/>
</moneyContext.Provider>
</div>
)
}
src/components/Father.js
import React from 'react'
import Son from './Son'
export default function Father() {
return (
<div>
<Son/>
</div>
)
}
src/components/Son.js
import React,{useContext} from 'react'
import {moneyContext} from '../context'
export default function Son() {
const money = useContext(moneyContext)
return (
<div>
{ money }
</div>
)
}
2.额外的 Hook
- useRef
作用:用于获取组件或元素
useRef案例:
目的:在父组件里获取子组件的方法
App.js
import React,{useRef} from 'react'
import Hello from './components/Hello'
//要在App组件里使用Hello组件里的方法
export default function App() {
const ele=useRef(null)//组件和dom节点都是对象
const dom=useRef(null)
function add(){
console.log('ele',ele)//ele {current: Hello}
ele.current.fn()
dom.current.style.background="red"
}
return (
<div>
<button onClick={add}>触发Hello组件里的方法</button>
{/* 绑定在组件上 */}
<Hello ref={ele}/>
{/* 绑定在元素上 */}
<p ref={dom}>123</p>
</div>
)
}
src/components/Hello.js (子组件Hello组件是类组件的情况,ref绑定在类组件上)
import React, { Component } from 'react'
export default class Hello extends Component {
fn=()=>{
alert('useRef')
}
render() {
return (
<div>
</div>
)
}
}
问题:当Hello组件是函数组件时?
控制台警告,建议我们使用React.forwardRef() - (ForwardRef ref绑定函数组件的)
问题:子组件使用了React.forwardRef()也拿不到FnComp组件,怎么办?
因为const fn=useRef(null)绑定的是函数组件,没有键值对,不是对象模型
解决方案:useImperativeHandle
- useImperativeHandle
作用:
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值
useImperativeHandle案例:
App.js
import React,{useRef} from 'react'
import FnComp from './components/FnComp'
//要在App组件里使用Hello组件里的方法
export default function App() {
const fn=useRef(null)//绑定的是函数组件,没有键值对,不是对象模型
function add(){
//子组件是函数组件时
// console.log('函数组件',fn)// 不使用useImperativeHandle时 fn {current: null}
fn.current.fn()//使用useImperativeHandle时 可以获取到子组件的方法
}
return (
<div>
<button onClick={add}>触发FnComp组件里的方法</button>
<FnComp ref={fn}/>
</div>
)
}
src/components/FnComp.js (FnComp组件是函数组件的情况)
import React,{useImperativeHandle} from 'react'
function FnComp(props,ref) {
useImperativeHandle(ref,()=>{
return {
fn(){
alert('函数组件')
}
}
})
return (
<div>
</div>
)
}
export default React.forwardRef(FnComp)
- useMemo
返回一个 memoized 值
作用:
用于完成优化,用于做缓存的
注意:函数组件默认帮我们做好了新旧状态判断的拦截,相当于做了shouldComponentUpdate钩子
useMemo案例:
src/components/Hello.js - 不使用useMemo的情况
import React,{useState} from 'react'
export default function Hello() {
const [count,setCount]=useState(0)
const arr=[1,2,3,4]
function renderArray(){
console.log('arr渲染了')
return arr.map((item,index)=><div key={index}>{item}</div>)
}
return (
<div>
<button onClick={()=>{setCount(preCount=>preCount+1)}}>+</button>
<p>{count}</p>
<hr/>
{renderArray()}
</div>
)
}
问题:点击按钮,改变count数据,renderArray函数有必要重新触发,进行渲染吗?
显然是没有必要的,但是状态改变,组件需要重新渲染,renderArray就会重新触发,对性能很不好
解决方案:使用useMemo对renderArray函数进行包裹一下
src/components/Hello.js - 使用useMemo的情况
import React,{useState,useMemo} from 'react'
export default function Hello() {
const [count,setCount]=useState(0)
const arr=[1,2,3,4]
const renderArray=useMemo(() => {
console.log('arr渲染了')
return arr.map((item,index)=><div key={index}>{item}</div>)
}, [])//当[count]填入count就会有关联
return (
<div>
<button onClick={()=>{setCount(preCount=>preCount+1)}}>+</button>
<p>{count}</p>
<hr/>
{renderArray}
</div>
)
}
改变count时,与count无关的renderArray不会再进行重复的渲染
- useCallback
返回一个 memoized 回调函数
专门用于父子组件的连通
useCallback案例:
组件的状态改变,组件需要重新渲染,子组件是父组件的一部分,所以子组件也会重新渲染
要求:
1. 我需要给子组件传递一个方法
2. count改变时,这个方法触发,val改变时不触发
src/components/Father.js - 不使用useCallback的情况
import React,{useState} from 'react'
import Son from './Son'
export default function Father() {
const [count,setCount]=useState(0)
const [val,setVal]=useState('')
function renderItem(){
console.log('render')
}
return (
<div>
<button onClick={()=>{setCount(preCount=>preCount+1)}}>+</button>
<p>{count}</p>
<hr/>
<input type='text' onChange={e=>{setVal(e.target.value)}} defaultValue={val}/>
{/* 将renderItem函数传给Son组件 */}
<Son renderItem={renderItem}/>
</div>
)
}
src/components/Father.js - 使用useCallback的情况
import React,{useState,useCallback} from 'react'
import Son from './Son'
export default function Father() {
const [count,setCount]=useState(0)
const [val,setVal]=useState('')
//不使用useCallback的情况
// function renderItem(){
// console.log('render')
// }
//----------------------------------------
//使用useCallback的情况
const renderItem=useCallback(()=>{
console.log('render')
},[count])
return (
<div>
<button onClick={()=>{setCount(preCount=>preCount+1)}}>+</button>
<p>{count}</p>
<hr/>
<input type='text' onChange={e=>{setVal(e.target.value)}} defaultValue={val}/>
{/* 将renderItem函数传给Son组件 */}
<Son renderItem={renderItem}/>
</div>
)
}
src/components/Son.js
import React from 'react'
function Son(props) {
const {renderItem}=props
return (
<div>
{renderItem()}
</div>
)
}
export default React.memo(Son)//只有需要被渲染的组件被渲染,所以这是一个性能提升的方法
总结:useMemo 传的是值,useCallback 传的是方法,给的东西不一样但是效果是一样的,都是来做优化的
-
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。也就是说它是最大的,管所有的DOM节点,写在index.js文件中,项目中可用可不用 -
useDebugValue
useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
自定义Hook核心作用是用来复用逻辑的,相当与类组件中的高阶组件,就是一个封装函数,把公用逻辑放到封装函数里去了,谁调用自定义Hook,谁就有这个公用的逻辑了
useDebugValue案例:
src/hooks/index.js
//! 自定义封装Hooks
//! Hooks就是一个函数而已
//! 逻辑复用
import React,{useDebugValue,useState} from 'react'
export function useStatus(num){
const [count,setCount]=useState(0)
useDebugValue(count ? 1 : 0)//里面跟一个状态
if(num!==0){
return '不为0'
}else{
return '为0'
}
}
App.js
import React from 'react'
import {useStatus} from './hooks'
export default function App() {
const msg=useStatus(0)
return (
<div>
{msg}
</div>
)
}
- useReducer
与 useState 的区别:
1.当 state 状态值结构比较复杂时,使用 useReducer 更有优势。
2.使用 useState 获取的 setState 方法更新数据时是异步的;而使用 useReducer 获取的 dispatch 方法更新数据是同步的。
import { useReducer } from '@alipay/bigfish/react';
const initState: any = {
tableData: [],
pageTotal: 0,
};
const reducer = (state: any, action: any) => {
const { type, payload } = action;
switch (type) {
case 'setState':
return { ...state, ...payload };
default:
return state;
}
};
export default function billManagementState() {
const [state, dispatch] = useReducer(reducer, initState);
return { state, dispatch };
}