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. 状态依赖(数组): 当配置了状态依赖项后,只有检测倒配置状态变动后,才会调用回调函数。
useEffect(() => {  // 只有组件render后执行}, []);useEffect(() => {  // 只有count扭转时才会执行}, [count]);

回调函数返回值

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

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>  );}

执行下面的代码,并点击几次按钮,会失去上面的后果:

执行...以后count: 1清理...以后count: 1执行...以后count: 2清理...以后count: 2执行...以后count: 3清理...以后count: 4

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

页面渲染...1执行...以后count: 1页面渲染...2清理...以后count: 1执行...以后count: 2页面渲染...3清理...以后count: 2执行...以后count: 3页面渲染...4清理...以后count: 3执行...以后count: 4

那为什么浏览器在渲染完后,再执行清理的办法还能找到上一次的state呢?起因很简略,咱们再useEffect中返回的是一个函数,造成了一个闭包,这能保障咱们上一次执行函数存储的变量不会被销毁和净化。

举例会更好了解:

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的第二个参数传入一个空数组时就能够实现这种成果。

function uesDidMount(callback) {  useEffect(callback, []);}
官网不举荐这种写法,因为可能会导致一些谬误。

模仿componentWillUnMount

function useUnMount(callback) {  useEffect(() => callback, []);}
不像componentDidMount或者componentDidUpdateuseEffect中应用的effect并不会阻止浏览器渲染页面。这让页面渲染看起来更加流程。

LayoutEffect Hook

红圈中是同步操作

useLayoutEffectuseEffect相似,但不同的是:

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

ref Hook

应用useRef Hook,你能够轻松获取domref

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生成一个标识,来记录是否为第一次执行:

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来判断,具体如下:

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传递给子组件;这样,子组件就能防止不必要的更新。

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

咱们先来看个反例:

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计算:

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的时候,返回上一次缓存的值。

同时也能够用来长久化一个执行函数,防止子组件的反复渲染,例如:

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>);});

未完待续