useEffect
副作用
对环境的改变即为副作用
用途
作为componentDidMount使用,[]作为第二个参数
作为componentDidUpdate使用,可指定依赖
作为componentWillUnmount使用,通过return
以上三种可同时存在
如果同时存在多个useEffect,会按照出现顺序执行
import React, {useState, useEffect} from "react";
import ReactDOM from "react-dom";
function App(){
const [n, setN] = useState(0);
const onClick = () =>{
setN( i => i + 1);
};
useEffect(() => {
console.log("第一次渲染之后执行这一句话");
},[])
//[]里面的变量变化时执行,[]表示除了第一次渲染,之后不会执行
useEffect(() => {
if(n !== 0){
console.log("n变化了");
}
},[n])
//在n变化时执行
useEffect(() => {
console.log("任何一个state变化时都执行");
})
//不写参数,就在任何一个state变化时都执行
useEffect(() => {
const id = setInterval(() => {
console.log('hi')
}, 1000)
//return是告诉react当组件消失时执行这里的代码,对环境有了任何变动,走的时候清理掉垃圾
return () => {
window.clearInterval(id)
}
},[])
return (
<div>
n: {n}
<button onClick={onClick}>+1</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
按顺序执行
useEffect(() => { console.log("1"); })
useEffect(() => { console.log("2"); })
打印出1,2
useLayoutEffect
布局副作用
useEffect //在浏览器渲染完成后执行
useLayoutEffect //在浏览器渲染前执行
import React, { useState, useEffect, useLayoutEffect } from "react";
import ReactDOM from "react-dom";
const BlinkyRender = () => {
const [value, setValue] = useState(0);
useEffect(() => {
document.querySelector('#x').innerText = `value: 1000`
}, [value]);
//当value更新之后(包含第一次),把div的innerText变成1000,先执行渲染,后执行函数
useLayoutEffect(() => {
document.querySelector('#x').innerText = `value: 1000`
}, [value]);
//先执行代码,先变成1000,再去渲染,省略了初次渲染
return (
<div id="x" onClick={() => setValue(0)}>value: {value}</div>
);
};
ReactDOM.render( <BlinkyRender />, document.querySelector("#root");
useLayoutEffect 总是比 useEffect 先执行
useEffect(() => { console.log("1"); })
useLayoutEffect(() => { console.log("2"); })
打印出2,1
useMemo
用来缓存一些,希望在2次新旧组件迭代的时候,用上一次的值
第一个参数是 ()=>value
第二个人参数是依赖[m,n]
只有当依赖变化时,才会计算出新的value; 如果依赖不变,那么就重用之前的value
如果你的value是个函数,就要写成一个返回函数的函数
useMemo( ()=> (x) => console.log(x))
React.memo:React默认有多余的render,添加监听函数之后,会从新执行监听函数,生产新的函数
新旧函数虽然功能一样,但地址不一样,所以要从新渲染
使用useMemo
import React, { useMemo } from "react";
import ReactDOM from "react-dom";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
const onClick2 = () => {
setM(m + 1);
};
//使用useMemo,接受一个函数,这个函数的返回值就是你要缓存的东西
const onClickChild = useMemo(() => {
const fn = div => {
console.log("on click child, m: " + m);
console.log(div);
};
return fn;
}, [m]);
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
<button onClick={onClick2}>update m {m}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
</div>
);
}
function Child(props) {
return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
uesCallback
使用uesCallback代替useMemo
useMemo( ()=> (x) => console.log(x), [m])
等价于
uesCallback(x => console.log(x), [m])
useRef
useRef,可以用来引用DOM对象,也可以用来引用普通对象
如果你需要一个值,在组件不断render时保持不变,使用useRef
初始化:const.count = useRef(0)
读取: count.current
使用current,为了保证两次useRef是同一个值(只有引用可以做到)
useRef不能变化时自动render,需要手动render
import React, { useRef, useEffect } from "react";
import ReactDOM from "react-dom";
function App(){
const count = useRef(0);
const [n, setN] = React.useState(0);
const [_n, _setN] = React.useState(null);
const onClick = () => {
setN(n + 1);
}
//手动更新count
const onClick2 = () => {
_ setN(n + 1);
}
//每次渲染完之后,就把count+=1,每次渲染完count都是不变的,是同一个count
//current才是真正的值,count是包裹器
useEffect(() =>{
count.current += 1;
});
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
<button onClick={onClick2}>update count: {count.current}</button>//更新count按钮
</div>
</div>
)
}
const rootElement = document.getElementById("root");
forwardRef
函数组件props无法传递ref属性,需要使用forwardRef把函数组件包起来
import React, {useRef} from "react";
import ReactDOM from "react-deo";
function App(){
//buttonRef是随着渲染不会变的量
const buttonRef = useRef(null);
return(
<div className="App">
//使用buttonRef引用到Button3对相应的DOM对象
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}
const Button3 = React.forwarRef((props, ref) => {
return <button className="red" ref={ref} {...props} />;
})
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useImperativeHandle
自定义ref,如果一个函数组件暴露一个ref给外面,可以使用useImperativeHandle自定义ref
import React, {useRef, useState, useEffect, useImperativeHandle,createRef} from "react";
import ReactDOM from "react-dom";
function App() {
const buttonRef = useRef(null);
useEffect(() => {
console.log(buttonRef.current);
});
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
<button className="close" onClick={() => {console.log(buttonRef);
buttonRef.current.x();}}>x</button>
</div>
);
}
const Button2 = React.forwardRef((props, ref) => {
const realButton = createRef(null);
const setRef = useImperativeHandle;
//自定义ref
setRef(ref, () => {
return {
x: () => {
realButton.current.remove();
},
realButton: realButton
};
});
return <button ref={realButton} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Stale Closure(过时闭包)
function createIncrementFixed(i) {
let value = 0;
function increment() {
value += i;
console.log(value);
return function logValue() {
const message = `Current value is ${value}`;
console.log(message);
};
}
return increment;
}
const inc = createIncrementFixed(1);
const log = inc(); // logs 1
inc(); // logs 2
inc(); // logs 3
log(); // logs "Current value is 3"
log()是一个过时的,每次value += i;
都会产生新的message,但log却对应着第一次产生的message,log没有更新
解决办法
function WatchCount() {
const [count, setCount] = useState(0);
useEffect(function() {
const id = setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
return function() {
//清除旧的id
clearInterval(id);
}
}, [count]);
//[count]绑定依赖,只要count更新,就会得到一个新的log
return (
<div>
{count}
<button onClick={() => setCount(count + 1) }>
Increase
</button>
</div>
);
}
详细资料点击:React内置Hook API