前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React Hooks随记

React Hooks随记

作者头像
Clearlove
发布2021-03-08 12:14:58
8770
发布2021-03-08 12:14:58
举报
文章被收录于专栏:前端客栈前端客栈

Hook规则

  1. 只在最顶层使用Hook,不在条件、循环或者嵌套函数中使用Hook
  2. 只在React函数式组件或自定义Hook中使用Hook

为什么Hook高度依赖执行顺序?

Hook存储在组件的私有属性中__hooks_list数组中。读取和存储都依赖currentIndex,如果hook的执行顺序改变,currentIndex获取的hook可能是完成错误的。

Effect Hook

Effect Hook 可以在函数组件中执行一些具有side effect(副作用)的操作

参数
  1. 回调函数: 在组件第一次render和之后的每次update后运行,React保证在DOM已经更新完成后才会回调。
  2. 状态依赖(数组): 当配置了状态依赖项后,只有检测倒配置状态变化后,才会调用回调函数。
代码语言:javascript
复制
useEffect(() => {
  // 只有组件render后执行
}, []);

useEffect(() => {
  // 只有count改变时才会执行
}, [count]);
回调函数返回值

useEffect的第一个参数可以返回一个函数,这个函数会在页面更新渲染后,执行下次useEffect之前调用。这个函数是对上一次调用useEffect进行清理。

代码语言:javascript
复制
export default function HookTest() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log(`执行...当前count: ${count}`);
    
    return () => {
      console.log(`清除...当前count: ${count}`);
    }
  }, [count]);
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick-{() => setCount(count + 1)}>click me</button>
    </div>
  );
}

执行上面的代码,并点击几次按钮,会得到下面的结果:

代码语言:javascript
复制
执行...当前count: 1
清理...当前count: 1
执行...当前count: 2
清理...当前count: 2
执行...当前count: 3
清理...当前count: 4

如果加上浏览渲染的情况,结果应该是这样的:

代码语言:javascript
复制
页面渲染...1
执行...当前count: 1
页面渲染...2
清理...当前count: 1
执行...当前count: 2
页面渲染...3
清理...当前count: 2
执行...当前count: 3
页面渲染...4
清理...当前count: 3
执行...当前count: 4

那为什么浏览器在渲染完后,再执行清理的方法还能找到上一次的state呢?原因很简单,我们再useEffect中返回的是一个函数,形成了一个闭包,这能保证我们上一次执行函数存储的变量不会被销毁和污染。

举例会更好理解:

代码语言:javascript
复制
let flag = 1;
let clean;

function effect(flag) {
  console.log(`effect...curFlag: ${flag}`);
  return function() {
    console.log(`clean...curFlag: ${flag}`);
  }
}

clean = effect(flag);
flag = 2;
clean();
clean = effect(flag);
flag = 3;
clean();
clean = effect(flag);
clean();

// effect...curFlag: 1
// clean...curFlag: 1
// effect...curFlag: 2
// clean...curFlag: 2
// effect...curFlag: 3
// clean...curFlag: 3
模拟componentDidMount

componentDidMount等价于useEffect的回调,仅在页面初始化完成后执行一次。当useEffect的第二个参数传入一个空数组时就可以实现这种效果。

代码语言:javascript
复制
function uesDidMount(callback) {
  useEffect(callback, []);
}

官方不推荐这种写法,因为可能会导致一些错误。

模拟componentWillUnMount
代码语言:javascript
复制
function useUnMount(callback) {
  useEffect(() => callback, []);
}

不像componentDidMount或者componentDidUpdateuseEffect中使用的effect并不会阻止浏览器渲染页面。这让页面渲染看起来更加流程。

LayoutEffect Hook

红圈中是同步操作

useLayoutEffectuseEffect类似,但不同的是:

  • useEffect不会阻塞浏览器的重绘
  • useLayoutEffect会阻塞浏览器的重绘。如果需要手动修改dom,推荐使用useLayoutEffect。因为如果在useEffect中更新domuseEffect不会阻塞浏览器重绘,用户可能会看到因为更新导致的闪烁。

ref Hook

使用useRef Hook,你可以轻松获取domref

代码语言:javascript
复制
export default function Input() {
  const inputEl = useRef(null);
  
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  
  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus this input</button>
    </div>
  );
}

useRef不仅仅可以用来当作获取domref。还可以通过useRef产生的refcurrent属性的可变性,用它来保存任意值。

模拟componentDidUpdate

componentDidUpdate就相当于第一次调用的useEffect,借助useRef生成一个标识,来记录是否为第一次执行:

代码语言:javascript
复制
function useDidUpdate(callback, prop) {
  const init = useRef(true);
  useEffect(() => {
    if (init.current) {
      init.current = false;
    } else {
      return callback();
    }
  }, prop)
}

