一、基础的State Hook和Effect Hook
第一段代码
function Myfunc() {
const [count, setCount] = useState(0);//State Hook
useEffect(() => {//Effect Hook
const interval = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <span>{count}</span>;
}
① State Hook
浅显的理解,state hook通过解构赋值的方法把0赋值给count
状态,把类似于class组件中setState
的方法传递给setCount
② Effect Hook
Effect Hook可以让你在函数组件中执行副作用操作。useEffect
可以看作componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
useEffect
中的回调函数中的函数体(不包括return后面的函数),相当于定义了componentDidMount
,componentDidUpdate
两个方法。而return的函数相当于componentWillUnmount
。在第三个副标题里面会详细介绍。
二、用useReducer处理复杂的数据
第二段代码
function countReducer(state, action) {
switch (action.type) {
case "add":
return state + 1;
case "minus":
return state - 1;
default:
return state;
}
}
function Myfunc2() {
const [count, dispatchCount] = useReducer(countReducer, 0);
useEffect(() => {
const interval = setInterval(() => {
dispatchCount({ type: "minus" });
}, 1000);
return () => clearInterval(interval);
}, []);
return <span>{count}</span>;
}
虽然此处的state很简单,但是有可能你的state是一个很复杂的数据结构,那么可以尝试使用useReducer
API
三、深入理解Effect Hook第二个数组参数
第三段代码
function Myfunc3() {
const [name, setName] = useState("zh-d");
const [count, setCount] = useState(0);
useEffect(() => {
console.log("effect invoked");
return () => {
console.log("effect deteched");
};
});
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={setCount((c) => c + 1)}>{count}</button>
</div>
);
}
① 现象1
运行这段代码,在浏览器中,控制台会先输出"effect invoked"。然后,无论你是在input框中输入文字还是button上面点击,控制台都会先输出”effect deteched“,然后再输出”invoked“
② 原因1
首先,"effect invoked"相当于componentDidMount
。然后,无论我们点击还是输入,都会触发state的改变(count或者name改变),这样的话Myfunc3
这个组件就要重新渲染。就相当于componentWillUnmount
+componentDidUpdate
第四段代码
function Myfunc3() {
const [name, setName] = useState("zh-d");
const [count, setCount] = useState(0);
useEffect(() => {
console.log("effect invoked");
return () => {
console.log("effect deteched");
};
},[]);//传入了一个空数组
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={setCount((c) => c + 1)}>{count}</button>
</div>
);
}
这段代码和第三段代码的区别是useEffect
这个Hook传入了一个[]作为参数。
③ 现象2
运行这段代码,在浏览器中,控制台会先输出"effect invoked"。然后,无论你怎么操作state,控制台都不会打印出任何内容。只有你切换到其他页面,控制台会打印”effect deteched“
④ 原因2
首先,"effect invoked"相当于componentDidMount
。后续点击或者输入没有触发componentWillUnmount
或componentDidUpdate
第五段代码
function Myfunc3() {
const [name, setName] = useState("zh-d");
const [count, setCount] = useState(0);
useEffect(() => {
console.log("effect invoked");
return () => {
console.log("effect deteched");
};
},[name]);//传入了一个name
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={setCount((c) => c + 1)}>{count}</button>
</div>
);
}
这段代码和第四段代码的区别是useEffect
这个Hook的数组中传入了一个name作为参数。
⑤ 现象3
运行这段代码,在浏览器中,控制台会先输出"effect invoked"。然后,你在input输入框中输入内容时控制台会依次打印”effect deteched“和”effect invoked“。而你点击button的时候就什么都没有打印。
⑥ 原因3
useEffect只依赖了name,只有name改变才会触发componentWillUnmount
+compontDidUpdate
⑦ 总结
(1)若你什么都不传,默认传递组件中所有state,任何state的更新都会触发useEffect的回调(componentWillUnmount+compontDidUpdate)
(2)若你传入一个空数组,就相当于什么state都没传,没有任何state的更新能够触发回调(componentWillUnmount+compontDidUpdate)
(3)若你传递一个数组且在数组中包含了state数据,那么只有数组中包含的state改变才会触发回调(componentWillUnmount+compontDidUpdate)
四、为什么在useEffect中调用setCount时不能直接使用count这个值
通过刚刚副标题三里面讲的内容可以知道,若你传入了一个空数组作为useEffect的第二个参数,则不会触发useEffect里面count的更新。
如下,setCount传递的count+1的count指向的还是第一次渲染的0
function Myfunc() {
const [count, setCount] = useState(0);//State Hook
useEffect(() => {//Effect Hook
const interval = setInterval(() => {
setCount(count + 1);//这里的count永远为0
}, 1000);
return () => clearInterval(interval);
}, []);
return <span>{count}</span>;//这里的是最新值
}
① 解决方法一
在数组中传入依赖[count]
但这样的话每次重新渲染渲染都会调用useEffect,更新count的值,但浪费性能
② 解决方法二
在setCount中传入箭头函数©=>c+1,c会自动拿到最新的count
② 解决方法三
useRef(下文会讲到)
五、useLayoutEffect
用法和useEffect一样,作用也差不多
永远在useEffect
前一步调用。useLayoutEffect
会在更新dom之前调用,而useEffect
dom更新之后调用。一般都用useEffect
而少用useLayoutEffect
,因为useLayoutEffect
会影响到dom更新。必须等待useLayoutEffect
执行完毕才会更新dom。偶尔需要在dom更新之前调用。
六、Context-Hooks
function Root() {
return (
<div className="root">
<MyContext.Provider value="aaa">
<Myfunc />
</MyContext.Provider>
</div>
);
}
function UseContext() {
const myContext = useContext(MyContext);
return (
<span>
<p>{myContext}</p> //打印出aaa
</span>
);
}
Context-Hooks相当于class组件里面的context api,如果不知道这个,也可以类比于react-redux来理解
七、Ref-Hook
function App() {
const [count, setCount] = useState(0);
const countRef = useRef();
useEffect(() => {
console.log("get");
const interval = setInterval(() => {
console.log(countRef.current.innerHTML);
setCount(+countRef.current.innerHTML + 1);
}, 1000);
return () => {
console.log("bye");
return clearInterval(interval);
};
}, []);
return <div ref={countRef}>{count}</div>;
}
Ref-Hook能拿到Dom节点最新的那个属性
八、使用memo、useMemo和useCallback组合拳对Hooks渲染优化
第一段代码
:不使用memo
function Parent() {
const [name, setName] = useState("aaa");
const [count, setCount] = useState(0);
const config = {
text: `count is ${count}`,
color: count > 3 ? "red" : "blue",
};
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<Child config={config} onButtonClick={() => setCount((count) => count + 1)} />
</div>
);
}
function Child({ onButtonClick, config }) {
console.log("child render");
return (
<button onClick={onButtonClick} style={{ color: config.color }}>
{config.text}
</button>
);
}
点击或者输入时都会打印出"child render",因为child组件不依赖于name和onButtonClick里面的回调函数,我们希望输入时不打印"child render",这是我们要用memo优化的点。
第二段代码
:使用memo和useMemo以及useCallback
import React, { useState, useEffect, memo, useMemo, useCallback } from "react";
export default function Parent() {
const [name, setName] = useState("aaa");
const [count, setCount] = useState(0);
const config = useMemo(
() => ({
text: `count is ${count}`,
color: count > 3 ? "red" : "blue",
}),
[count]
);
const handleButtonClick = useCallback(
() => setCount((count) => count + 1),
[]
);
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<Child config={config} onButtonClick={handleButtonClick} />
</div>
);
}
const Child = memo(({ onButtonClick, config }) => {
console.log("child render");
return (
<button onClick={onButtonClick} style={{ color: config.color }}>
{config.text}
</button>
);
});
只有输入时会打印出"child render",通过useMemo解决了其他对象的变化,通过useCallBack解决了函数的变化影响。
九 闭包问题
当你点击一次handleClick然后再点击两次handleClick2,输出的结果会是10而不是12
import { useState } from "react";
export default function Bt() {
const [count, setCount] = useState(10);
const handleClick = () => {
setTimeout(() => {
alert(count);
}, 2000);
};
const handleClick2 = () => {
setCount(count + 1);
};
return (
<div>
<button onClick={handleClick}>alert button</button>
<button onClick={handleClick2}>change button</button>
<span>{count}</span>
</div>
);
}
如果要显示更新之后的值,请使用useRef
import { useState, useRef } from "react";
export default function Bt() {
const countRef = useRef();
const [count, setCount] = useState(10);
const handleClick = () => {
setTimeout(() => {
alert(countRef.current.innerHTML);
}, 2000);
};
const handleClick2 = () => {
setCount(count + 1);
};
return (
<div>
<button onClick={handleClick}>alert button</button>
<button onClick={handleClick2}>change button</button>
<span ref={countRef}>{count}</span>
</div>
);
}