cisen / blog

Time waits for no one.
134 stars 20 forks source link

react hooks 相关 #267

Open cisen opened 5 years ago

cisen commented 5 years ago

介绍

https://github.com/cisen/blog/issues/174

思考

hooks使用规则

API

useState

想定于初始化一个值和它的getter

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

传入函数

initialState参数是初始渲染期间使用的状态。 在随后的渲染中,它被忽略了。 如果初始状态是昂贵计算的结果,则可以改为提供函数,该函数仅在初始渲染时执行

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

useEffect

useEffect 就是告诉组件在“渲染完”之后做点什么事,是异步的 第二个可选参数,只有同一 useEffect 的两次调用第二个参数不同时,第一个函数参数才会被调用

useEffect(() => {
    // 这里只有mount时才被调用,相当于componentDidMount
  }, [123]);
useEffect(() => {
    // 这里只有mount时才被调用,相当于componentDidMount
  }, []);

useEffect(() => {
    // 这里只有mount时才被调用,相当于componentWillUpdate
  }, random());

useContext

const context = useContext(Context);

接受由最近的context provider使用React.createContext生成的Context作为参数,返回Context的值给用户使用。当provider更新的时候,这个hook会触发一次render并返回最新的context值

useReducer

这个类似useState,只是它能接受一个reducer来派发不同action生成不同的state。官方说明挺多的,建议看看。

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter({initialState}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

useCallback

const set = new Set();

export default function Callback() { const [count, setCount] = useState(1); 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)}>+</button>
        <input value={val} onChange={event => setVal(event.target.value)}/>
    </div>
</div>;

}

我们可以看到,每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。

知道useCallback有什么样的特点,那有什么作用呢?

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
```js
import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    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={event => setVal(event.target.value)}/>
        </div>
    </div>;
}

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

不仅是上面的例子,所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。

useMemo

重要你只能用useMemo作为性能优化,不能作为语法支持。因为未来react可能会忘记以前记忆的值,并在下一次render重新计算它。所以确保代码在没有useMemo支持下也能正确运行!

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
import React from 'react';

export default function WithoutMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');

    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }

    return <div>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

这里创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。我们可以看到:无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印);但是这里的昂贵计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。在这种情况下,我们就可以使用useMemo,只在count的值修改时,执行expensive计算:

export default function WithMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = 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}-{expensive}</h4>
        {val}
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

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

useRef

useLayoutEffect

useDebugValue

不推荐在代码中使用

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // Show a label in DevTools next to this Hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}
cisen commented 5 years ago

各种模拟

模拟shouldComponentUpdate

const areEqual = (prevProps, nextProps) => {
   // 返回结果和shouldComponentUpdate正好相反
   // 访问不了state
}; 
React.memo(Foo, areEqual);

模拟componentDidMount

useEffect(() => {
    // 这里在mount时执行一次
}, []);

模拟componentDidUpdate

const mounted = useRef();
useEffect(() => {
  if (!mounted.current) {
    mounted.current = true;
  } else {
    // 这里只在update是执行
  }
});

模拟componentDidUnmount

useEffect(() => {
    // 这里在mount时执行一次
    return () => {
       // 这里在unmount时执行一次
    }
}, []);

模拟isMounted

// 使用
onst isMounted = useIsMounted();
// 模拟
const [isMount, setIsMount] = useState(false);
useEffect(() => {
  if (!isMount) {
    setIsMount(true);
  }
  return () => setIsMount(false);
}, []);
return isMount;

模拟Force Update

// 使用
const update = useUpdate();
// 模拟,注意:setXXX === useState(0)[1],这里相当于强制调用setXXX来重新render
const useUpdate = () => useState(0)[1];