title: Hooks基础使用
order: 4
常用的usestate、useEffect就不讲解了
文章目录
- Hooks基础使用
- 1.函数组件和类组件的区别
- 2.useContext():共享状态钩子
- 3.useReducer():action 钩子
- 4.useRef():保存引用
- 5.useCallback
- 6.useMemo
- 7.useImperativeHandle
- 8.useLayoutEffect
- 9.自定义hooks
- 二.父触发子事件
- 1.父组件
- 2.子组件
- 三.更多概念
- 1.useMemo和useCallback区别和场景
- 2.hooks的闭包
类组件的性能消耗比较大,因为类组件需要创建类组件的实例,而且不能销毁。
函数式组件性能消耗小,因为函数式组件不需要创建实例,渲染的时候就执行一下,得到返回的react元素后就直接把中间量全部都销毁。
函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。
如想实现Test
和Message
组件通信
<div className="App">
<Test></Test>
<Message></Message>
</div>
第一步、外部建立一个组件
import {
createContext } from 'react'
const NavContext= createContext<any>({
});
export default NavContext;
第二步、在要共享状态组件外包裹
import AppContext from './AppContext'
...
<AppContext.Provider value={
{
username: 'superawesome'
}}>
<div className="App">
<Test></Test>
<Message></Message>
</div>
</AppContext.Provider>
第三步,在组件里使用数据
import React,{
useContext} from 'react'
import AppContext from './AppContext'
export default function Test() {
const {
username } = useContext(AppContext);
return (
<div>
{
username}
</div>
)
}
useReducer
不是redux
的某个替代品。useReducer
仅仅是useState
的一种替代方案:
在某些场景下,如果state
的处理逻辑比较复杂,我们可以通过useReducer
来对其进行拆分;
或者这次修改的state
需要依赖之前的state
时,也可以使用;
单独创建一个reducer/counter.js文件:
export function counterReducer(state, action) {
switch(action.type) {
case "increment":
return {
...state, counter: state.counter + 1}
case "decrement":
return {
...state, counter: state.counter - 1}
default:
return state;
}
}
home.js
import React, {
useReducer } from 'react'
import {
counterReducer } from '../reducer/counter'
export default function Home() {
const [state, dispatch] = useReducer(counterReducer, {
counter: 100});
return (
<div>
<h2>当前计数: {
state.counter}</h2>
<button onClick={
e => dispatch({
type: "increment"})}>+1</button>
<button onClick={
e => dispatch({
type: "decrement"})}>-1</button>
</div>
)
}
创建另外一个profile.js也使用这个reducer函数,是否会进行数据的共享:
import React, {
useReducer } from 'react'
import {
counterReducer } from '../reducer/counter'
export default function Profile() {
const [state, dispatch] = useReducer(counterReducer, {
counter: 0});
return (
<div>
<h2>当前计数: {
state.counter}</h2>
<button onClick={
e => dispatch({
type: "increment"})}>+1</button>
<button onClick={
e => dispatch({
type: "decrement"})}>-1</button>
</div>
)
}
数据是不会共享的,它们只是使用了相同的counterReducer的函数而已。
所以,useReducer只是useState的一种替代品,并不能替代Redux。
使用场景,如保存定时器ID
、卸载时删除定时器、获取dom
用法一:使用ref保存上一次的某一个值
import React, {
useEffect,useRef } from 'react';
...
let intervalHandle = useRef<any>();
let setTimeoutHandle = useRef<any>();
...
setTimeoutHandle.current = setTimeout(() => {
appHistory.push('/login');
clearInterval(intervalHandle.current);
}, 5000);
intervalHandle.current = setInterval(() => {
clearInterval(intervalHandle.current);//注意这个地方也要clear
setSecond(v => {
return v > 0 ? v - 1 : 0
}
)
}, 1000);
...
useEffect(() => {
return () => {
clearTimeout(setTimeoutHandle.current);
clearInterval(intervalHandle.current);
};
}, []);//清除定时器
用法二:引用DOM
import React, {
useRef } from 'react';
export default function RefHookDemo() {
const inputRef = useRef();
const titleRef = useRef();
const handleOperating = () => {
titleRef.current.innerHTML = "我是coderwhy";
inputRef.current.focus();
}
return (
<div>
<input type="text" ref={
inputRef}/>
<h2 ref={
titleRef}>默认内容</h2>
<button onClick={
e => handleOperating()}>操作</button>
</div>
)
}
useCallback实际的目的是为了进行性能的优化。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b]
);
我们来看下面一段很有趣的代码:
increment1在每次函数组件重新渲染时,会返回相同的值;
increment2每次定义的都是不同的值;
问题:是否increment1会比increment2更加节省性能呢?
那么,为什么说useCallback是为了进行性能优化呢?
import React, {
memo, useState, useCallback } from 'react'
export default function CallbackHookDemo() {
const [count, setCount] = useState(0);
const increment1 = useCallback(function increment() {
setCount(count + 1);
}, []);
const increment2 = function() {
setCount(count + 1);
}
return (
<div>
<h2>当前计数: {
count}</h2>
<button onClick={
increment1}>+1</button>
<button onClick={
increment2}>+1</button>
</div>
)
}
我们来对上面的代码进行改进:
import React, {
memo, useState, useCallback } from 'react';
const CounterIncrement = memo((props) => {
console.log("CounterIncrment被渲染:", props.name);
return <button onClick={
props.increment}>+1</button>
})
export default function CallbackHookDemo() {
const [count, setCount] = useState(0);
const increment1 = useCallback(function increment() {
setCount(count + 1);
}, []);
const increment2 = function() {
setCount(count + 1);
}
return (
<div>
<h2>当前计数: {
count}</h2>
{
/*
*/}
<CounterIncrement increment={
increment1} name="increment1"/>
<CounterIncrement increment={
increment2} name="increment2"/>
</div>
)
}
网上使用场景:
使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props
;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
import React, {
useState, useCallback, useEffect } from 'react';
function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count;
}, [count]);
return <div>
<h4>{
count}</h4>
<Child callback={
callback}/>
<div>
<button onClick={
() => setCount(count + 1)}>+</button>
<input value={
val} onChange={
event => setVal(event.target.value)}/>
</div>
</div>;
}
function Child({
callback }) {
const [count, setCount] = useState(() => callback());
useEffect(() => {
setCount(callback());
}, [callback]);
return <div>
{
count}
</div>
}
useMemo实际的目的也是为了进行性能的优化。
如何进行性能的优化呢?
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
我们来看一个案例:
+1
还是 切换
案例都会重新计算一次;import React, {
useState, useMemo } from 'react';
function calcNum(count) {
let total = 0;
for (let i = 0; i < count; i++) {
total += i;
}
console.log("计算一遍");
return total
}
export default function MemoHookDemo() {
const [count, setCount] = useState(10);
const [isLogin, setIsLogin] = useState(true);
const total = calcNum(count);
return (
<div>
<h2>数字和: {
total}</h2>
<button onClick={
e => setCount(count + 1)}>+1</button>
{
isLogin && <h2>Coderwhy</h2>}
<button onClick={
e => setIsLogin(!isLogin)}>切换</button>
</div>
)
}
这个时候,我们可以使用useMemo来进行性能的优化:
import React, {
useState, useMemo } from 'react';
function calcNum(count) {
let total = 0;
for (let i = 0; i < count; i++) {
total += i;
}
console.log("计算一遍");
return total
}
export default function MemoHookDemo() {
const [count, setCount] = useState(10);
const [isLogin, setIsLogin] = useState(true);
const total = useMemo(() => {
return calcNum(count);
}, [count]);
return (
<div>
<h2>数字和: {
total}</h2>
<button onClick={
e => setCount(count + 1)}>+1</button>
{
isLogin && <h2>Coderwhy</h2>}
<button onClick={
e => setIsLogin(!isLogin)}>切换</button>
</div>
)
}
当然,useMemo也可以用于子组件的性能优化:
import React, {
useState, useMemo, memo } from 'react';
function calcNum(count) {
let total = 0;
for (let i = 0; i < count; i++) {
total += i;
}
console.log("计算一遍");
return total
}
const ShowCounter = memo((props) => {
console.log("重新渲染");
return <h1>Counter: {
props.total}</h1>
})
const ShowInfo = memo((props) => {
console.log("ShowInfo重新渲染");
return <h1>信息: {
props.info.name}</h1>
})
export default function MemoHookDemo() {
const [count, setCount] = useState(10);
const [isLogin, setIsLogin] = useState(true);
const total = useMemo(() => {
return calcNum(count);
}, [count]);
const info = useMemo(() => {
return {
name: "why"}
}, [])
return (
<div>
<h2>数字和: {
total}</h2>
<ShowCounter total={
total} />
<ShowInfo info={
info}/>
<button onClick={
e => setCount(count + 1)}>+1</button>
{
isLogin && <h2>Coderwhy</h2>}
<button onClick={
e => setIsLogin(!isLogin)}>切换</button>
</div>
)
}
useImperativeHandle并不是特别好理解,我们一点点来学习。
我们先来回顾一下ref和forwardRef结合使用:
import React, {
useRef, forwardRef } from 'react';
const HYInput = forwardRef(function (props, ref) {
return <input type="text" ref={
ref}/>
})
export default function ForwardDemo() {
const inputRef = useRef();
return (
<div>
<HYInput ref={
inputRef}/>
<button onClick={
e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
上面的做法本身没有什么问题,但是我们是将子组件的DOM直接暴露给了父组件:
通过useImperativeHandle可以只暴露固定的操作:
传入的ref
和useImperativeHandle第二个参数返回的对象
绑定到了一起;inputRef.current
时,实际上使用的是返回的对象
;focus函数
,甚至可以调用 printHello函数
;import React, {
useRef, forwardRef, useImperativeHandle } from 'react';
const HYInput = forwardRef(function (props, ref) {
// 创建组件内部的ref
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
printHello: () => {
console.log("Hello World")
}
}))
// 这里绑定的是组件内部的inputRef
return <input type="text" ref={
inputRef}/>
})
export default function ImperativeHandleHookForwardDemo() {
const inputRef = useRef();
return (
<div>
<HYInput ref={
inputRef}/>
<button onClick={
e => inputRef.current.focus()}>聚焦</button>
<button onClick={
e => inputRef.current.printHello()}>Hello World</button>
</div>
)
}
同vue中nextTick
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:
如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect。
我们来看下面的一段代码:
import React, {
useEffect, useState, useLayoutEffect } from 'react';
export default function EffectHookDemo() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count === 0) {
setCount(Math.random()*200)
}
}, [count]);
return (
<div>
<h2>当前数字: {
count}</h2>
<button onClick={
e => setCount(0)}>随机数</button>
</div>
)
}
事实上,我们上面的操作的目的是在count被设置为0时,随机另外一个数字:
import React, {
useEffect, useState, useLayoutEffect } from 'react';
export default function EffectHookDemo() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
if (count === 0) {
setCount(Math.random()*200)
}
}, [count]);
return (
<div>
<h2>当前数字: {
count}</h2>
<button onClick={
e => setCount(0)}>随机数</button>
</div>
)
}
Hooks
代码还可以封装起来,变成一个自定义的 Hook
,便于共享。
const usePerson = (personId) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({
});
useEffect(() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${
personId}/`)
.then(response => response.json())
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId]);
return [loading, person];
};
usePerson()
就是一个自定义的 Hook
Person
组件就改用这个新的钩子,引入封装的逻辑
const Person = ({
personId }) => {
const [loading, person] = usePerson(personId);
if (loading === true) {
return <p>Loading ...</p>;
}
return (
<div>
<p>You're viewing: {
person.name}</p>
<p>Height: {
person.height}</p>
<p>Mass: {
person.mass}</p>
</div>
);
};
const uploadFile = () => {
childRef.current.onSubmit();
};
...
<UploadC ref={
childRef}/>
//通过属性ref,想要获取通过childRef这个ref存放子组件实例
//子组件接收这个ref,然后给它身上绑定一些想要属性(也就是useImperativeHandle的第二个参数返回值)
//通过useImperativeHandle可以只暴露固定的操作
import {
memo, forwardRef, useImperativeHandle } from 'react'
export default memo(forwardRef(function index(props, ref) {
useImperativeHandle(ref, () => {
return {
onSubmit,
};
});
const onSubmit = () => {
console.log('object')
};
return (
<div>
</div>
)
}))
共同作用:
仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。
两者区别:
useMemo
计算结果是 return
回来的值
, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态useCallback
计算结果是 函数
, 主要用于 缓存函数
,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。注意: 不要滥用会造成性能浪费,react中减少render就能提高性能,所以这个仅仅只针对缓存能减少重复渲染时使用和缓存计算结果。
总结:比较消耗性能的再用这两
useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。