1、useReducer
适用情况为对一个状态多种复杂操作,通俗的讲就是比如对count这个变量加减乘除的各种情况
改造前
import { useState } from "react";
function App() {
//计数器
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
const handleDecrement = () => {
setCount(count - 1);
};
return (
<div style={{ padding: 10 }}>
<button onClick={handleIncrement}>+</button>
<span>{count}</span>
<button onClick={handleDecrement}>-</button>
</div>
);
}
export default App;
改造后
import { useReducer } from "react";
function countReducer(state, action) {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
throw new Error();
}
}
function App() {
//计数器
const [state, dispatch] = useReducer(countReducer, 0);
const handleIncrement = () => {
dispatch({ type: "increment" });
};
const handleDecrement = () => {
dispatch({ type: "decrement" });
};
return (
<div style={{ padding: 10 }}>
<button onClick={handleIncrement}>+</button>
<span>{state}</span>
<button onClick={handleDecrement}>-</button>
</div>
);
}
export default App;
2、useRef
ref记住状态变更之前的值
import { useRef, useState } from "react";
function App() {
const [count, setCount] = useState(0);
const prevCount = useRef();
function handleClick() {
prevCount.current = count;
setCount(count + 1);
}
return (
<div>
<p>当前的count:{count}</p>
<p>上一次的count:{prevCount.current}</p>
<button onClick={handleClick}>增大count</button>
</div>
);
}
export default App;
ref获取标签
import { useRef } from "react";
function App() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>按钮</button>
</div>
);
}
export default App;
ref获取其他子组件
需要以下步骤
1、子组件不能定义为一个普通函数,必须使用函数表达式的方式
2、这个函数表达式需要forwardRef包裹处理,这样才可以被父组件使用
3、父组件给子组件传入ref
4、子组件中的方法还需要useImperativeHandle包裹处理,才能被父组件调用
import { useRef, forwardRef, useImperativeHandle } from "react";
const Child = forwardRef(function (props, ref) {
useImperativeHandle(ref, () => ({
//暴露给父组件的方法
myFn: () => {
console.log("子组件myFn的方法");
},
}));
return <div>子组件</div>;
});
function App() {
const childRef = useRef();
function handleClick() {
childRef.current.myFn();
}
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>点击</button>
</div>
);
}
export default App;
3、useEffect
React要求所有的函数式组件都是纯函数,意味同样的输入就有同样的输出
如果想要设置副作用的话,那我们可以使用useState事件这种
如果想要在组件加载或者组件更新时(非用户操作触发),能够执行一些副作用的话,需要用到useEffect
useEffect在React严格模式下默认会执行两次
如果想要在组件渲染的时候执行一次,以后就不再变更的话,可以给useEffect传递一个空的依赖数组,如果数组中填写了一些状态的话,这些状态的变化会导致副作用的重新执行
import { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
const handleDecrement = () => {
setCount(count - 1);
};
useEffect(() => {
console.log("useEffect");
}, [count]);
return (
<div style={{ padding: 10 }}>
<button onClick={handleIncrement}>+</button>
<span>{count}</span>
<button onClick={handleDecrement}>-</button>
</div>
);
}
export default App;
4、useMemo
一种用来缓存数据的钩子
以下代码,父组件改变count的值时,父组件会重新渲染,子组件也会随着父组件渲染,但是子组件并没有状态变更,不需要重新渲染,这没有意义,所以需要使用useMemo包裹处理需要缓存的那个数据,来监听那个数据有没有变化,使得子组件没有状态变更就不重新渲染,提高性能
import { useState } from "react";
function DoSomeMath({ value }) {
console.log("DoSomeMath执行了");
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += value * 2;
}
return (
<div>
<p>输入内容:{value}</p>
<p>经过复杂计算的数据:{result}</p>
</div>
);
}
function App() {
const [inputValue, setInputValue] = useState(5);
const [count, setCount] = useState(0);
return (
<div>
<p>count的值为:{count}</p>
<button onClick={() => setCount(count + 1)}>点击更新</button>
<br />
<br />
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(parseInt(e.target.value))}
/>
<DoSomeMath value={inputValue} />
</div>
);
}
export default App;
优化后的代码
import { useState } from "react";
import { useMemo } from "react";
function DoSomeMath({ value }) {
const result = useMemo(() => {
console.log("DoSomeMath执行了");
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += value * 2;
}
return result;
}, [value]);
return (
<div>
<p>输入内容:{value}</p>
<p>经过复杂计算的数据:{result}</p>
</div>
);
}
function App() {
const [inputValue, setInputValue] = useState(5);
const [count, setCount] = useState(0);
return (
<div>
<p>count的值为:{count}</p>
<button onClick={() => setCount(count + 1)}>点击更新</button>
<br />
<br />
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(parseInt(e.target.value))}
/>
<DoSomeMath value={inputValue} />
</div>
);
}
export default App;
5、useCallback
一种用来缓存函数的钩子
以下代码当父组件更新count状态时,父组件会重新渲染,所以handleClick会重新创建变成一个新的函数,那么传给子组件的handleClick也会变成新的props,但实际上父组件传给子组件的props没有变化,但是子组件会重新渲染
要解决这个问题,有两个步骤要做:
a、将要缓存的函数使用memo记忆体包裹,并且写成函数表达式的形式,变成记忆组件。memo的作用是当传入的prop是同一个没有变化时,就不会让子组件重新渲染
b、使用useCallback包裹父组件的那个传入子组件的函数,使得父组件重新渲染时,因为缓存了该函数,不会重新创建该函数,使得组件传入的props没变,最终使得子组件不会被重新渲染
import { useState } from "react";
function Button({ onClick }) {
console.log("Button渲染了");
return <button onClick={onClick}>子组件</button>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log("点击按钮");
};
const handleUpdate = () => {
setCount(count + 1);
};
return (
<div>
<p>Count:{count}</p>
<button onClick={handleUpdate}>点击</button>
<br />
<Button onClick={handleClick}></Button>
</div>
);
}
export default App;
优化后的代码
import { memo, useCallback, useState } from "react";
const Button = memo(function ({ onClick }) {
console.log("Button渲染了");
return <button onClick={onClick}>子组件</button>;
});
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("点击按钮");
}, []); //依赖项为空数组,表示该函数只在组件挂载时创建一次
const handleUpdate = () => {
setCount(count + 1);
};
return (
<div>
<p>Count:{count}</p>
<button onClick={handleUpdate}>点击</button>
<br />
<Button onClick={handleClick}></Button>
</div>
);
}
export default App;