useCallback

  1. const memoizedCallback = useCallback(
  2. () => { # 回调函数仅在某个依赖项改变时才会更新
  3. doSomething(a, b);
  4. },
  5. [a, b],
  6. );
  7. # useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

返回一个 memoized 回调函数。

由于javascript函数的特殊性,当函数签名被作为deps传入useEffect时,还是会引起re-render(即使函数体没有改变),这种现象在类组件里边也存在:

  1. // 当Parent组件re-render时,Child组件也会re-render
  2. class Parent extends Component {
  3. render() {
  4. const someFn = () => {}; // re-render时,someFn函数会重新实例化
  5. return (
  6. <>
  7. <Child someFn={someFn} />
  8. <Other />
  9. </>
  10. );
  11. }
  12. }
  13. class Child extends Component {
  14. componentShouldUpdate(prevProps, nextProps) {
  15. return prevProps.someFn !== nextProps.someFn; // 函数比较将永远返回false
  16. }
  17. }

Function Component(查看demo):

  1. function App() {
  2. const [count, setCount] = useState(0);
  3. const [list, setList] = useState([]);
  4. const fetchData = async () => {
  5. setTimeout(() => { # 2.依赖的这个函数会对 state进行更改,一旦更改会触发组件重绘
  6. setList(initList); # 而组件一旦更新 又会进入useEffect 所以就会无限循环
  7. }, 3000);
  8. };
  9. useEffect(() => { # 1.副作用的依赖是 函数
  10. fetchData();
  11. }, [fetchData]); # 3.解决方法(中断无限循环): 如何让组件re-render的时候 fetchData不是新的
  12. return (
  13. <>
  14. <div>click {count} times</div>
  15. <button onClick={() => setCount(count + 1)}>Add count</button>
  16. <List list={list} />
  17. </>
  18. );
  19. }

解决方法:
1、将fetchData函数移到组件外部(缺点是无法读取组件的状态了)
2、条件允许的话,把函数体移到useEffect内部 (直接干掉这个函数)
3、通用解决方法:
使用useCallback API包裹函数,useCallback的本质是对函数进行依赖分析,依赖变更时才重新执行
image.png

useMemo

useMemo用于缓存一些耗时的计算结果,只有当依赖参数改变时才重新执行计算:

  1. const fibValue = useMemo(() => fibonacci(start), [start]); // 缓存耗时操作
  1. function App(props) {
  2. const list = props.list;
  3. # list 更改的时候 MemoList才会重新计算出 li
  4. # 简单理解:useCallback(fn, deps) === useMemo(() => fn, deps)
  5. const MemoList = useMemo(() => <List list={list} />, [list]);
  6. return (
  7. <>
  8. {MemoList}
  9. <Other />
  10. </>
  11. );
  12. }
  13. # useMemo也可以用React.memo,但相比将整个组件React.memo,用useMemo去渲染memo后的dom,更细粒度
  14. // 只有列表项改变时组件才会re-render
  15. const MemoList = React.memo(({ list }) => {
  16. return (
  17. <ul>
  18. {list.map(item => (
  19. <li key={item.id}>{item.content}</li>
  20. ))}
  21. </ul>
  22. );
  23. });
  24. # 相比React.memo,
  25. # useMemo在组件内部调用,可以访问组件的props和state,所以它拥有更细粒度的依赖控制。

useRef

useRef 返回一个ref对象的可变引用,但useRef的用途比ref更广泛,它可以存储任意javascript值而不仅仅是DOM引用。
其 .current 属性被初始化为传入的参数(initialValue)
修改useRef值的唯一方法是修改其current的值,且值的变更不会引起re-render
每一次组件render时useRef都返回固定不变的值,不具有Capture Values特性
useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”

  1. // const refContainer = useRef(initialValue);
  2. function TextInputWithFocusButton() {
  3. const inputEl = useRef(null);
  4. const onButtonClick = () => {
  5. // `current` 指向已挂载到 DOM 上的文本输入元素
  6. inputEl.current.focus();
  7. };
  8. return (
  9. <>
  10. // 将 ref 对象以 <div ref={myRef} /> 形式传入组件,
  11. // 则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。
  12. <input ref={inputEl} type="text" />
  13. <button onClick={onButtonClick}>Focus the input</button>
  14. </>
  15. );
  16. }

useRef() 和自建一个 {current: …} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。

概念

Capture Values特性

