React-Hook相关知识
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
尽可能使用标准的 useEffect 以避免阻塞视觉更新。
示例,如果使用 useEffect,会明显有个闪屏问题
这个是用在处理 DOM 的时候,当你的 useEffect 里面的操作需要处理 DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect 里面的 callback 函数会在 DOM 更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.
import React, { useEffect, useLayoutEffect, useState } from "react";
function BoxDemo() {
const [currStyle, setCurrStyle] = useState({
width: "100px",
height: "100px",
backgroundColor: "red",
position: "absolute",
top: "0px",
left: "0px",
});
// useLayoutEffect(() => {
// setCurrStyle({
// ...currStyle,
// position: "absolute",
// top: "0px",
// left: "100px",
// });
// }, []);
useEffect(() => {
setCurrStyle({
...currStyle,
position: "absolute",
top: "0px",
left: "100px",
});
}, []);
const onButtonClick = () => {};
return (
<>
<div style={currStyle}>我是box</div>
</>
);
}
export default BoxDemo;
参考地址:https://www.jianshu.com/p/412c874c5add
==============
useMemo 的使用
转载于:https://segmentfault.com/a/1190000018697490
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
下面我们通过一个实例,来理解下 useMemo 的用法。
父组件
function App() {
const [name, setName] = useState("名称");
const [content, setContent] = useState("内容");
return (
<>
<button onClick={() => setName(new Date().getTime())}>name</button>
<button onClick={() => setContent(new Date().getTime())}>content</button>
<Button name={name}>{content}</Button>
</>
);
}
子组件
function Button({ name, children }) {
function changeName(name) {
console.log("11");
return name + "改变 name 的方法";
}
const otherName = changeName(name);
return (
<>
<div>{otherName}</div>
<div>{children}</div>
</>
);
}
熟悉 react 的同学可以很显然的看出,当我们点击父组件的按钮的时候,子组件的 name 和 children 都会发生变化。
注意我们打印 console.log 的方法。
不管我们是改变 name 或者 content 的值,我们发现 changeName 的方法都会被调用。
是不是意味着 我们本来只想修改 content 的值,但是由于 name 并没有发生变化,所以无需执行对应的 changeName 方法。但是发现他却执行了。 这是不是就意味着性能的损耗,做了无用功。
下面我们使用 useMemo 进行优化
优化之后的子组件
function Button({ name, children }) {
function changeName(name) {
console.log("11");
return name + "改变 name 的方法";
}
const otherName = useMemo(() => changeName(name), [name]);
return (
<>
<div>{otherName}</div>
<div>{children}</div>
</>
);
}
export default Button;
这个时候我们点击 改变 content 值的按钮,发现 changeName 并没有被调用。
但是点击改变 name 值按钮的时候,changeName 被调用了。
所以我们可以使用 useMemo 方法 避免无用方法的调用,当然一般我们 changName 里面可能会使用 useState 来改变 state 的值,那是不是就避免了组件的二次渲染。
达到了优化性能的目的
========================
使用 ref 定义类似实例变量
有些情况下,我们需要保证函数组件每次 render 之后,某些变量不会被重复申明,比如说 Dom 节点,定时器的 id 等等,在类组件中,我们完全可以通过给类添加一个自定义属性来保留,比如说 this.xxx, 但是函数组件没有 this,自然无法通过这种方法使用,有的朋友说,我可以用 useState 来保留变量的值,但是 useState 会触发组件 render,在这里完全是不需要的,我们就需要使用 useRef 来实现了,
其实这个特点和类组件中 setState 类似,可以接收一个新的 state 值更新,也可以函数式更新。如果新的 state 需要通过使用先前的 state 计算得出,那么就要使用函数式更新。因为setState更新可能是异步,当你在事件绑定中操作 state 的时候,setState更新就是异步的。一般操作state,因为涉及到 state 的状态合并,react 认为当你在事件绑定中操作 state 是非常频繁的,所以为了节约性能 react 会把多次 setState 进行合并为一次,最后在一次性的更新 state,而定时器里面操作 state 是不会把多次合并为一次更新的。
import React, { useState, useRef } from "react";
function BoxDemo() {
const [currTimer, SetCurrTimer] = useState(0);
const t = useRef(null);
const handleClick = () => {
t.current = setInterval(() => {
SetCurrTimer((nextTimer) => nextTimer + 1);//此外变化state用的函数
}, 1000);
};
const handleClear = () => clearTimeout(t.current);//注意这里赋值给了变量 useRef,
return (
<>
<p>当前timer:{currTimer}</p>
<button onClick={handleClick}>start</button>
<button onClick={handleClear}>clear</button>
</>
);
}
export default BoxDemo;
当我们们使用普通的按钮去暂停定时器时发现定时器无法清除,因为 App 组件每次 render,都会重新申明一次 timer2, 定时器的 id 在第二次 render 时,就丢失了,所以无法清除定时器,针对这种情况,就需要使用到 useRef,来为我们保留定时器 id,类似于 this.xxx,这就是 useRef 的另外一种用法。
import React, { useEffect, useCallback, useState, useRef } from "react";
function BoxDemo() {
const [count, setCount] = useState(0);
const timer = useRef(null);
let timer2;
useEffect(() => {
let id = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
timer.current = id;
timer2 = id;
return () => {
clearInterval(timer.current);
};
});
//useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
const onClickRef = useCallback(() => {
clearInterval(timer.current);
}, []);
const onClick = useCallback(() => {
clearInterval(timer2);
}, []);
return (
<div>
点击次数: {count}
<button onClick={onClick}>普通</button>
<button onClick={onClickRef}>useRef</button>
</div>
);
}
export default BoxDemo;
1、react 中使用 htmlFor 代替 label 中的 for
<>
<label htmlFor="box-id">姓名</label>
<input id="box-id" />
</>
2、样式不会自动补齐前缀,如需支持旧版浏览器,请手动补充对应的样式属性:
import React, { useState } from "react";
const BoxDemo = () => {
const [currStatus, setCurrStatus] = useState(false);
const divStyle = currStatus
? {
width: "50px",
height: "50px",
backgroundColor: "green",
WebkitTransition: "all 2s", // note the capital 'W' here
msTransition: "all 2s", // 'ms' is the only lowercase vendor prefix
}
: {
width: "100px",
height: "200px",
backgroundColor: "red",
WebkitTransition: "all 2s", // note the capital 'W' here
msTransition: "all 2s", // 'ms' is the only lowercase vendor prefix
};
const changeStatus = () => {
setCurrStatus(!currStatus);
};
return (
<>
<div style={divStyle}>This should work cross-browser</div>;
<button onClick={() => changeStatus()}>点击me</button>
</>
);
};
export default BoxDemo;
剪贴板事件,在复制文本的时候触发事件。
import React, { useState } from "react";
const BoxDemo = () => {
const [currStatus, setCurrStatus] = useState("我是大街上看到撒侃大山");
const copyEvnt = () => {
console.log(currStatus);
};
const startEvnt = ()=>{
console.log('切换输入法');
}
return (
<>
<input type="text" defaultValue={currStatus} onCopy={() => copyEvnt()} onCompositionStart={()=>startEvnt()/>
</>
);
};
export default BoxDemo;
react Hook 使用注意事项
1、useState 和相关的 useEffect 放一起,Hook 允许我们按照代码的用途分离他们
function FriendStatusWithCounter(props) {
//代码片段一
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
//代码片段二
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
如果我们想要有条件地执行一个 effect,可以将判断放到 Hook 的内部:
useEffect(function persistForm() {
// 👍 将条件判断放置在 effect 中
if (name !== "") {
localStorage.setItem("formData", name);
}
});
useState 中第二个参数如果传入的是函数,则可以获取上一个 state
const [count, setCount] = useState(initialCount);
setCount((currCount) => {
console.log(currCount); //如果传入的是函数,则可以获取上一个state
return currCount + 1;
});
再如
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
</>
);
}
如下所示
import React, { useState } from "react";
function BoxDemo({ initialCount }) {
const [count, setCount] = useState(initialCount);
function reduceEvnt() {
let currCount = count - 1;
setCount(currCount);
}
//多次执行也只是按照一次执行
// function addEvnt() {
// setCount(count + 1);
// setCount(count + 1);
// setCount(count + 1);
// }
//可以多次执行
function addEvnt() {
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => count + 1);
}
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={reduceEvnt}>-</button>
<button onClick={addEvnt}>+</button>
</>
);
}
export default BoxDemo;
惰性初始 state
initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
使用扩展运算符定义多个变量
import React, { useState, useEffect, useRef } from "react";
function BoxDemo() {
const [state, setState] = useState({
left: 0,
top: 0,
width: 100,
height: 100,
position: "absolute",
backgroundColor: "red",
});
useEffect(() => {
function handleWindowMouseMove(e) {
// 展开 「...state」 以确保我们没有 「丢失」 width 和 height
setState((state) => ({ ...state, left: e.pageX, top: e.pageY }));
}
// 注意:这是个简化版的实现
window.addEventListener("mousemove", handleWindowMouseMove);
return () => window.removeEventListener("mousemove", handleWindowMouseMove);
}, []);
return (
<>
<p style={state}></p>
</>
);
}
export default BoxDemo;
只在更新时运行 effect
这是个比较罕见的使用场景。如果你需要的话,你可以 使用一个可变的 ref 手动存储一个布尔值来表示是首次渲染还是后续渲染,然后在你的 effect 中检查这个标识。(如果你发现自己经常在这么做,你可以为之创建一个自定义 Hook。)
import React, { useState, useEffect, useRef } from "react";
function BoxDemo() {
const [currNum, SetCurrNum] = useState(0);
const flag = useRef(false);
useEffect(() => {
if (flag.current) {
console.log(currNum);
} else {
flag.current = true;
}
});
return (
<>
<p>{currNum}</p>
<button onClick={() => SetCurrNum(currNum + 1)}>点击me</button>
</>
);
}
export default BoxDemo;
自定义 hook 只在更新时运行 effect
import React, { useState, useEffect, useRef } from "react";
function useFirstAction() {
const flag = useRef(false);
useEffect(() => {
if (!flag.current) {
flag.current = true;
}
}, []);
return flag.current;
}
function BoxDemo() {
const [currNum, SetCurrNum] = useState(0);
const isFirst = useFirstAction();
useEffect(() => {
if (isFirst) console.log(currNum);
});
return (
<>
<p>{currNum}</p>
<button onClick={() => SetCurrNum(currNum + 1)}>点击me</button>
</>
);
}
export default BoxDemo;
利用 useRef 获取上一轮的 props 或 state
import React, { useState, useEffect, useRef } from "react";
function BoxDemo() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return (
<>
<h1>
Now: {count}, before: {prevCount}
</h1>
<button onClick={() => setCount(count + 1)}>点击me</button>
</>
);
}
export default BoxDemo;
利用页面先渲染先拿到 count 值为 0,然后此时 prevCount 刚刚创建还是个 undefined,当页面渲染完成进入副作用 useEffect 中,进行赋值操作 prevCountRef.current = count。这时候 count 的值就保存到 current 中,但是页面不会重新渲染。
点击按钮增加时页面重新渲染,count 为 2,然后 prevCount 还存储这上次的 1 值,只有等进入 useEffect 中重新赋值为才能改变。
改造成自定义 hook
import React, { useState, useEffect, useRef } from "react";
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function BoxDemo() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<>
<h1>
Now: {count}, before: {prevCount}
</h1>
<button onClick={() => setCount(count + 1)}>点击me</button>
</>
);
}
export default BoxDemo;
陈旧的 props 和 state
import React, { useState, useEffect, useRef } from "react";
function BoxDemo() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert("You clicked on: " + count);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
}
export default BoxDemo;
如果你先点击「Show alert」然后增加计数器的计数,那这个 alert 会显示在你点击『Show alert』按钮时的 count 变量。这避免了那些因为假设 props 和 state 没有改变的代码引起问题。
如果你刻意地想要从某些异步回调中读取 最新的 state,你可以用 一个 ref 来保存它,修改它,并从中读取。
import React, { useState, useEffect, useRef } from "react";
function BoxDemo() {
const [count, setCount] = useState(0);
const ref = useRef();
useEffect(() => {
ref.current = count;
});
function handleAlertClick() {
setTimeout(() => {
alert("You clicked on: " + ref.current);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
}
export default BoxDemo;
将使用了依赖项的函数放入 useEffect 中
只有 当函数(以及它所调用的函数)不引用 props、state 以及由它们衍生而来的值时,你才能放心地把它们从依赖列表中省略。
错误用法
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
async function fetchProduct() {
const response = await fetch("http://myapi/product/" + productId); // 使用了 productId prop
const json = await response.json();
setProduct(json);
}
useEffect(() => {
fetchProduct();
}, []); // 🔴 这样是无效的,因为 `fetchProduct` 使用了 `productId`
// ...
}
推荐的修复方案是把那个函数移动到你的 effect 内部。这样就能很容易的看出来你的 effect 使用了哪些 props 和 state,并确保它们都被声明了:
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
useEffect(() => {
// 把这个函数移动到 effect 内部后,我们可以清楚地看到它用到的值。
async function fetchProduct() {
const response = await fetch("http://myapi/product/" + productId);
const json = await response.json();
setProduct(json);
}
fetchProduct();
}, [productId]); // ✅ 有效,因为我们的 effect 只用到了 productId
// ...
}
更新 props 时,对应更新 useEffect
1、原代码,这样更新外部 counter,并不会执行 useEffect 内的代码
import React, { useState, useEffect } from "react";
function Example(props) {
useEffect(() => {
function tick() {
// 在任何时候读取最新的 props
console.log(props.counter);
}
const id = setInterval(tick, 1000);
return () => {
console.log("s");
clearInterval(id);
};
}, []); // 这个 effect 从不会重新执行
return <div>子组件</div>;
}
function BoxDemo() {
const [counter, setCounter] = useState(0);
return (
<>
<div>counter:{counter}</div>
<Example counter={counter} />
<button onClick={() => setCounter(counter + 1)}>点击么</button>
</>
);
}
export default BoxDemo;
2、增加监听数据,但是这样违背了我们的本意,原本是只绑定一次自动执行,现在每次改变 props.counter 相当于重置了 setInterval
import React, { useState, useEffect } from "react";
function Example(props) {
useEffect(() => {
function tick() {
// 在任何时候读取最新的 props
console.log(props.counter);
}
const id = setInterval(tick, 1000);
return () => {
console.log("s");
clearInterval(id);
};
}, [props.counter]); // 这个 effect 从不会重新执行
return <div>子组件</div>;
}
function BoxDemo() {
const [counter, setCounter] = useState(0);
return (
<>
<div>counter:{counter}</div>
<Example counter={counter} />
<button onClick={() => setCounter(counter + 1)}>点击么</button>
</>
);
}
export default BoxDemo;
3、使用 ref 保存变量,实现 useEffect 只执行一次,然后自动监听 props 发生变化
import React, { useState, useEffect, useRef } from "react";
function Example(props) {
const ref = useRef(props);
useEffect(() => {
//每次都更新ref为最新的props
ref.current = props;
});
useEffect(() => {
function tick() {
// 在任何时候读取最新的 props
console.log(ref.current.counter);
}
const id = setInterval(tick, 1000);
return () => {
console.log("s");
clearInterval(id);
};
}, []); // 这个 effect 从不会重新执行
return <div>子组件</div>;
}
function BoxDemo() {
const [counter, setCounter] = useState(0);
return (
<>
<div>counter:{counter}</div>
<Example counter={counter} />
<button onClick={() => setCounter(counter + 1)}>点击么</button>
</>
);
}
export default BoxDemo;
使用 useMemo 模拟 vue 中的 computed
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
//useMemo,useCallback
import React, { memo, useMemo, useCallback, useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(0);
let double = useMemo(() => {
return count * 2;
}, [count]); //double依赖于count,当count改变时,double自动改变,详情可见我的useMemo文章
return (
<div>
<button
onClick={() => {
setCount((count) => count + 1);
}}
>
count+1
</button>
</div>
);
});
export default App;