Hooks are a new feature in 16.7.0-alpha that addresses the reuse of state logic, enabling stateless components to have many of the capabilities of state components, Such as the ability to update (useState), access to the ref (useRef), context, the context (useContext), more advanced setState (useReducer) and cycle method (useEffect/useLayoutEffect) and cache (useMem O, useCallback). The underlying implementation doesn’t change much. The overall approach is more functional syntax, logical cohesion, and high-level encapsulation, giving you a sense of the power and elegance of Hooks at the same time.

If you’re tired of writing about things like changing the title of a web page, checking the user’s offline status, listening to the page size, or the user’s phone status (battery, auger…) If you are tired of being a developer, custom hooks are for you. Just want to focus on Custom Hooks, F send!!!!

Before reading this article, it is recommended to unlearn, i.e. forget the “React” you learned before. It is not the “it” anymore, otherwise it will only “mislead” you.

Ps: for a better reading experience, – means deleting code, + means adding code, and * means modifying lines

This article custom Hooks repo

Personal Blog

useState

Before reading this article, we already know our level. How can we look at the API like a novice? Of course, we should start with the source code first.

function useState<S> (initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}
Copy the code

WTF???

Back to the point, how can a person have high hands and low eyes?

Master, of course, is to start from the source code.

Let’s take a look at the official API

UseState is used to define component data variables. You pass in an initial value and get an array of values, which is a dispatch function that updates the values.

const [value, updateVal] = useState(0)
value / / 0
updateVal(1)
value / / 1
Copy the code

You can also pass in a synchronization function.

// ⚠️ math.random () every time render calls math.random ()
const [value, updateVal] = useState(Math.random());
// It will only be executed once
const [value, updateVal] = useState(() = > Math.random());
Copy the code

UseState is initialized only the first time a function component is initialized. Always return the most recent value for as long as the component exists. The initialization function is never executed again. Does this look like a closure?

Pointing to problems

// Index.js
class Index extends Component {
  componentDidMount() {
    setTimeout(() = > console.log('classComponent'.this.state.value), 1000);
    this.setState({ value: 5 });
  }

  render() {
    return null; }}// App.js
function App () {
  const [value, updateVal] = useState(0);
  useEffect(() = > {
    setTimeout(() = > console.log('FunctionComponent', value), 1000);
    updateVal(5); } []);return (
    <Index />
  );
}

// classComponent 5
// FunctionComponent 0

Copy the code

If you don’t know useEffect, you can think of it as componentWillMount for now. The goal is to print the current value one second later.

The former uses this to access the latest value, whereas function components, due to closures, print to the same value before the update. This situation can be solved with useRef, if you don’t already know useRef

const num = useRef(null);
useEffect(() = > {
    setTimeout(() = > console.log(num.current), 1000); / / 2
    updateVal((pre) = > {
      const newPre = pre * 2;
      num.current = newPre;
      returnnewPre; }); } []);Copy the code

However, the timing of updateVal execution is not guaranteed (at the end of the cycle, after all); Another low option is the useState dispatch function.

 useEffect(() = > {
    setTimeout(() = > updateVal((pre) = > {
      console.log('FunctionComponent', pre);
      return pre;
    }), 1000);
    updateVal(5); } []);// classComponent 5
// FunctionComponent 5
Copy the code

It works, but re-render is triggered once due to the new dispatch set value. Therefore, this solution is not recommended. There will be enclosed hooks to do this.

The root scope is declared in order

Cannot be nested iniforforIn a statement

Hooks are stored as an array, which is why they are used. But later practice found that this is not the case (even the official misinformed me).

How does React associate Hook calls with components?

React keeps track of the current rendering component. Because of the Hooks rule, we know that Hooks can only be called from the React component (or custom Hooks can only be called from the React component as well). Each component has an internal list of “memory units”. They’re just JavaScript objects that we can put some data in. When a Hook like useState() is called, it reads the current cell (or initializes it on first rendering) and then moves the pointer to the next cell. This is how multiple useState() calls each get independent local state.

It’s actually a one-way circular linked list. Similar to a.ext === B => B.ext === C.Analyze thereference

const [state1,setState1] = useState(1)
const [state2,setState2] = useState(2)
const [state3,setState3] = useState(3)
Copy the code

Each FunctionalComponent has a corresponding Fiber object,

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;  // ReactElement[?typeof]
  this.type = null;         // ReactElement.type
  this.stateNode = null;

  / /... others
  this.ref = null;
  this.memoizedState = null;
  / /... others
}
Copy the code

The useState called in it will have a Hook object.

