List

  • 关于 Hooks
  • useCallback & useMemo
  • 通过 hooks 实现防抖、节流
  • useReducer
  • ProvidersComposer
  • 遗留问题合集

关于 Hooks

React内置的hook提供了基础的能力,虽然本质上它也有一些分层,比如:

  • useState是基于useReducer的简化版。
  • useMemo和useCallback事实上可以基于useRef实现。

开发过程中注意配合插件 eslint-plugin-react-hooks,指定相关规则 rules-of-hooks、exhaustive-deps

Why there is no callback for setState, useEffect

Because it invokes a thought pattern that may very well be an antipattern with hooks.

With hooks, you are writing code more declarative than imparative.

The thought of “do one thing THEN do another thing THEN do another thing” would be the imparative (here: callback- or promise-based) way.

What actually should be done in the hooks model would rather be a “if this has been rendered, and this property is different than before, X should be done to reach the target state”, which is more declarative. You don’t designate when stuff happens, but just give a condition/dependency and let react pick the right point in time for it.

useCallback & useMemo

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useCallback(fn, deps) 返回 fn 这个会根据 deps 变化的回调函数,侧重于函数

而 useMemo(() => fn/computeExpensiveValue(a, b), [a, b]) 侧重于返回值,哪怕你这个返回值实际上是个函数

useMemo 缓存组件,以 PinViewComments 为例,仅在初始拿到 tmpProps 内容后就应该被独立出去

之后 usePinViewHook 中状态的变更不应该影响到 PinViewComments,参考如下:

  1. // before 每次 usePinViewHook 中状态有变更,会导致 PinViewComments 重新加载
  2. const PinViewCommentsLogic = () => {
  3. const { comments, comment_count } = pin;
  4. if (comment_count) {
  5. let tmpProps = {
  6. comments,
  7. comment_count,
  8. pf,
  9. };
  10. console.log('enter PinViewCommentsLogic'); // 这里会在初始化时被打印,之后 usePinViewHook 中状态变更再次打印
  11. return <PinViewComments {...tmpProps} />;
  12. } else {
  13. return null;
  14. }
  15. };
  16. // adxxxxxxxxxxx top
  17. // adxxxxxxxxxxx pin_detail
  18. return (
  19. <div className={pf}>
  20. // ...
  21. <PinViewCommentsLogic />
  22. </div>
  23. );
  24. // after 只在初始后
  25. const PinViewCommentsLogic = useMemo(() => {
  26. const { comments, comment_count } = pin;
  27. if (comment_count) {
  28. let tmpProps = {
  29. comments,
  30. comment_count,
  31. pf,
  32. };
  33. console.log('enter PinViewCommentsLogic'); // 仅在初始化时被打印
  34. return <PinViewComments {...tmpProps} />;
  35. } else {
  36. return null;
  37. }
  38. }, []);
  39. return (
  40. <div className={pf}>
  41. // ...
  42. {/* <PinViewCommentsLogic /> */}
  43. {PinViewCommentsLogic}
  44. );

通过 hooks 实现防抖、节流

  1. // 防抖
  2. useEffect(() => {
  3. const timer = setTimeout(() => {
  4. console.log(a);
  5. }, 2000);
  6. return () => clearTimeout(timer);
  7. }, [a])
  8. function useDebounce(fn, delay, dep = []) {
  9. const { current } = useRef({ fn, timer: null });
  10. useEffect(function () {
  11. current.fn = fn;
  12. }, [fn]);
  13. return useCallback(function f(...args) {
  14. if (current.timer) {
  15. clearTimeout(current.timer);
  16. }
  17. current.timer = setTimeout(() => {
  18. current.fn.call(this, ...args);
  19. }, delay);
  20. }, dep)
  21. }
  22. // 节流

useReducer

  1. function useReducer(reducer, initialState) {
  2. const [state, setState] = useState(initialState);
  3. function dispatch(action) {
  4. const nextState = reducer(state, action);
  5. setState(nextState);
  6. }
  7. return [state, dispatch];
  8. }

ProvidersComposer

  1. const ProviderComposer = ({ providers, children }) => {
  2. return providers.reduceRight((children, parent, index) => React.cloneElement(parent, { children }), children);
  3. };
  4. const ProvidersComposer = (props: any) => {
  5. return (
  6. props.providers.reduceRight((children: any, Parent: any) => (
  7. <Parent>{ children }</Parent>
  8. ), props.children)
  9. );
  10. };

遗留问题合集

  • 为什么会渲染两次

为什么会渲染两次

  1. class Test extends Components {
  2. render() {
  3. console.log('render');
  4. return <div>Test</div>;
  5. }
  6. }

上面这个说实话我讲不出所以然,改用现在函数式的写法不会渲染两次,再看下面这个:

  1. const Test = () => {
  2. const [f, setF] = useState(false);
  3. let t = useRef(null);
  4. const onClick = () => {
  5. setF(true);
  6. };
  7. console.log('render: ', f);
  8. return (
  9. <div style={{ width: '200px', height: '100px', backgroundColor: '#3fa' }} onClick={onClick} ref={t}>
  10. {f.toString()}
  11. </div>
  12. );
  13. };

这个的第一次点击,将状态 f 从 false 变更成 true,这个 render 还能理解,可以再点击一次,还会打印,之后继续点击,再无反应,这个怎么解释呢?