文章目录
强烈推荐这个react-hooks的学习教程:https://github.com/puxiao/react-hook-tutorial
react hooks
react hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
三个常用的Hook
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
useState
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue);
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数
(4). setXxx()2种写法:
- setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值。如果多次连续调用,最后只会更新最近一次的值(合并更新一次状态)。
- setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值。如果多次连续调用,参数value是最新的值,会更新多次状态。
function Demo() {
// useState函数参数,初始化指定的值会在内部作缓存
// 如果要使用多个状态,需要编写多次React.useState()
const [count, setCount] = React.useState(1);
const [name, setName] = React.useState("小明");
function add() {
// setCount(count + 1);
// setCount的第二种写法
setCount(count => count + 1);
}
function changeName() {
setName("晓明");
}
return (
<div>
<h2>求和为:{count}</h2>
<h2>姓名:{name}</h2>
<button onClick={add}>+1</button>
<button onClick={changeName}>改名字</button>
</div>
);
}
setXxx是采用 “异步直接赋值” 的形式,并不会像类组件中的setState()那样做“异步对比累加赋值”。
“异步”?这里的“异步”和类组件中setState中的异步是同一个意思,都是为了优化React渲染性能而故意为之。也就是无法在setXxx之后立即拿到最新的值。
“直接赋值”:
1、在Hook中,对于简单类型数据,比如number、string类型,可以直接通过setXxx(newValue)直接进行赋值。
2、但对于复杂类型数据,比如array、object类型,若想修改其中某一个属性值而不影响其他属性,则需要先复制出一份,修改某属性后再整体赋值。
通过 setXxx 设置新值,但是如果新值和当前值完全一样,那么会引发React重新渲染吗?通过React官方文档可以知道,当使用 setXxx 赋值时,Hook会使用Object.is()来对比当前值和新值,结果为true则不渲染,结果为false就会重新渲染。
useEffect
(1)Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)。
(2)React中的副作用操作
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
(3)语法和说明:useEffect(effect,[deps])函数可以传入2个参数,第1个参数为我们定义的执行函数、第2个参数是依赖关系(可选参数)。若一个函数组件中定义了多个useEffect,那么他们实际执行顺序是按照在代码中定义的先后顺序来执行的。
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4)可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
(5)在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的。
- 有时候,我们只想**在 React 更新 DOM 之后运行一些额外的代码。**比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。因为我们在执行完这些操作之后,就可以忽略他们了。
- 一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!
如果React.useEffect只传入了第一个函数参数,相当于是检测所有的状态。当页面第一次加载时会执行一次,每当有状态更新时也会执行该函数,也就是在每次渲染的时候都会执行。相当于是componentDidMount和componentDidUpdate。
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
}
有些副作用可能需要清除,这是可以返回一个函数,返回的函数相当于是componentWillUnmount
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// 订阅
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
// 取消订阅
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
可以使用多个Effect Hook,Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的_每一个_ effect。
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
如果传入第二个参数是空数组,则第一个函数参数只会执行一次,谁也不监测。则第一个函数参数相当于是componentDidMount
如果传入的第二个参数不是空数组,数组中有对应的元素[count],则只检测该状态的改变,状态改变时会重新执行函数。
useEffect(() => {
document.title = `You clicked ${count} times`
}, [count]); // 仅在 count 更改时更新
总结:useEffect的第二个参数可分为以下几种情况。
1、若缺省,则组件挂载、组件重新渲染、组件即将被卸载前(return的函数),每一次都会触发该useEffect;
2、若传值,则必须为数组,数组的内容是函数组件中通过useState自定义的变量或者是父组件传值过来的props中的变量。在组件挂载时会执行一次,之后只有数组内的变量发生变化时才会触发useEffect;
3、若传值,但是传的是空数组 [],则表示该useEffect里的内容仅会在“挂载完成后和组件即将被卸载前(return的函数)”执行一次;
常见问题
下方案例中,count的值从0变为1后就不再变化?为什么?
function Demo() {
const [count, setCount] = React.useState(1);
// 使用生命周期钩子,这里相当于是componentDidMount和componentWillUnmount的结合
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count + 1);
}, 1000);
return function () {
clearInterval(timer);
};
}, []);
// 卸载组件
function unMount() {
ReactDom.unmountComponentAtNode(document.getElementById("root"));
}
return (
<div>
<h2>{count}</h2>
<button onClick={unMount}>卸载组件</button>
</div>
);
}
关键点在于我们对useEffect传入来一个空数组,并且使用了setCount(count + 1);
如果传入空的依赖数组 [],意味着该 hook 只在组件挂载时运行一次,并非重新渲染时。但如此会有问题,在 setInterval 的回调中,count 的值不会发生变化。因为当 effect 执行时,我们会创建一个闭包,并将 count 的值被保存在该闭包当中,且初值为 0。每隔一秒,回调就会执行 setCount(0 + 1),因此,count 永远不会超过 1。
只需要改为setCount(count => count + 1)就可以解决这个问题,这种更新不再依赖于外部的count变量。
useRef
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = React.useRef()
useRef可以“获取某些组件挂载完成或重新渲染完成后才拥有的某些对象”的引用,且保证该引用在组件整个生命周期内固定不变,都能准确找到我们要找的对象。
useRef关联对象的2种用法:
1、针对 JSX组件(原生标签),通过属性 ref={xxxRef} 进行关联。
2、针对 useEffect中的变量,通过 xxxRef.current 进行关联。
//先定义一个xxRef引用变量,用于“勾住”某些组件挂载完成或重新渲染完成后才拥有的某些对象
const xxRef = useRef(null);
//针对 JSX组件,通过属性 ref={xxxRef} 进行关联
<xxx ref={xxRef} />
//针对 useEffect中的变量,通过 xxxRef.current 进行关联
useEffect(() => {
xxRef.current = xxxxxx;
},[]);
补充说明:
1、useRef是专门针对函数组件的,如果是类组件则使用React.createRef()。
2、React.createRef()也可以在函数组件中使用。
只不过React.createRef创建的引用不能保证每次重新渲染后引用固定不变。如果你只是使用React.createRef“勾住”JSX组件转换后对应的真实DOM对象是没问题的,但是如果想“勾住”在useEffect中创建的变量,那是做不到的。二者都想可以“勾住”,只能使用useRef。
在线案例:https://codesandbox.io/s/useref-40sxf5
使用案例1
若我们有一个组件,该组件只有一个输入框,我们希望当该组件挂载到网页后,自动获得输入焦点。
需求分析:需要使用useRef “勾住”这个输入框,当它被挂载到网页后,通过操作原生html的方法,将焦点赋予该输入框上。
import React,{useEffect,useRef} from 'react'
function Component() {
//先定义一个inputRef引用变量,用于“勾住”挂载网页后的输入框
const inputRef = useRef(null);
useEffect(() => {
//inputRef.current就是挂载到网页后的那个输入框,一个真实DOM,因此可以调用html中的方法focus()
inputRef.current.focus();
},[]);
return <div>
{/* 通过 ref 属性将 inputRef与该输入框进行“挂钩” */}
<input type='text' ref={inputRef} />
</div>
}
export default Component
注意:
1、在给组件设置 ref 属性时,只需传入 inputRef,千万不要传入 inputRef.current。
2、在“勾住”渲染后的真实DOM输入框后,能且只能调用原生html中该标签拥有的方法。
使用案例2
若我们有一个组件,该组件的功能需求如下:
1、组件中有一个变量count,当该组件挂载到网页后,count每秒自动 +1。
2、组件中有一个按钮,点击按钮可以停止count自动+1。
import React,{useState,useEffect,useRef} from 'react'
function Component() {
const [count,setCount] = useState(0);
const timerRef = useRef(null);//先定义一个timerRef引用变量,用于“勾住”useEffect中通过setIntervale创建的计时器
useEffect(() => {
//将timerRef.current与setIntervale创建的计时器进行“挂钩”
timerRef.current = setInterval(() => {
setCount((prevData) => { return prevData +1});
}, 1000);
return () => {
//通过timerRef.current,清除掉计时器
clearInterval(timerRef.current);
}
},[]);
const clickHandler = () => {
//通过timerRef.current,清除掉计时器
clearInterval(timerRef.current);
};
return (
<div>
{count}
<button onClick={clickHandler} >stop</button>
</div>
)
}
export default Component
在 TS中使用 useRef 创建计时器注意事项
timerRef.current = setInterval(() => {
setCount((prevData) => { return prevData +1});
}, 1000);
如果是在 TS 语法下,上面的代码会报错误:不能将类型“Timeout”分配给类型“number”。
造成这个错误提示的原因是:
- TypeScript 是运行在 Nodejs 环境下的,TS 编译之后的代码是运行在浏览器环境下的。
- Nodejs 和浏览器中的 window 他们各自实现了一套自己的 setInterval
- 原来代码 timerRef.current = setInterval( … ) 中 setInterval 会被 TS 认为是 Nodejs 定义的 setInterval,而 Nodejs 中 setInterval 返回的类型就是 NodeJS.Timeout。
- 所以,我们需要将上述代码修改为:timerRef.current = window.setInterval( … ),明确我们调用的是 window.setInterval,而不是 Nodejs 的 setInterval。
useContext
useContext是来解决什么问题?
useContext是<XxxContext.Consumer>的替代品,可以大量简化获取共享数据值的代码。
补充说明:
1、函数组件和类组件,对于<XxxContext.Provider>、<XxxContext.Consumer>使用方式没有任何差别。
2、你可以在函数组件中不使用useContext,继续使用<XxxContext.Consumer>,这都没问题。只不过使用useContext后,可以让获取共享数据相关代码简单一些。
使用案例
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。
useContext(MyContext) 只是让你能够_读取_ context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件_提供_ context。
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
示例中是把XxxContext直接定义在组件内部,没有使用import,但是这种方式没有办法在其它组件中使用XxxContext。如果将XxxContext定义在外部单独的模块中,各个组件都可以引用。
下方示例中,ContextCmp --> MiddleCmp --> ChildCmp,ContextCmp组件要传递数据给ChildCmp
// context.js
import React from "react";
export const PersonContext = React.createContext({ name: "a", age: 10 });
// ContextCmp.jsx
import { PersonContext } from "./context";
import MiddleCmp from "./MiddleCmp";
export default function ContextCmp() {
return (
<div>
<h1>ContextCmp</h1>
<PersonContext.Provider value={{ name: "b", age: 1 }}>
<MiddleCmp />
</PersonContext.Provider>
</div>
);
}
// MiddleCmp.jsx
import ChildCmp from "./ChildCmp";
export default function MiddleCmp() {
return (
<div>
<h2>MiddleCmp</h2>
<ChildCmp />
</div>
);
}
// ChildCmp.jsx
import { useContext } from "react";
import { PersonContext } from "./context";
export default function ChildCmp() {
const data = useContext(PersonContext);
return (
<div>
<h3>ChildCmp</h3>
<div>
{data.name}--{data.age}
</div>
</div>
);
}
useContext只是简化了获取共享数据value的代码,但是对于<XxxContext.Provider>的使用没有做任何改变
为什么不使用redux
在Hook出现以前,React主要负责视图层的渲染,并不负责组件数据状态管理,所以才有了第三方Redux模块,专门来负责React的数据管理。
但是自从有了Hook后,使用React Hook 进行函数组件开发,实现数据状态管理变得切实可行。只要根据实际项目需求,使用useContext以及useReducer,一定程度上是可以满足常见需求的。
useReducer
useReducer(reducer,initialValue)函数通常传入2个参数,第1个参数为我们定义的一个“由dispatch引发的数据修改处理函数”,第2个参数为自定义数据的默认值,useReducer函数会返回自定义变量的引用和该自定义变量对应的“dispatch”。
useReducer是useState的升级版,可以实现复杂逻辑修改,而不是像useState那样只是直接赋值修改。补充说明:
1、在React源码中,实际上useState就是由useReducer实现的,所以useReducer准确来说是useState的原始版。
2、无论哪一个Hook函数,本质上都是通过事件驱动来实现视图层更新的。
代码形式:
import React, { useReducer } from 'react'; //引入useReducer
//定义好“事件处理函数” reducer
function reducer(state, action) {
switch (action) {
case 'xx':
return xxxx;
case 'xx':
return xxxx;
default:
return xxxx;
}
}
function Component(){
//声明一个变量xxx,以及对应修改xxx的dispatch
//将事件处理函数reducer和默认值initialValue作为参数传递给useReducer
const [xxx, dispatch] = useReducer(reducer, initialValue);
//若想获取xxx的值,直接使用xxx即可
//若想修改xxx的值,通过dispatch来修改
dispatch('xx');
}
//请注意,上述代码中的action只是最基础的字符串形式,事实上action可以是多属性的object,这样可以自定义更多属性和更多参数值
//例如 action 可以是 {type:'xx',param:xxx}
使用案例
import React, { useReducer, useState } from "react";
function reducer(state, action) {
const { type, payload } = action;
switch (type) {
case "add":
return state + payload;
case "sub":
return state - payload;
case "mul":
return state * payload;
default:
return state;
}
}
export default function Count() {
const [num, setNum] = useState(0);
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<h3>{count}</h3>
<input
type="number"
value={num}
onChange={(e) => setNum(parseInt(e.target.value))}
/>
<button
onClick={() => {
dispatch({ type: "add", payload: num });
}}
>
+
</button>
<button
onClick={() => {
dispatch({ type: "sub", payload: num });
}}
>
-
</button>
<button
onClick={() => {
dispatch({ type: "mul", payload: num });
}}
>
*
</button>
</div>
);
}
如果不使用useReducer,而是使用之前学习过的useState,那么对count的每一种修改逻辑代码,都必须分散写在每个按钮的点击事件处理函数中。使用useReducer之后,修改count的逻辑都集中在一起了。
示例中所有的变量都是在同一个组件内定义和修改的,现实项目中肯定牵扯到不同模块组件之间共享并修改某个变量,那又该怎么办呢?**用 useReducer + useContext 来实现全局不同层级组件共享并修改某变量(模拟redux的功能)。**useReducer可以让我们实现复杂逻辑的数据修改,结合useContext更能做到全局数据共享和修改。
使用useContext和useReducer实现操作全局共享数据
需求:
1、父组件中定义某变量xx;
2、任何层级下的子组件都可以轻松获取变量xx、并且可以“修改”变量xx;
实现原理
- 用 useContext 实现“获取全局数据”
- 用 userReducer 实现“修改全局数据”
在线案例:https://codesandbox.io/s/usereducer-usecontext-ymdxeh
useCallback
只有父组件同时有多个子组件时,才有必要使用useCallback去做性能优化,防止某一个子组件引发的重新渲染也导致其他子组件跟着重新渲染。
useCallback(callback,deps)函数通常传入2个参数,第1个参数为我们定义的一个“处理函数”,通常为一个箭头函数。第2个参数为该处理函数中存在的依赖变量,请注意凡是处理函数中有的数据变量都需要放入deps中。如果处理函数没有任何依赖变量,可以传入一个空数组[]。
userCallback需要配合经过优化的并使用引用相等性去避免非必要渲染的子组件来使用。
- 子组件是类组件:使用shouldComponentUpdate或者PureComponent来避免非必要渲染
- 子组件是函数组件:使用React.memo()来避免非必要渲染
使用案例
在线查看:https://codesandbox.io/s/usecallback-wc11kd
若我们有一个自定组件,代码如下:
import React from "react";
function Button({ children, onClick }) {
console.log(`render...${children}`);
return <button onClick={onClick}>{children}</button>;
}
{/* 使用React.memo()包裹住要导出的组件 */}
export default React.memo(Button);
父组件:
使用useCallback后,点击一个按钮并不会引起另一个按钮组件的重新渲染
import React, { useState, useCallback } from "react";
import Button from "./Button";
export default function CallbackCmp() {
const [count, setCount] = useState(0);
const [age, setAge] = useState(19);
const changeCount = useCallback(() => {
setCount(count + 1);
}, [count]);
const changeAge = useCallback(() => {
setAge(age + 1);
}, [age]);
return (
<div>
<h1>
数字:{count},年龄:{age}
</h1>
<Button onClick={changeCount}>button1</Button>
<Button onClick={changeAge}>button2</Button>
</div>
);
}
useMemo
useMemo可以将某些函数的计算结果(返回值)挂载到react底层原型链上,并返回该函数返回值的索引。当组件重新渲染时,如果useMemo依赖的数据变量未发生变化,那么直接使用原型链上保存的该函数计算结果,跳过本次无意义的重新计算,达到提高组件性能的目的。
useMemo的作用是“勾住”组件中某些处理函数的返回值。当组件重新渲染时,需要再次用到这些函数返回值,此时不再重新执行一遍运算,而是直接使用之前运算过的返回值。
useCallback是针对函数,useMemo是针对函数返回值。 useMemo并不需要子组件必须使用React.memo。
useCallback中的参数fn主要用来处理各种操作事务的代码,例如修改某变量值或加载数据等。而useMemo中的fn主要用来处理各种计算事务的代码。useCallback和useMemo都是为了提升组件性能,但是他们两个的适用场景却不相同,不是谁是谁的替代品或谁是谁的简化版。
使用方式
useMemo(create,deps)函数通常传入2个参数,第1个参数为我们定义的一个“包含复杂计算且有返回值的函数”。
第2个参数为该处理函数中存在的依赖变量,只有当处理函数依赖的变量发生改变时才会重新计算并保存一次函数返回结果。请注意凡是处理函数中有的数据变量都需要放入deps中。如果处理函数没有任何依赖变量,可以传入一个空数组[]。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
const xxxValue = useMemo(() => {
let result = xxxxx;
//经过复杂的计算后
return result;
}, [xx]);
1、使用useMemo()将计算函数包裹住,将计算函数中使用到的数据变量作为作为第2个参数。
2、计算函数体内,把计算结果以 return 形式返回出去。
3、xxxValue 为该函数返回值,可以直接使用。
使用案例
若某React组件内部有2个number类型的变量num,random,有2个button,点击之后分别可以修改num,random的值。 与此同时,该组件中还要求显示出num范围内的所有质数个数总和。
需求分析:
1、显示出num范围内的所有质数个数总和,这个就是本组件中的“复杂的计算”。
2、只要num的值未发生变化,质数总数是固定的,那么我们应该避免每次重新渲染时都需要计算一遍。
3、useMemo函数,就是帮我们解决这个问题。
在线查看:https://codesandbox.io/s/usememo-cwoqd8
useImperativeHandle
useImperativeHandle可以让父组件获取子组件内某些自定义函数或变量。本质上其实是子组件将自己内部的函数或变量通过useImperativeHandle添加到父组件中useRef定义的对象中。
useImperativeHandle(ref,create,[deps])函数前2个参数为必填项,第3个参数为可选项。
- 第1个参数为父组件通过useRef定义的引用变量;
- 第2个参数为子组件要附加给ref的对象,该对象中的属性即子组件想要暴露给父组件的函数(方法);
- 第3个参数为可选参数,为函数的依赖变量。凡是函数中使用到的数据变量都需要放入deps中,如果处理函数没有任何依赖变量,可以忽略第3个参数。
如果想使用useImperativeHandle,那么还要结合useRef、React.forwardRef一起使用
1、useRef创建引用变量
2、React.forwardRef将引用变量传递给子组件
3、useImperativeHandle将子组件内定义的函数作为属性,添加到父组件中的ref对象上。
使用案例
在线查看:https://codesandbox.io/s/useimperativehandle-gt8s5e
举例,若某子组件的需求为:
1、有变量count,默认值为0
2、有一个函数 addCount,该函数体内部执行 count+1
3、有一个按钮,点击按钮执行 addCount 函数
父组件的需求为:
1、父组件内使用上述子组件
2、父组件内有一个按钮,点击执行上述子组件内定义的函数 addCount
子组件代码:
import React, { useState, useImperativeHandle } from "react";
function Count(props, ref) {
const [count, setCount] = useState(0);
// 子组件通过useImperativeHandle函数,将addCount函数添加到父组件中的ref.current中
useImperativeHandle(ref, () => ({ add }));
const add = () => {
setCount(count + 1);
};
return (
<div>
<h1>{count}</h1>
<button onClick={add}>count+1</button>
</div>
);
}
// 子组件导出时需要被React.forwardRef包裹,否则无法接收 ref这个参数
export default React.forwardRef(Count);
父组件代码:
import React, { useRef } from "react";
import Count from "./Count";
export default function ImperativeCmp() {
const childRef = useRef();
const addCount = () => {
console.log(childRef);
// 父组件调用子组件内部的 add 函数
childRef.current.add();
};
return (
<div>
{/* 父组件通过给子组件添加 ref 属性,将childRef传递给子组件,
子组件获得该引用即可将内部函数添加到childRef中 */}
<Count ref={childRef} />
<button onClick={addCount}>父组件中的按钮</button>
</div>
);
}
useLayoutEffect
useLayoutEffect的作用是“勾住”挂载或重新渲染完成这2个组件生命周期函数useLayoutEffect使用方法、所传参数和useEffect完全相同。他们的不同点在于,你可以把useLayoutEffect等同于componentDidMount、componentDidUpdate,因为他们调用阶段是相同的。而useEffect是在componentDidMount、componentDidUpdate调用之后才会触发的。
也就是说,当组件所有DOM都渲染完成后,同步调用useLayoutEffect,然后再调用useEffect。useLayoutEffect永远要比useEffect先触发完成。
在react官方文档中,明确表示只有在useEffect不能满足你组件需求的情况下,才应该考虑使用useLayoutEffect。 官方推荐优先使用useEffect。
useDebugValue
useDebugValue
自定义hook
像useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect、useDebugValue这10个hook是react默认自带的hook,而所谓自定义hook就是由我们自己编写的hook。
所谓自定义hook就是把原来写在函数组件内的hook相关代码抽离出来,单独定义成一个函数,而这个抽离出来的hook函数就称之为“自定义hook钩子函数”,简称“自定义hook”,以达到代码复用的目的。react规定所有的自定义hook函数命名时必须使用 useXxx 这种形式。
通常自定义hook返回值有3种表现形式:
1、不带返回值的函数
2、带普通返回值的函数
3、带特殊结构返回值的函数(我们把组件需要用到的多项属性设置,合并为一个对象 并 return 出去,供组件使用)
在线演示:https://codesandbox.io/s/zi-ding-yi-hook-psnqox