export type Hook = {
  memoizedState: any.baseState: any.baseUpdate: Update<any.any> | null.queue: UpdateQueue<any.any> | null.next: Hook | null.// point to the next hook node
};

Copy the code

We don’t have to worry too much about the other issues, except that when we first implement useState, we will have memoizedState on the Fiber object. This property was originally designed to store the state of the ClassComponent. Since state is the entire object in ClassComponent, there is a one-to-one correspondence with memoizedState.

In Hooks, React doesn’t know how many times we called useState, so React came up with an interesting solution to save state.

Fiber.memoizedState === hook1
state1 === hook1.memoizedState
hook1.next === hook2
state2 === hook2.memoizedState
hook2.next === hook3
state3 === hook2.memoizedState
Copy the code

Each useState called in FunctionalComponent has a corresponding Hook object that is stored on Fiber.memoizedState in the order they are executed in a one-way circular linked list format.

If the following logic occurs

if(false) {
    const [status,setStatus] = useState(false)}// or
let times = 10 // Some logic changed times
for(let i = 0; i < times; i++) {const status = useState(false)}Copy the code

After a certain re-render, a hook is missing and the next point is wrong, such as HOOk1. Next points to HOOk3, causing data confusion and failing to achieve the desired effect.

useEffect

The phased approach to the lifecycle, similar to cb in setState(state, cb), is executed at the end of the entire update cycle.

Not much, first on the source code.

///////// useEffect
export function useEffect(
  create: () => (() => void) | void,
  inputs: Array<mixed> | void | null.) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, inputs);
}
/ /... omit
function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null.) :void {
 return mountEffectImpl(
    UpdateEffect | PassiveEffect,
    UnmountPassive | MountPassive,
    create,
    deps,
  );
}

Copy the code

Well, this method is the most important of hooks. If you think of useState as HTML+CSS, then useEffect is JS.

useEffect(fn, deps? :any[]), fn executes the function, deps dependent. Like useState, fn executes once on initialization. Subsequent execution depends on the dePS changes, and if the effects are executed after re-render and the dePS is different from the last one, execution will be triggered.

Ps: React internally uses object. is to perform shallow comparisons to DEPS.

When we first switched from classComponent to hooks, we thought it would execute before Render.

By default, effects run after each rendering

useEffect(() = > {
    // only executed during initialization (after first render)} []); useEffect(() = > {
    // execute after each render
});
Copy the code

Fn returns a cleanup function, mostly used with addEventListener and removeEventListener