非useRef相关的Hooks API,(useState、useEffect、event handle)
(state是Immutable的,ref是mutable的)
本质上都形成了闭包,闭包有自己独立的状态,这就是Capture Values的本质。

useSate:

  1. function Counter1() {
  2. const [count, setCount] = useState(0);
  3. const log = () => { # 点击的时候会触发这个函数
  4. setCount(count + 1); # 每点击一次 就setCount一次,页面就更新显示 clicked xx times
  5. # 这个异步函数 也是被执行那么多次 所以3s后 控制台依次输出 1 2 3 4 ..
  6. setTimeout(() => {
  7. console.log(`current count is ${count}.`);
  8. }, 3000);
  9. };
  10. return (
  11. <div>
  12. <p>You clicked {count} times</p>
  13. <button onClick={log}>Click me</button>
  14. </div>
  15. );
  16. }
  17. # 然而你换成class组件
  18. class Counter2 extends Component {
  19. state = { # 用state去管理变量 由于异步更新机制 setState是异步的
  20. count: 0
  21. };
  22. render() {
  23. const log = () => {
  24. this.setState({ count: this.state.count + 1 });
  25. setTimeout(() => {
  26. # 所以这里 3s后 打印的全是n次 cliled is n
  27. console.log(`current count is ${this.state.count}.`);
  28. }, 3000);
  29. };
  30. return (
  31. <div>
  32. <p>You clicked {this.state.count} times</p>
  33. <button onClick={log}>Click me</button>
  34. </div>
  35. );
  36. }
  37. }

这个例子说明了,useState 和 this.setState相比, useState是立竿见影的,本质是维护的闭包,利用了闭包
由于闭包本身的性能优良,所以无需再利用个异步更新队列setState去异步更新状态

useEffect:

  1. function Counter() {
  2. const [count, setCount] = useState(0);
  3. useEffect(() => {
  4. document.title = `You clicked ${count} times`;
  5. });
  6. # 连续点击三次button,页面的title将依次改为1、2、3,而不是3、3、3
  7. return (
  8. <div>
  9. <p>You clicked {count} times</p>
  10. <button onClick={() => setCount(count + 1)}>Click me</button>
  11. </div>
  12. );
  13. }

eventhandle

image.png
先点击(触发函数3s渲染props.name的回调),然后在控制台输出前去切换select,改变showName的props
问:控制台输出啥?
setState排队的异步 肯定先于 settimeout,所以 是 foo
这里只能拿到变更前的!!!
相当于,FC组件在props更新的时候 没有更新
render的时候是更新的
image.png
但是 函数里的settimeout 的回调没有是最新的
===> 回调在click事件发生的那瞬间就 将回调放在settimeout里了 那个时候的值就确定了

相比较而言:
image.png
是因为 函数是否 实例的原因吗?
==》 no 都只渲染了一次。当上层的props更新的时候

demo:https://codesandbox.io/s/cv-handler-mqsun?file=/src/index.js

不具备的ref的表现

state是Immutable的,ref是mutable的

  1. const [count, setCount] = useState(0);
  2. const countRef = useRef(count); # countRef.current 记录变量
  3. # 每次订阅count变化后,重置变量 = 最新的count
  4. # 3s后控制台输出结果是 n次的 5 (最终拿到的都是最新值)
  5. # 而对比 如果 countRef的变量是 useState管理的
  6. # 肯定是 输出 1 2 3 4
  7. useEffect(() => {
  8. countRef.current = count;
  9. setTimeout(() => {
  10. // setTimeout里边始终能拿到state最新值
  11. console.log(`current count is ${countRef.current}`);
  12. }, 3000);
  13. }, [count]);

匪夷所思

写React Hooks前必读

依赖问题

useCallback与useMemo

参考文章

蚂蚁金服衍良 - React Hooks完全上手指南

  1. 从React组件设计理论说起->class 组件问题(组件复用困局、js class的缺陷)、function组件缺失的问题->function+hook
  2. 性能优化的api:useCallback(针对无线循环代码 为啥useCallback可以解决)、useMemo(渲染list)、useRef
  3. Capture values
    1. useState 和 this.setState() 相比,useState更改是立竿见影的
    2. eventhandle 让我怀疑人生了
    3. ref看懂了

===> 嗯 Capture values的例子 让我怀疑 function组件 和 class组件的基本功都没掌握好
于是从该文章末尾的参考资料 顺藤摸瓜 到了这两篇文章
精读《Function Component 入门》
精读《useEffect 完全指南》