useCallback
const memoizedCallback = useCallback(() => { # 回调函数仅在某个依赖项改变时才会更新doSomething(a, b);},[a, b],);# useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
返回一个 memoized 回调函数。
由于javascript函数的特殊性,当函数签名被作为deps传入useEffect时,还是会引起re-render(即使函数体没有改变),这种现象在类组件里边也存在:
// 当Parent组件re-render时,Child组件也会re-renderclass Parent extends Component {render() {const someFn = () => {}; // re-render时,someFn函数会重新实例化return (<><Child someFn={someFn} /><Other /></>);}}class Child extends Component {componentShouldUpdate(prevProps, nextProps) {return prevProps.someFn !== nextProps.someFn; // 函数比较将永远返回false}}
Function Component(查看demo):
function App() {const [count, setCount] = useState(0);const [list, setList] = useState([]);const fetchData = async () => {setTimeout(() => { # 2.依赖的这个函数会对 state进行更改,一旦更改会触发组件重绘setList(initList); # 而组件一旦更新 又会进入useEffect 所以就会无限循环}, 3000);};useEffect(() => { # 1.副作用的依赖是 函数fetchData();}, [fetchData]); # 3.解决方法(中断无限循环): 如何让组件re-render的时候 fetchData不是新的return (<><div>click {count} times</div><button onClick={() => setCount(count + 1)}>Add count</button><List list={list} /></>);}
解决方法:
1、将fetchData函数移到组件外部(缺点是无法读取组件的状态了)
2、条件允许的话,把函数体移到useEffect内部 (直接干掉这个函数)
3、通用解决方法:
使用useCallback API包裹函数,useCallback的本质是对函数进行依赖分析,依赖变更时才重新执行
useMemo
useMemo用于缓存一些耗时的计算结果,只有当依赖参数改变时才重新执行计算:
const fibValue = useMemo(() => fibonacci(start), [start]); // 缓存耗时操作
function App(props) {const list = props.list;# list 更改的时候 MemoList才会重新计算出 li# 简单理解:useCallback(fn, deps) === useMemo(() => fn, deps)const MemoList = useMemo(() => <List list={list} />, [list]);return (<>{MemoList}<Other /></>);}# useMemo也可以用React.memo,但相比将整个组件React.memo,用useMemo去渲染memo后的dom,更细粒度// 只有列表项改变时组件才会re-renderconst MemoList = React.memo(({ list }) => {return (<ul>{list.map(item => (<li key={item.id}>{item.content}</li>))}</ul>);});# 相比React.memo,# useMemo在组件内部调用,可以访问组件的props和state,所以它拥有更细粒度的依赖控制。
useRef
useRef 返回一个ref对象的可变引用,但useRef的用途比ref更广泛,它可以存储任意javascript值而不仅仅是DOM引用。
其 .current 属性被初始化为传入的参数(initialValue)
修改useRef值的唯一方法是修改其current的值,且值的变更不会引起re-render
每一次组件render时useRef都返回固定不变的值,不具有Capture Values特性
useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”
// const refContainer = useRef(initialValue);function TextInputWithFocusButton() {const inputEl = useRef(null);const onButtonClick = () => {// `current` 指向已挂载到 DOM 上的文本输入元素inputEl.current.focus();};return (<>// 将 ref 对象以 <div ref={myRef} /> 形式传入组件,// 则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。<input ref={inputEl} type="text" /><button onClick={onButtonClick}>Focus the input</button></>);}
useRef() 和自建一个 {current: …} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。
概念
Capture Values特性
非useRef相关的Hooks API,(useState、useEffect、event handle)
(state是Immutable的,ref是mutable的)
本质上都形成了闭包,闭包有自己独立的状态,这就是Capture Values的本质。
useSate:
function Counter1() {const [count, setCount] = useState(0);const log = () => { # 点击的时候会触发这个函数setCount(count + 1); # 每点击一次 就setCount一次,页面就更新显示 clicked xx times# 这个异步函数 也是被执行那么多次 所以3s后 控制台依次输出 1 2 3 4 ..setTimeout(() => {console.log(`current count is ${count}.`);}, 3000);};return (<div><p>You clicked {count} times</p><button onClick={log}>Click me</button></div>);}# 然而你换成class组件class Counter2 extends Component {state = { # 用state去管理变量 由于异步更新机制 setState是异步的count: 0};render() {const log = () => {this.setState({ count: this.state.count + 1 });setTimeout(() => {# 所以这里 3s后 打印的全是n次 cliled is nconsole.log(`current count is ${this.state.count}.`);}, 3000);};return (<div><p>You clicked {this.state.count} times</p><button onClick={log}>Click me</button></div>);}}
这个例子说明了,useState 和 this.setState相比, useState是立竿见影的,本质是维护的闭包,利用了闭包
由于闭包本身的性能优良,所以无需再利用个异步更新队列setState去异步更新状态
useEffect:
function Counter() {const [count, setCount] = useState(0);useEffect(() => {document.title = `You clicked ${count} times`;});# 连续点击三次button,页面的title将依次改为1、2、3,而不是3、3、3return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);}
eventhandle

先点击(触发函数3s渲染props.name的回调),然后在控制台输出前去切换select,改变showName的props
问:控制台输出啥?setState排队的异步 肯定先于 settimeout,所以 是 foo
这里只能拿到变更前的!!!
相当于,FC组件在props更新的时候 没有更新
render的时候是更新的
但是 函数里的settimeout 的回调没有是最新的
===> 回调在click事件发生的那瞬间就 将回调放在settimeout里了 那个时候的值就确定了
相比较而言:
是因为 函数是否 实例的原因吗?
==》 no 都只渲染了一次。当上层的props更新的时候
demo:https://codesandbox.io/s/cv-handler-mqsun?file=/src/index.js
不具备的ref的表现
state是Immutable的,ref是mutable的
const [count, setCount] = useState(0);const countRef = useRef(count); # countRef.current 记录变量# 每次订阅count变化后,重置变量 = 最新的count# 3s后控制台输出结果是 n次的 5 (最终拿到的都是最新值)# 而对比 如果 countRef的变量是 useState管理的# 肯定是 输出 1 2 3 4useEffect(() => {countRef.current = count;setTimeout(() => {// setTimeout里边始终能拿到state最新值console.log(`current count is ${countRef.current}`);}, 3000);}, [count]);
匪夷所思
依赖问题
useCallback与useMemo
参考文章
- 从React组件设计理论说起->class 组件问题(组件复用困局、js class的缺陷)、function组件缺失的问题->function+hook
- 性能优化的api:useCallback(针对无线循环代码 为啥useCallback可以解决)、useMemo(渲染list)、useRef
- Capture values
- useState 和 this.setState() 相比,useState更改是立竿见影的
- eventhandle 让我怀疑人生了
- ref看懂了
===> 嗯 Capture values的例子 让我怀疑 function组件 和 class组件的基本功都没掌握好
于是从该文章末尾的参考资料 顺藤摸瓜 到了这两篇文章
精读《Function Component 入门》
精读《useEffect 完全指南》