useEffect(() = > {
    // execute after first render
    return () = > {
        // Execute before component uninstallation}} []); useEffect(() = >{(function fn() = >{ /*dst... * /}) ()// execute after each render
    return () = > {
        // From the second time, run the cleanup function first and then fn}});Copy the code

The execution timing of the cleanup function can be interpreted as if the Effect handle has been executed, the cleanup function will be executed first next time to prevent memory leaks, and last time after the component is unloaded.

If I had to describe a life cycle, I’d say componentDidUpdate.

Do not manipulate the DOM in useEffect. For example, add tens of thousands of nodes using requestAnimationFrame. There will be unexpected surprises.

eg:

const Message = ({ boxRef, children }) = > {
  const msgRef = React.useRef(null);
  React.useEffect(() = > {
    const rect = boxRef.current.getBoundingClientRect(); // Get the size
    msgRef.current.style.top = `${rect.height + rect.top}px`; // Put it under the box
  }, [boxRef]);

  return (
    <span ref={msgRef} className="msgA">
      {children}
    </span>
  );
};
const App = () = > {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
        <div ref={boxRef} className="box" onClick={()= >setShow(prev => ! prev)}> Click me A</div>
        {show && <Message boxRef={boxRef}>useEffect</Message>}
    </div>
  );
};

Copy the code

The goal is simple, to display the Message component under a div, but when it actually runs, you’ll see a momentary jump effect.

When the Message component insideuseEffectSwitch touseLayoutEffectIt’s normal.

The reason is that although useEffect is executed after the browser draws, it also means that it fires before a new rendering. It will refresh existing effects before it needs to perform a new render.

What? You don’t believe?

Um participant:

const [val, updateVal] = useState(0)
useEffect(() = > { // hooks1
    updateVal(2); } []); useEffect(() = > { // hooks2
    console.log(val);/ / - 0
});
Copy the code

After render, hook1 updateVal(2) triggers re-render, but before doing so, we need to refresh the existing Effects, so hooks2 val prints 0. Effects Hooks2 prints 2 after the render is triggered again.

Depends on closures

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() = > {
    setTimeout(() = > {
      console.log(`You clicked ${count} times`);
    }, 3000);
  });

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

What do you think pops up with five quick clicks in a row?

Unlike classComponent, it calls this instead. Not closures.

componentDidUpdate() {
    setTimeout(() = > {
      console.log(`You clicked The ${this.state.count} times`);
    }, 3000);
  }
Copy the code

The events executed in the timer are completely dependent on closures. You may not agree, but it is true.

Don’t lie to React about dependencies

function App(){
   const [tagType, setTagType] = useState()
    async function request () {
        const result = await Api.getShopTag({
            tagType
        })
    }
    useEffect(() = >{
        setTagType('hot')
        request()
    },[])
    return null
}
Copy the code

The request function depends on the tagType, but Effects does not depend on the tagType. When the tagType changes, the value of the tagType in the request remains hot. You might just want to mount it, but for now just remember: if you set a dependency, all the values in the components used in effect will be included in the dependency. This includes props, state, functions-anything inside the component.

The latter

This is often written when a component requests data during the year

function App(){
    async function request () {
        // ...
        setList(result)
        setLoaded(false)
    }
    useEffect(() = >{
        request()
    },[])
    return null
}
Copy the code

While access is fine under normal circumstances, you can be pleasantly surprised when components are bulky or slow to request.

It means you go to another page before the request is complete, causing the effectsetList/setLoadedDon’t know how to send warmth. This is also the downside of closures — they can’t be destroyed in time. There is another solutionAbortController.

In fact, with these two apis, 80% of the business is done. In line with the 80-20 rule, that is, 20% of the functions accomplish 80% of the business. Encapsulation of custom hooks is also required for most.

useLayoutEffect

The name useLayoutEffect differs from useEffect by a Layout. As the name implies, the difference is that the execution time is different, which means that the trigger is triggered after Layout. After the render.

The source code is as follows:

The signature is the same as useEffect, but fires synchronously after all DOM changes. Use it to read the layout from the DOM and synchronize rerendering. Updates that will be planned within useLayoutEffect will refresh synchronously before the browser has a chance to draw.

The official explanation is that it blocks rendering, so useEffect when you don’t manipulate the dom to avoid blocking visual updates.

Render > useLayoutEffect > useEffect > setState(useState) > Effects > Render (2nd time) >…

The execution mechanism of setState in useLayoutEffect is different from that of useEffect. Although the merger strategy was implemented in the end. The mount and update phases are also different. Even the order in which useState is declared at the top of a function component can result in inconsistent results.

The order in which the Hooks are triggered by update is a little easier to understand than the component mount.

RequestAnimationFrame “breaks” the task, which is executed after the redraw, which is when useLayoutEffect is executed.

Ps: useEffect is preferred if you just change data, as it does not block rendering. This is both a plus and a minus, no blocking (representing asynchrony), and certainly no order. UseLayoutEffect is recommended for DOM manipulation.

useReducer

The built-in hook is related to Redux. Use the same method as redux.

const reducer = (state,action) = > {
    letbackups = { ... state }switch(action.type){
        case 'any': // ... ; break;
    }
    return backups
}
const initial = { nowTimes: Date.now() }
function App () {
    const [val, dispatch] = useReducer(reducer,initial);
    return null
}
Copy the code

Manually implement a useReducer using useState.

function useMyReducer(reducer, initialState, init) {
  const compatible = init ? init(initialState) : initialState;
  const [state, setState] = useState(compatible);
  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}
Copy the code

The third parameter, similar to redux’s initialState, sets the initialState. Re-render is triggered each dispatch regardless of whether the Reducer is matched or not.

If you want to use it instead of Redux it might still be missing something. One obvious problem is that the state defined here is bound to the component and, like useState, cannot share data with other components. But you can do that with useContext.

useContext

useContext(context: React.createContext)

// Context/index.js
const ContextStore = React.createContext()

// App.js

function Todo() {
  const value = React.useContext(ContextStore);
  return (
    <React.Fragment>
      {
        JSON.stringify(value, null, 4)
      }
    </React.Fragment>
  );
}

function App() {
  return (
    <ContextStore.Provider value={store}>
      <Todo />
    </ContextStore.Provider>
  );
}
Copy the code

By using method discovery, the useReducer can produce/change data using context. Provider at the top of the component tree and consume data using useContext in the child components.

const myContext = React.createContext();
const ContextProvider = () = > {
    const products = useReducer(productsReducer, { count: 0 });
    const order = useReducer(orderReducer, { order: []});const store = {
        product: products,
        order: order // [ order,deOrder ]
    }
    return (
        <myContext.Provider value={store}>
          <Todo />
        </myContext.Provider>
    );
};

const Todo = () = > {
    const { product, order } = useContext(myContext)
    return (
        <React.Fragment>
            {
                JSON.stringify(state, null, 4)
            }
            <button onClick={product.dispatch}> product dispatch </button>
        </React.Fragment>)}Copy the code

The downside is that as the amount of data increases, the entire application becomes “bloated” and performs poorly. There’s a nice implementation of ioStore.

useMemo

原 文 memo, if more appropriate I think it should be called cache, useCache? But in hindsight, it’s ok to call it a memo, because it’s state logic reuse.

UseMemo and resELECT libraries function in the same way, depending on incoming values, there is a fixed input must be a fixed output. You don’t have to recalculate to optimize performance. Avoid recalculating wasted performance without changing dependencies.

But ResELECT is too cumbersome to use. UseMemo is much simpler

const memoDate = useMemo(() = >{
   return new Date().toLocalString() 
},[])
Copy the code

The second parameter of useMemo is the same as the useEffect function, which is recalculated when the dependency changes. MemoDate will always be the same within the component.

dependency

Maybe you’ve written code like this

const [count, setCount] = useState(0);

const request = useMemo(() = > {
    return aysnc () => {
        let result = await Api.getMainShopTag({
            startNum: count,
            size: 10
        })
        setCount(result.count)
    }
}, []);
return <button onClick={request} type="button"> request </button>
Copy the code

A simple paging label request starts out fine, and when the second request is made count is still 0. UseMemo caches functions, and naturally caches Pointers to variables within functions. So you need to add the parameters that the function needs to depend on in DEPS.

If you’re not familiar with all this, there is a plugin for ESLint: eslint-plugin-react-hooks that automatically fixes dependencies and optimizes them. Strictly follow guidelines when developing custom hooks.

npm i eslint-plugin-react-hooks -D

Copy the code
// .eslintrc
{
    // other ...
    "plugins": [
        "html"."react"."react-hooks"]."rules": {"react-hooks/rules-of-hooks": "error"."react-hooks/exhaustive-deps": "warn"}}Copy the code

useCallback

UseCallback is a variant of useMemo. They work the same way, and you can interpret the former as favoring function caching. Use the useCallback cache whenever possible when defining property variable methods that do not depend on the current component. Avoid component redeclarations before each render.

useCallback(fn, deps) === useMemo(() = > fn, deps))
Copy the code

For example, the code above you can simplify to

const request = useCallback(aysnc () => {
        let result = await Api.getMainShopTag({
            startNum: count,
            size: 10
        })
        setCount(result.count)
    }, [count]);
Copy the code

About memo and callback

Use useCallback and useMemo with caution. Many people think that both are about solving the performance problems of creating functions, but they are not.

Serving:

const forgetPwd = () = > {
    const sendSmsCode = () = > { / *... * /}}const forgetPwd = () = > {
    const sendSmsCode = useMemo(() = >{ / *... * / }, [A,B])
}

Copy the code

In the example above the effect is the same, sendSmsCode will be created anyway. As long as the latter requires a slight performance cost (no matter how small the mosquito is, it is still meat), it may be wondered why the performance is getting worse with caching.

<button onClick={() = > {}}>Search</button>
// second render
<button onClick={()= > {}}>Search</button>
Copy the code

Two render inline functions are never equal, contrary to the memo concept. This is a meaningless diff. It’s a waste of time, and components are never optimized by Memo.

UseCallback actually caches instances of inline callback, which works with React. Memo to avoid unnecessary rendering. Both will suffer if one is missing. When your function component UI container contains any inline functions, any optimizations are meaningless.

useRef

The problem is that the component data state cannot be saved

function Interval() {
  const [time, setTime] = useState(Date.now());
  let intervalId;
  const start = () = > {
    intervalId = setInterval(() = > {
      setTime(Date.now());
    }, 500);
  };
  const clear = () = > {
    clearInterval(intervalId);
  };
  return (
    <div>
      <button onClick={start} type="button">start</button>
      <button onClick={clear} type="button">clear</button>
    </div>
  );
}
Copy the code

This appears to be a normal logic, but after starting the timer, it is found that the timer cannot be turned off.

This is because after the timer is started and setTime(date.now ()) updates the value, the function component is re-render. At this point intervalId has been redeclared. So we can’t clear the old timer.

The function component is not instantiated, meaning that this cannot be used and there is no internal component property variable. You need to avoid it being redeclared every time.

const [intervalId, setIntervalId] = useState(null)
const start = () = > {
    setIntervalId(
        setInterval(() = > setTime(Date.now()), 500))};Copy the code

Do I have to use all the useState storage states? As mentioned earlier, re-render is triggered every time the setIntervalId handle is executed, even if it is not used in the view.

You can use useRef to handle component properties. Transform component

// ... other
let intervalId = useRef(null);
const start = () = > {
    intervalId.current = setInterval(() = > {
        setTime(Date.now());
    }, 500);
};
const clear = () = > {
    clearInterval(intervalId.current);
};

Copy the code

The best reason to use useRef is that it does not trigger re-render. Source:

function mountRef<T> (initialValue: T) :{current: T} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  if (__DEV__) {
    Object.seal(ref);
  }
  hook.memoizedState = ref;
  return ref;
}