useCallback和useMemo的异同

React中,性能优化点在于:

  1. 调用setState,就会触发组件的重新渲染,不论state是否变化
  2. 父组件更新,子组件也会更新

基于以上两点,useCallbackuseMemo就是解决性能问题的杀手锏。

useCallbackuseMemo的异同: 共同点: 仅仅是依赖数据发生变化时,才会重新计算结果,起到缓存作用。

两者区别:

  1. useMemo计算结果是return回来的值,主要用于缓存计算结果的值。应用场景: 需要计算的状态。
  2. useCallback计算结果是函数,主要用于缓存函数。
useCallback

useCallback返回的是缓存的函数,最简单的用法:

const fnA = useCallback(fnB, [a]);

当依赖 a 变更时,会返回新的函数。我们无法很好的判断返回函数是否变更,可以借助ES6新增的数据类型Set来判断,具体如下:

代码语言:javascript
复制
const set = new Set();

export default function Callback() {
  const [count, setCount] = useState(0);
  const [val, setVal] = useState('');
  
  const callback = useCallback(() => {
    console.log(count);
  }, [count]);
  
  set.add(callback);
  
  return(
    <div>
      <h4>{count}</h4>
      <h4>{set.size}</h4>
      <div>
        <button onClick={() => setCount(count + 1)}>add</button>
        <input value={val} onChange={e => setVal(e.target.value)} />
      </div>
    </div>
  );
}

每次修改countset.size都会+1,这说明useCallback依赖变量count变化时,会返回新的函数。而val变化时,set.size无变化,说明返回的是缓存的函数。

知道useCallback特点后,有什么作用呢? 使用场景:有一个父组件,包含子组件,子组件接收一个函数作为peops。通常而言,如果父组件更新了,子组件也会执行。但大多数情况下,更新是没有必要的。我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

代码语言:javascript
复制
function Parent() {
  const [count, setCount] = useState(0);
  const [val, setVal] = useState('');
  
  const callback = useCallback(() => {
    return count;
  }, [count]);
  
  return (
    <div>
      <h4>{count}</h4>
      <Child callback={callback} />
      <div>
        <button onClick={() => setCount(count + 1)}></button>
        <input value={val} onChange={e => setVal(e.target.val)} />
      </div>
    </div>
  );
}

function Child({ callback }) {
  const [count, setCount] = useState(() => callback());
  
  return (
    <div>{count}</div>
  );
}
useMemo

我们先来看个反例:

代码语言:javascript
复制
export default function withoutMemo() {
  const [count, setCount] = useState(1);
  const [val, setVal] = useState('');
  
  function expensive() {
    console.log('compute');
    let sum = 0;
    for(let i = 0; i < count * 1000; i ++) {
      sum += i;
    }
    return sum;
  }
  
  return (
    <div>
      <h4>{count}-{val}-{expensive()}</h4>
      <div>
        <button onClick={() => setCount(count + 1)}></button>
        <input value={val} onChange={e => setVal(e.target.val)} />
      </div>
    </div>
  );
}

这里创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。我们可以看到:无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行。但是这里的昂贵计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。

在这种情况下,我们就可以使用useMemo,只在count的值修改时,执行expensive计算:

代码语言:javascript
复制
export default function withMemo() {
  const [count, setCount] = useState(1);
  const [val, setVal] = useState('');
  const expensive = useMemo(() => {
    console.log('compute');
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
      sum += i;
    }
    return sum;
  }, [count]);
  
  return (
    <div>
      <h4>{count}-{val}-{expensive()}</h4>
      <div>
        <button onClick={() => setCount(count + 1)}></button>
        <input value={val} onChange={e => setVal(e.target.val)} />
      </div>
    </div>
  );
}

上面我们可以看到,使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去。这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值。

同时也可以用来持久化一个执行函数,避免子组件的重复渲染,例如:

代码语言:javascript
复制
function Parent() {
  const [count, setCount] = useState(1);
  const [val, setValue] = useState('');
 
  const getNum = useCallback(() => {
    return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a + b);
  }, [count]);
 
  return (
    <div>
      <Child getNum={getNum} />
      <div>
        <button onClick={() => setCount(count + 1)}>+1</button>
        <input value={val} onChange={e => setValue(e.target.value)}/>
      </div>
    </div>
  );
}

const Child = React.memo(function ({ getNum }) {
  return (<h4>总和:{getNum()}</h4>);
});

未完待续

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Hook规则
    • 为什么Hook高度依赖执行顺序?
      • Effect Hook
        • 参数
        • 回调函数返回值
        • 模拟componentDidMount
        • 模拟componentWillUnMount
      • LayoutEffect Hook
        • ref Hook
          • 模拟componentDidUpdate
        • useCallback和useMemo的异同
          • useCallback
          • useMemo
        • 未完待续
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档