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 /><inputtype="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 /><inputtype="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;