Copy the code

What about useRef?

You can think of it as a box. You can put any data (or even components) in there — it’s all inclusive. Current in the box will be isolated and the value will be deeply copied, undisturbed and unresponsive. You can reassign via ref.current. Re-render and useEffect are not triggered. Values obtained through.current are always up to date.

const info = useRef({ status: false });

const focus = () = > {
    // Always up to date
    setTimeout(() = > console.log(info.current), 1000); // {status: true}
    info.current.status = true;
}

const input = useRef();
useEffect(() = > {
    // Access methods on elements
    input.current.focus()
}, [])

useEffect(() = > {
    // Info changes are not triggered
}, [info.current.status])

return <input ref={input} type="text" onFocus={focus} />

Copy the code

useImperativeHandle

Although this component property is accessible through useRef. But if the parent element wants to manipulate the child component, it is less powerful. You can use this.chilren to access child component methods in classComponent. Function components do not have this privilege because they are not instantiated. UseImperativeHandle (formerly useImperativeMethods) is officially provided to expose component methods to the parent component. In addition, the forwardRef needs to forward the node to use. The official example is very clear:

// FancyInput.js
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () = > ({
    focus: () = > {
      inputRef.current.focus();
    },
    blurs: () = >{
        // dst...}}));return <input ref={inputRef} . />;
}
FancyInput = forwardRef(FancyInput);

//App.js
function App(){
    const fancyInputRef = useRef()

    useEffect(() = >{
        fancyInputRef.current.focus()
    },[])
    return (
        <React.Fragment>
            <FancyInput ref={fancyInputRef} />
        </React.Fragment>)}Copy the code

useDebugValue

This is a secondary identifier. Used on a Custom Hook, identifying the Hook as a Custom Hook. For example, declare a useMount.

const useMount = (mount) = > {
  useEffect(() = >{ mount && mount(); } []); useDebugValue('it"s my Custom hook'.fit= > `${fit}! ` );
};
// App.js
const App = () = > {
  useMount(() = > { /*dst... * /});
  return (
    <Provider>
      <Count />
      <IncrementButton />
    </Provider>
  );
};

Copy the code

The Api will only load if ReactDevTools is enabled, as shown in blueCustom HookName, such as useMount. Red is the description.

Custom hook

Implement the setState callback

export const useStateCallback: <T>(
  initialState: T
) = > [T, stateCallback<T>] = <T>(initialState: T) = > {
  const [state, setState] = useState<T>(initialState)
  const cbRef = useRef<stateCallback<T>>(null)

  const setStateCallback: stateCallback<T> = (state, cb) = > {
    cbRef.current = cb // store passed callback to ref
    setState(state)
  }

  useEffect(() = > {
    if (cbRef.current) {
      cbRef.current(state)
      cbRef.current = null // reset callback after execution
    }
  }, [state])

  return [state, setStateCallback]
}
Copy the code

useLifeCycles

Maybe you don’t need to take it out separately. But I couldn’t help it.

const useLifeCycles = (mount, unMount) = > {
  useEffect(() = > {
    mount && mount();
    return () = >{ unMount && unMount(); }; } []); };Copy the code

useRequestAnimationFrame

RequestAnimationFrame is used a lot, and of course it is wrapped as a hooks; Too long a name is not a good thing.

/**
 * useRaf useRequestAnimationFrame
 * @param Callback callback function *@param StartRun immediately executes */
const useRaf = (callback, startRun = true) = > {
  const requestRef = useRef(); // Store the ID returned by RequestAnimationFrame
  const previousTimeRef = useRef(null); // Each time interval

  const animate = useCallback((time) = > {
    if(previousTimeRef.current ! = =undefined) {
      const deltaTime = time - previousTimeRef.current; // Time interval
      callback(deltaTime);
    }
    previousTimeRef.current = time;
    requestRef.current = requestAnimationFrame(animate);
  }, [callback]);

  useEffect(() = > {
    requestRef.current = requestAnimationFrame(animate);
    return () = >cancelAnimationFrame(requestRef.current); } []);const stopRaf = useCallback(() = > {
    if(startRun) cancelAnimationFrame(requestRef.current);
    requestRef.current = null;
  }, [animate]);

  const restartRaf = useCallback(() = > {
    if (requestRef.current === null) {
      requestAnimationFrame(animate);
    }
  }, [animate]);

  return [restartRaf, stopRaf];
};

// App.js
const App = () = > {
  const [count, setCounter] = useState(0);
  const run = () = > {
    setCounter(pre= > pre + 1);
  };
  const [start, stop] = useRaf(() = > run());
  return (
    <div>
      <button type="button" onClick={start}>start</button>
      <button type="button" onClick={stop}>suspended</button>
      <h1>{count}</h1>
    </div>
  );
};
Copy the code

This hook takes a function as a callback for frame changes, and the callback takes a parameter as the elapsed time since the last performing.now () interval, typically around 16ms (not significant, but can enable optimizations for low-profile users). A hook returns two controllers, one for restarting without resetting the data, and the other for pausing.

Ps: Do not use it to manipulate the DOM. If you must use it, use useLayoutEffect instead.

With this hook, you can easily make cool components such as stopwatches, countdowns, and numbers changing frame by frame.

usePrevious

Use useRef to save the previous value. In Effect, the first value is undefined. Sometimes it is necessary to judge. Here we use Symbol to judge and return the value for the first time. Of course, you can ignore this case (the second argument is false).

export const usePrevious = (value) = > {
  const r = useRef(Math.random().toString(36)) // Create globally unique IDS using random numbers
  const ref = useRef(Symbol.for(r.current));

  useEffect(() = > {
    ref.current = value;
  });
  return Symbol.for(r.current) === ref.current ? value : ref.current;
};
Copy the code

useEventListener

Don’t want to write native Event events too often, wrap them as hooks.

export function useEventListener(eventName, handler, target = window) {
  const memoHandler = useRef();
  useEffect(() = > {
    memoHandler.current = handler;
  }, [handler]);

  useEffect(() = > {
    const eventListener = event= > memoHandler.current(event);
    const targetEl =
      "current" in target && typeof target.current === "object"
        ? target.current
        : target;
    targetEl.addEventListener(eventName, eventListener);
    return () = > {
      targetEl.removeEventListener(eventName, eventListener);
    };
  }, [eventName, target]);
}
Copy the code

Because react-dom is IE9+, attachEvent is not considered. Function components can only access elements through ref, adding. Current judgment to prevent errors.

useDebounce

Anti-shake is familiar

/** * the anti-shake function executes *@param {*} Fn is the buffered function *@param {number} [ms = 300] interval * /
export const useDebounce = (fn, args, ms = 300 ) = > {
  
  const pendingInput = useRef(true);
  
  useEffect(() = > {
    let savedHandlerId;
    if (pendingInput.current) {
      pendingInput.current = false;
    } else {
      savedHandlerId = setTimeout(fn, ms);
    }
    return () = > clearTimeout(savedHandlerId);
  }, [fn, ms, args]);
};
Copy the code

useThrottle

Throttling has a simpler third-party implementation

const throttled = useRef(throttle((newValue) = > {
    // dst...
}, 1000))
useEffect(() = > throttled.current(value), [value])
Copy the code

But when in Rome, do as the Romans do.

/* * Throttle function, wait for the elevator, the elevator 15 seconds round, enter does not reset. @param {*} fn is being throttled * @param {*} args depends on updating parameters * @param {number} [timing=300] throttle time * @returns Throttle value */
const useThrottle = (fn, args, timing = 300) = > {
  const [state, setState] = useState(() = >fn(... args));const timeout = useRef(null);
  const lastArgs = useRef(null); // Last parameter
  const hasChanged = useRef(false); // Is there an update
  useEffect(() = > {
    if(! timeout.current) {const timeoutHandler = () = > {
        if (hasChanged.current) { // If there is an update, update it immediately and start it again, otherwise abandon the update
          hasChanged.current = false;
          setState(() = >fn(... lastArgs.current)); timeout.current =setTimeout(timeoutHandler, timing);
        } else {
          timeout.current = undefined; }}; timeout.current =setTimeout(timeoutHandler, timing);
    } else {
      lastArgs.current = args; // Update the latest parameters
      hasChanged.current = true; // There is an update task
    }
  }, [...args, fn, timing]);
  return state;
};
Copy the code

Method of use

const throttledValue = useThrottle(value= > value, [val], 1000);
Copy the code

useImtArray

Make an ImmutableArray.

/** * Double encapsulates the array to achieve ImmutableArray effect *@param {*} initial 
 * @returns* /
const useImtArray = (initial = []) = > {
  const [value, setValue] = useState(() = >{
    if(!Array.isArray(initial)) {
      throw new Error('useImtArray argument Expectations are arrays. Actually, they are' + Object.prototype.toString.call(initial))
    }
    return initial
  });
  return {
    value,
    push: useCallback(val= > setValue(v= > [...v, val]), []),
    pop: useCallback(() = > setValue(arr= > arr.slice(0, arr.length - 1)), []),
    shift: useCallback(() = > setValue(arr= > arr.slice(1, arr.length)),[]),
    unshift: useCallback(val= > setValue(v= > [val, ...v]), []),
    clear: useCallback(() = > setValue(() = > []), []),
    removeByVal: useCallback(val= > setValue(arr= > arr.filter(v= >v ! == val)),[]),removeByIdx: useCallback(index= > setValue(arr= >
          arr.filter((v, idx) = > parseInt(index, 10) !== idx),
        ), []),
  };
};
Copy the code

usePromise

Promise, of course.

/** * simplify Promise *@param {*} Fn Promise function *@param {*} [args=[]] depends on the update parameter *@returns Loading: loading state,value: successful value,error: failed value */
const usePromise = (fn, args = []) = > {
  const [state, setState] = useState({});
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const memoPromise = useCallback(() = > fn(), args);

  useEffect(() = > {
    let pending = true; // Prevent multiple triggers
    setState(newestState= > ({ ...newestState, loading: true }));
    Promise.resolve(memoPromise())
      .then(value= > {
        if (pending) {
          setState({
            loading: false,
            value,
          });
        }
      })
      .catch(error= > {
        if (pending) {
          setState({
            loading: false, error, }); }});return () = > {
      pending = false;
    };
  }, [memoPromise]);

  return state;
};


// App.js
const request = () = > new Promise((resolve,reject) = >{
  setTimeout(() = >{
    if(Math.random() > 0.5 ){
      resolve('Success')}else{
      reject('Fail')}},2000)})function App(){
  const { value, loading, error} = usePromise(request)
  return (
    <div>{loading? <span>Loading...</span> : result:<span>{error||value}</span>}</div>)}Copy the code

useGetter

Object.definedproperty makes it easy to listen to read properties

import { clone, isPlainObject } from '.. /utils';
/** * Listen for object properties to be read *@param {*} Watcher listening object *@param {*} Fn callback * /
const useGetter = (watcher, fn) = > {
  if(! isPlainObject(watcher)) {throw new Error(
      `Expectation is the object, the actual result The ${Object.prototype.toString.call(
        watcher,
      )}`,); }const value = useMemo(() = > watcher, [watcher]);
  const cloneVal = useMemo(() = > clone(watcher), [watcher]);
  const cb = useRef(fn);

  Object.keys(cloneVal).forEach(name= > {
    Object.defineProperty(value, name, {
      get() {
        if (typeof cb.current === 'function')
          cb.current(name, cloneVal);
        returncloneVal[name]; }}); }); };Copy the code

useLockBodyScroll

This hook is seen accidentally, aimed at preventing the mask from rolling through. The original address

/** * lock the body scrollbar, used for modal, background */
const useLockBodyScroll = () = > {
  useLayoutEffect(() = > {
    const originalStyle = window.getComputedStyle(document.body)
      .overflow;
    document.body.style.overflow = 'hidden';
    return () = > {
      document.body.style.overflow = originalStyle; }; } []); };Copy the code

useTheme

You can even switch themes and colors yourself, like this

/** * change the theme *@param {*} Theme Indicates the theme data */
const useTheme = theme= > {
  useLayoutEffect(() = > {
    for (const key in theme) {
      document.documentElement.style.setProperty(
        `--${key}`,
        theme[key],
      );
    }
  }, [theme]);
};
Copy the code

useInput

Are you still manually onChange when writing input?

/**
 * auto Input Hooks
 * @param {*} Initial Input Initial value *@returns InputProps clear to empty the replace (arg: any | Function) bind binding Input * /
function useInput(initial) {
  const [value, setValue] = useState(initial);
  function onChange(event) {
    setValue(event.currentTarget.value);
  }
  const clear = () = > {
    setValue(' ');
  };
  const replace = arg= > {
    setValue(pre= > (typeof arg === 'function' ? arg(pre) : arg));
  };
  return {
    bind: {
      value,
      onChange,
    },
    value,
    clear,
    replace,
  };
}

function Input() {
  let userName = useInput("Seven"); // {clear,replace,bind:{value,onChange}}
  return <input {. userName.bind} / >;
}
Copy the code

useDragger

A minimalist drag hook, slightly modified.

/** * drag element *@param {*} El Target element *@returns X, y offset pageX, top left corner of the pageY element */
function useDraggable(el) {
  const [{ dx, dy }, setOffset] = useState({ dx: 0.dy: 0 });
  const [{ pageX, pageY }, setPageOffset] = useState({
    pageX: 0.pageY: 0}); useEffect(() = > {
   const { top, left } = el.current.getBoundingClientRect();
    setPageOffset({ pageX: top, pageY: left });
    const handleMouseDown = event= > {
      const startX = event.pageX - dx;
      const startY = event.pageY - dy;
      const handleMouseMove = e= > {
        const newDx = e.pageX - startX;
        const newDy = e.pageY - startY;
        setOffset({ dx: newDx, dy: newDy });
      };
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup'.() = > {
          document.removeEventListener('mousemove', handleMouseMove); }, {once: true });
    };
    el.current.addEventListener('mousedown', handleMouseDown);
    return () = > {
      el.current.removeEventListener('mousedown', handleMouseDown);
    };
  }, [dx, dy, el]);

  useEffect(() = > {
    el.current.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
  }, [dx, dy, el]);

  return { x: dx, y: dy, pageX, pageY };
}
Copy the code

Life cycle: The transition from a class component to a function component

React the latest version is 16.9. Use illustrations to explore how to implement the react lifecycle. There’s no reason to use LifyCycle anymore, but it’s good to know.

ComponentDidMount and componentWillUnmount

Because it is a function component, there will not be a full LifeCycle that is not instantiated. ComponentWillMount and componentDidMount are only sequentially placed on top of the component.

function App (){
    // Function component top
    const [value, setValue] = useState(0)
    useEffect(() = > {
        console.log('componentDidMount');
        return () = > {
          console.log('componentWillUnMount'); }; } []);// other
}
Copy the code

forceUpdate

Force an update by updating an unrelated state closure variable

const [updateDep,setUpdateDep] = useState(0)
function forceUpdate() {
    setUpdateDep((updateDep) = > updateDep + 1)}Copy the code

getSnapshotBeforeUpdate

Call useLayoutEffect after render and before render dom. The effect needs to be verified.

const SnapshotRef = useRef()
useLayoutEffect(() = >{
    SnapshotRef.current = / /...
})
Copy the code

componentDidUpdate

Using the setState callback example above, the difference is that componentDidUpdate relies on all values, so there is no DEPS. Combined with useRef and useEffect implementation, componentDidUpdate execution time is the second time the component starts render, only need to judge whether the number of render execution is greater than 1. The timing was later than useLayoutEffect. To get the latest snapshotref.current.

let updateRender = useRef(0)

useEffect(() = > {
  updateRender.current++ 
  if(updateRender.current > 1) {// componentDidUpdate
      // get SnapshotRef.current do some thing}})Copy the code

shouldComponentUpdate

In class, PureComponent replaces shouldComponentUpdate, and in function components, the Memo of course, it takes a lot of work to optimize a component perfectly.

But sometimes we just want to keep a component from updating. It can be done

const ShouldUpdateCertainCpt = useMemo(() = > (
    <div>Never updated</div>
), [])
    
return (
    <ShouldUpdateCertainCpt />
)
Copy the code

The latter

That said, do as the Romans do. Remember that hooks are revolutionary and cannot be considered evolution that will LifeCycle with ClassComponent. They should be called reworks, which are not newbie friendly. Exposing the underlying mechanics to developers through Effects is a smart move. If LifeCycle is still intended to be implemented using Hooks then why not use the “old version” of React?

Hooks are more interesting

Some hooks in this paper are developed from the ideas of React -use and Hook – Guide. With hooks in your hand, you are only one Idea away from success.

Hooks describe
React Use Hooks tool library
useHistory Manage the history stack
useScript Dynamically adding scripts
useAuth User state
useWhyDidYouUpdate Hook according to – Did You – Update
useDarkMode Switch to Night mode

reference

  • Dan A Complete Guide to useEffect

If you still feel good about it, so do star