Hooks是React16.8的新增特性。它可以让你在不编写class的情况下使用state,以及其他的React特性,用简单的话来说,React
Hooks就是一些React提供的内置函数,这些函数可以让Function Component和Class
Component一样能够拥有组件状态(state)以及进行副作用(side effect)。
虽然现在有了React Hooks,但是React中的class是没有计划移除的。Class组件之间难以服用状态逻辑,复杂组件难以理解,使用class类组件导致学习成本变高,生命周期逻辑难以维护,同一个生命周期中包含不同逻辑,同一类逻辑分散在不同生命周期。父组件给子组件传递函数时,必须绑定this。
<button onClick={this.handleClick2}>btn1</button>
<button onClick={this.handleClick1.bind(this)}>btn2</button>
<button onClick={() => this.handleClick1()}>btn3</button>
<button onClick={this.handleClick3}>btn4</button>
副作用的关注点分离,副作用指那些没有发生在数据向视图转换过程中的逻辑,入Ajax请求,访问原生dom元素,本地持久化缓存,绑定/解绑时间,添加订阅,设置定时器,记录日志,以往这些副作用都是写在类组件生命周期函数中的,而useEffect在全部渲染完毕后才会执行,userLayoutEffect会在浏览器layout之后,painting之前执行。
在 React 的世界中,不同的 hooks 用处也是不同的,对 React hooks 按照功能分类,分成了 数据更新驱动,状态获取与传递,执行副作用,状态派生与保存,和工具类型, 具体功能划分和使用场景如下图:
useState可以让函数组件像类组件那样拥有state,函数组件通过userState可以让组件重新渲染,更新视图。
const [xxx,setxxxx] = React.useState(initValue)
参数一、xxx,目的提供给UI,座位渲染视图的数据源。
参数二、setxxx,改变state的函数推动函数组件渲染的渲染函数。
参数三、initValue,两种类型,第一种情况是非函数,将作为state初始化的值,第二种情况是函数,函数返回值作为useState初始化的值。
使用示例
import React,{useState} from "react"
function User(){
const [count,setCount] = useState(5) // 默认值为0
return (
<div>
count:{count}
<br/>
{/* 使setCount让count+1操作 */}
<button onClick={ ()=> {setCount(count+1)} } >改变count</button>
</div>
);
}
export default User
在使用React的时候,如果遇到了状态管理,一般会用到Redux,而React本身不提供状态管理的,但是useReducer提供了状态管理。userReducer是react-hooks提供的能够在无状态组件中运行的类似redux的功能api。
const [state,dispatch] = useReducer(reducer,initialState)
参数一、state更新之后的state值。
参数二、派发更新的dispatchAction函数,本质上和useState的dispatchAction是一样的。
参数三、一个函数reducer,我们可以认为他就是一个redux中的reducer,reducer的参数就是常规的reducer里面的state和action,返回改变后的state,这里需要注意的是,如果返回的state和之前的state,内存指向相同,那么组件将不会更新。
userReducer基本使用1:
import { useReducer } from "react";
/* 当state需要维护多个数据且它们互相依赖时,推荐使用useReducer
组件内部只是写dispatch({...})
处理逻辑的在useReducer函数中。获取action传过来的值,进行逻辑操作
*/
// reducer计算并返回新的state
function reducer(state, action) {
const { type, nextName } = action;
switch (type) {
case "ADD":
return {
...state,
age: state.age + 1
};
case "NAME":
return {
...state,
name: nextName
};
}
throw Error("Unknown action: " + action.type);
}
export default function ReducerTest() {
const [state, dispatch] = useReducer(reducer, { name: "wuyong", age: 12 });
function handleInputChange(e) {
dispatch({
type: "NAME",
nextName: e.target.value
});
}
function handleAdd() {
dispatch({
type: "ADD"
});
}
const { name, age } = state;
return (
<>
<input value={name} onChange={handleInputChange} />
<br />
<button onClick={handleAdd}>添加1</button>
<p>
Hello,{name}, your age is {age}
</p>
</>
);
}
userReducer基本使用2:
import React,{useReducer} from "react"
const User = ()=> {
const [num,dispatchNum] = useReducer((state,action)=>{
const { resetNum , name } = action
switch(name){
case "add":
return state+1
case "sub":
return state-1
case "reset":
return resetNum
}
return state
},0)
return (
<div>
count:{num}
<br/>
<button onClick={()=>dispatchNum({ name:'add' })} >增加</button>
<button onClick={()=>dispatchNum({ name:'sub' })} >减少</button>
<button onClick={()=>dispatchNum({ name:'reset' ,resetNum:0 })} >重置</button>
</div>
);
}
export default User
useSyncExternalStore 能够让React组件在concurrent模式下安全地有效地读取外界数据源,在组件渲染过程中能够检测到变化,并且在数据源发生变化时侯,能够调度更新。当读取到外部状态发生了变化,会触发一个强制更新,来保证结果的一致性。
useSyncExternalStore(subscribe,getSnapshot,getServerSnapshot)
参数一、subscribe为订阅数,当数据发生改变的时候,会触发subscribe,在useSyncExternalStore会通过带有记忆性的getSnapshot来判别数据是否发生变化,如果发生变化,那么会强制更新数据。
参数二、getSnapshot可以理解成一个带有记忆功能的选折起,当store变化的时候,会通过getSnapshot生成新的状态值,这个状态值可提供给组件数据源使用,getSnapshot可以检查订阅的值是否发生改变,改变的话那么会触发更新。
参数三、getServerSnapshot用于hydration模式下的getSnapshot。
当组件init,dom render完成操纵dom,请求数据(如componentDidMount)等;不限制条件,组件每次更新都会触发useEffect --> componentDidUpdate与componentWillreceiveprops;
useEffect(()=>{return destory},dep)
参数一、第一个参数callback回调,返回的desotry作为下一次callback执行之前调用,用于清楚上一次callback产生的副作用。第一个参数为处理事件,第二个参数为接受数组,为限定条件,当数组发生变化时,触发事件,为数组只在组件初始化时触发。
参数二、作为依赖项,是一个数组,可以有多个依赖项,依赖项改变,执行上一次回调返回的destory,和执行新的effect第一个参数callback。
对于useEffect执行,react处理逻辑是采用异步调用,面对每一个effect的callback,react会向setTimeout回调函数一样,放入任务列队,等到主线程任务完成,DOM更新,js执行完成,绘图绘制完成才执行,所以effect回调函数不会阻塞浏览器绘制视图。
注意:useEffect⽆法直接使⽤async await
import React, { useState, useRef } from "react"
import { useEffect } from "react"
/* 模拟数据交互 */
function getUserInfo(a) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: a,
age: 16
})
}, 500)
})
}
const SwitchPage = ({ a }) => {
const [userMessage, setUserMessage] = useState({})
const div = useRef()
const [number, setNumber] = useState(0)
// 模拟事件处理函数
const handleResize = () => { }
// useEffect使用
useEffect(() => {
// 请求数据
getUserInfo(a).then(res => {
console.log("请求数据")
setUserMessage(res)
})
// 定时器,延时器
const timer = setInterval(() => console.log("定时器", 666), 1000)
// 操作dom
console.log(div.current)
// 事件监听
window.addEventListener('resize', handleResize)
// 消除副作用,定时器,延时器
return () => {
/*
只有当props->a和state->number改变的时候 ,useEffect副作⽤函数重新执⾏ ,
如果此时数组为空[],证明函数只有在初始化的时候执⾏⼀次相当于componentDidMount
*/
clearInterval(timer)
window.removeEventListener('resize', handleResize)
}
}, [a, number])
return (<div ref={div} >
<span>{userMessage.name}</span>
<span>{userMessage.age}</span>
<div onClick={() => setNumber(1)} >{number}</div>
</div>)
}
export default SwitchPage
1.操作DOM
2.请求数据
3.注册事件监听器, 事件绑定
4.清除定时器,事件解绑
渲染更新之前的,如果你希望界面必须在执行完一些业务代码后才进行渲染,那么可以把代码放到useLayoutEffect的effect中。首先useLayoutEffect在DOM更新之后,浏览器绘制之前,这样可以方便修改DOM,获取DOM信息,这样浏览器智慧绘制一次,如果修改DOM布局放在useEffect,那么useEffect执行是在浏览器绘制视图之后,接下来又更改DOM,这样会导致浏览器再次回流和重绘,而且由于这两次重绘,视图上可能会照成闪现突兀的效果。
import React, { useState, useRef } from "react"
import { useEffect,useLayoutEffect } from "react"
const SwitchPage = () => {
const [count,setCount] = useState(0)
useEffect(()=>{
if(count===0){
const randomNum = 111
setCount(111)
}
},[count])
useLayoutEffect(()=>{
if(count === 0){
const randomNum = 111
setCount(111)
}
},[count])
return (
<div>
<div onClick={()=>setCount(0)}>{count}</div>
</div>)
}
export default SwitchPage
用来获取元素,缓存数据,入参可以作为初始值。
import React, { useState, useRef } from "react"
import { useEffect,useLayoutEffect } from "react"
const SwitchPage = () => {
const dom = useRef(null)
const handerSubmit = () =>{
console.log(dom.current)
}
return (
<div>
<div ref={dom} >表单组件</div>
<button onClick={()=>handerSubmit()}>提交</button>
</div>)
}
export default SwitchPage
const contextValue = useContext(context)
用来获取父级组件传递过来的context值,这个当前值就是最近的父级组件Provider的value;可以使用useContext来获取父级组件传递过来的context值,这个当前值就是最近的父级组件,Provider设置的value值,useContext参数一般是由createContext方式创建的,也可以父级上下文context传递的【参数为context】。useContext可以替代context.Consumer来获取Provider中保存的value值。
当数据进行(父子或者爷爷传递孙子时)多成绩数据传递的时候,在函数组件中我们可以运用useContext进行数据传输。
举个简单的demo例子,子组件通过按钮修改爷爷组件中的num值:
目录结构:
ContextDemo代码:
import React , {createContext} from 'react'
const DemoContext = createContext()
export default DemoContext
GrandDad代码:
import React,{useState} from "react"
import Father from './Father'
import DemoContext from "./ContextDemo"
function GrandDad(){
const[num,setNum] = useState(0)
const handleAddClick=()=>{
setNum(num=>num+1)
}
const handleSubClick=()=>{
setNum(num=>num-1)
}
return <div style={{background:"yellow"}}>
爷爷组件
<DemoContext.Provider value={{num,handleAddClick,handleSubClick}}>
{num}<br/>
<Father></Father>
</DemoContext.Provider>
</div>
}
export default GrandDad
Father代码:
import React from "react"
import Son from './Son'
function Father(){
return <div style={{background:"red"}}>
爸爸组件<br />
<Son></Son>
</div>
}
export default Father
son代码:
import React,{useContext} from "react"
import DemoContext from './ContextDemo'
function Son(){
const variable = useContext(DemoContext)
console.log("儿子组件",variable)
return <div style={{background:"pink"}}>
儿子组件<br />
<div>
<button onClick={variable.handleSubClick}>-</button>
{variable.num}
<button onClick={variable.handleAddClick}>+</button>
</div>
</div>
}
export default Son
useMemo它是用来作为一个缓存使用,只有当一个依赖项改变的时候才会发生变化,否则拿缓存的值,就不用在每次渲染的时候在做计算,这种有助于避免在每次渲染的时候进行高开销计算,uerMemo的函数在函数渲染期间执行,所以不该在此期间做的操作请去除,如果没有提供依赖项数据,每次都会重新计算值,相当于没有优化了。
简单demo例子
import React,{useState,useMemo} from "react"
function Demo2(){
const [count,setCount] = useState(10)
const [value,setValue] = useState(10)
let memoMessage = useMemo(()=>{
console.log("useMemo....")
return `memoMessage will change with ${count}`
},[count])
function hangleClick(name){
if(name==="count"){
setCount(count + 1)
setValue(value + 1)
}else{
setValue(value + 1)
}
}
return <div>
<p>{memoMessage}</p>
<h3>count:{count}</h3>
<h3>value:{value}</h3>
<br/>
<button onClick={()=>{hangleClick("count")}}>count</button>
<button onClick={()=>{hangleClick("value")}}>value</button>
</div>
}
export default Demo2
函数相等性检查,使用useCallback优化代码,是对传过来的回调函数优化,返回的是一个函数,useMemo可以返回任何值,函数,对象等都可以,简单的来说就是返回一个函数,只有在依赖项发生变化的时候才会更新返回一个函数。
React.useCallback(function,array)
使用场景:当父组件传入子组件函数时,由于React.memo进行的是浅比较,重新渲染时,函数的引用是发生改变的,所以会导致子组件重新渲染,而用useCallback后只要依赖的变量未发生改变将始终返回同一个函数引用,不会导致子组件重新渲染。
注意区别于useMemo 缓存的是函数的返回结果。useCallback缓存的是函数。
使用案例:
import React, { useCallback, useState, useEffect } from 'react';
function Demo_useCallback() {
const [query, setQuery] = useState(1);
const [queryOther, setQueryOther] = useState(1);
const fecthData = useCallback(() => {
console.log('新的fetch');
return query;
}, [query])
const add = () => {
console.log('点击add');
setQuery(query + 1);
}
const addOther = () => {
console.log('点击addOther');
setQueryOther(queryOther + 1);
}
return (
<div>
<Child fecthData={fecthData} />
<button onClick={add}>+1</button>
<button onClick={addOther}>other+1</button>
father
<div>{ query }</div>
</div>
);
}
const Child = React.memo((props)=>{
console.log('子组件相关内容');
useEffect(() => {
const querN = props.fecthData();
console.log('子组件调用该函数获取到相关内容', querN);
}, [props.fecthData])
return <div>
Child
</div>
}
)
export default Demo_useCallback;
React.memo检测的是props中数据的栈地址是否改变。而父组件重新构建的时候,会重新构建父组件中的所有函数(旧函数销毁,新函数创建,等于更新了函数地址),新的函数地址传入到子组件中被props检测到栈地址更新。也就引发了子组件的重新渲染。所以,在上面的代码示例里面,子组件是要被重新渲染的。上文中的fetchData因为失去了useCallback的保护使得子组件的props发生了变化,从而React.memo也失去了作用,而且因为fetchData因为失去了useCallback的保护,使得点击other+1按钮改变无关的变量时,子组件也调用了请求函数。
以上就是对react常用hooks进行的总结。