1 the introduction

Last week’s Close reading on React Hooks covered the basics of React Hooks. You may have seen the Basic implementation anatomy of React Hooks, but understand the implementation principles and use them. Learning is knowledge, but using skills, watching others’ usage is like brushing Douyin (Wow, can you eat like this?). There’s always something new for you.

This article puts that knowledge into practice and looks at how many program workers are tapping the potential of React Hooks.

Remember that react-hooks are “easy to Connect to everything”, so they can listen on streams, networks, and timers. The React component makes everything that changes as input sources. When those sources change, the React component retriggers the Render function. Just pick which Hooks the component is bound to, and write the Render function.

2. Intensive reading

After referring to some of the React Hooks components, I have categorized them by function.

Because React Hooks are not very complex, they are not classified by technical implementation, after all the technology will become proficient one day, and classification by function is of enduring reference value.

DOM side effects modification/listening

There are always some seemingly unrelated hassles to building a web page, such as changing the title of the page (make sure to change the default title when switching pages), listening for changes in page size (make sure to cancel the listening when destroying components), and being disconnected (layer upon layer of decorators is starting to pile up). And react-hooks are particularly good at doing those things. They make wheels of every size.

Because React Hooks reduce the cost of using higher-order components, a lifetime of juggling becomes much simpler.

Here are a few examples:

Modify page title

Effect: Call useDocumentTitle in the component to set the page title, and when switching pages, the page title is reset to the default “front-end intensive reading” title.

UseDocumentTitle (" Personal Center ");Copy the code

Implementation: Directly with document.title assignment, can not be simpler. This simple function can be abstracted into a project utility function that each page component needs to call.

function useDocumentTitle(title) { useEffect( () => { document.title = title; Return () => (document.title = ""); }, [title] ); }Copy the code

The online Demo

Listen for page size changes and whether the network is disconnected

Effect: When the component calls useWindowSize, it gets the page size and automatically triggers component updates when the browser is scaled.

const windowSize = useWindowSize(); Return <div> page height: {windowsize.innerWidth}</div>;Copy the code

Implementation: InnerHeight = window.addEventListener(‘resize’); window.addeventListener (‘resize’) Calling setValue will trigger a call to its OWN UI component rerender, and that’s it!

Finally, note that removeEventListener unlogs the listener during destruction.

function getSize() { return { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }; } function useWindowSize() { let [windowSize, setWindowSize] = useState(getSize()); function handleResize() { setWindowSize(getSize()); } useEffect(() => { window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; } []); return windowSize; }Copy the code

The online Demo

Dynamically injecting CSS

Effect: Inject a class into the page and remove the class when the component is destroyed.

const className = useCss({
  color: "red"
});

return <div className={className}>Text.</div>;
Copy the code

Implementation: As you can see, the convenience of Hooks is to remove side effects on component destruction, so we can safely use Hooks to do some side effects. Injecting CSS is unnecessary, but destroying CSS is just a matter of finding the injected reference and destroying it, as shown in this code snippet.

There are several libraries available for DOM side effects modification/listening scenarios, as the name suggests: Document-visibility, network-status, online-status, window-scroll position, window-size, and document-title.

Auxiliary components

Hooks also enhance component capabilities, such as fetching and listening to the component runtime width.

Gets the component width and height

Effect: Get the width of a component ref instance by calling useComponentSize, and rerender gets the latest width when the width changes.

const ref = useRef(null);
let componentSize = useComponentSize(ref);

return (
  <>
    {componentSize.width}
    <textArea ref={ref} />
  </>
);
Copy the code

Implementation: Similar to DOM listening, this time it uses ResizeObserver to listen on the component REF and destroy the listener when the component is destroyed.

The essence is still to listen for side effects, but with the passing of the REF, we can listen and manipulate component granularity.

useLayoutEffect(() => { handleResize(); let resizeObserver = new ResizeObserver(() => handleResize()); resizeObserver.observe(ref.current); return () => { resizeObserver.disconnect(ref.current); resizeObserver = null; }; } []);Copy the code

Online Demo, corresponding to component component-size.

Get the value thrown by the component onChange

Effect: Use useInputValue() to get the value currently entered by the user in the Input box, rather than manually listening onChange with an otherInputValue and a callback function to write all the logic in an irrelevant place.

let name = useInputValue("Jamie"); // name = { value: 'Jamie', onChange: [Function] } return <input {... name} />;Copy the code

As you can see, not only does this not take up the component’s own state, but it also does not require hand-written onChange callback processing, which is compressed into a single line of use hooks.

Implementation: As you can probably guess from reading this, use useState to store the value of the component and throw value and onChange, Listening on onChange and modifying value with setValue triggers the component’s rerender to be called each time onChange occurs.

function useInputValue(initialValue) { let [value, setValue] = useState(initialValue); let onChange = useCallback(function(event) { setValue(event.currentTarget.value); } []); return { value, onChange }; }Copy the code

It is important to note that when we enhance a component, the component’s callback usually does not need to destroy the listener and only needs to listen once, which is different from DOM listening. Therefore, in most cases, we need to use the useCallback package and pass an empty array to ensure that we always listen once. And there is no need to unlog the callback when the component is destroyed.

Online Demo, corresponding component input-value.

Do the animation

To animate with React Hooks, we usually take some elastic values that we can assign to a component like a progress bar so that the progress changes according to some kind of animation curve.

Gets a value between 0 and 1 over a period of time

This is the basic concept of animation, getting a linear increase in time.

Effect: useRaf(t) gets a number between 0 and 1 that is constantly refreshed in t milliseconds. The component is constantly refreshed, but the refresh rate is controlled by the requestAnimationFrame (it does not lag the UI).

const value = useRaf(1000);
Copy the code

Implementation: It’s a bit verbose to write, so here’s a brief description. Use requestAnimationFrame to give a value between 0 and 1 for a given amount of time, so each time you refresh, just determine what percentage of the total time the current refresh point is, and make the denominator 1.

Online Demo, corresponding component use-raf.

Elastic animation

Effect: The component refreshes at a fixed frequency using useSpring, which increases or decreases the animation value as an elastic function.

The actual invocation is usually done by getting a value from useState and then wrapping it with an animation function, so that the component is refreshed N times instead of once, and the resulting value changes as the animation function rules, until the value stabilizes to the final input value (50 in the example).

const [target, setTarget] = useState(50);
const value = useSpring(target);

return <div onClick={() => setTarget(100)}>{value}</div>;
Copy the code

Implementation: In order to animate it, we need to rely on the rebound library, which decoubles a target value into a function called the bounce function. What we need to do is use the React Hooks call spring.setEndValue to trigger the animation event when the target value is received for the first time. And useEffect to do a one-time listen, and setValue again when the value changes.

The most amazing setTarget linkage useSpring recalculates the elastic animation part with the second parameter useEffect:

useEffect(
  () => {
    if (spring) {
      spring.setEndValue(targetValue);
    }
  },
  [targetValue]
);
Copy the code

So useSpring doesn’t need to listen for the setTarget at the point of the call. It only needs to listen for the change of target. Using the second parameter of useEffect can make a lot of difference.

The online Demo

Tween animation

Understand the principle of elastic animation, Tween animation is easier.

Effect: Use useTween to get a value from 0 to 1. The animation curve of this value is tween. And as you can see, since the range is fixed, we don’t have to give an initial value.

const value = useTween();
Copy the code

Implementation: Use useRaf to get a linearly growing value (also 0-1) and map it to 0-1 values using easing library. Here, hook is used to call hook linkage (useTween is driven by useRaf), and we can draw a parallel in other places.

const fn: Easing = easing[easingName];
const t = useRaf(ms, delay);

return fn(t);
Copy the code

Send the request

Using Hooks, you can encapsulate any request Promise as an object with standard state: Loading, Error, Result.

Generic Http encapsulation

Effect: useAsync breaks a Promise into loading, error, and result.

const { loading, error, result } = useAsync(fetchUser, [id]);
Copy the code

Implementation: Loading is set at the beginning of the Promise, result is set at the end, and error is set if there is an error. Here, the request object can be wrapped as useAsyncState to handle, which is not released here.

export function useAsync(asyncFunction) { const asyncState = useAsyncState(options); useEffect(() => { const promise = asyncFunction(); asyncState.setLoading(); promise.then( result => asyncState.setResult(result); , error => asyncState.setError(error); ) ; }, params); }Copy the code

The specific code can refer to react-async-hook. It is recommended to only understand the principle of this function, because there are some boundary cases to consider, such as component isMounted before the corresponding request result.

Request Service

The business layer usually abstracts a request service for uniform fetch (uniform URL, uniform socket implementation, etc.). If the previous relatively low approach is:

Async componentDidMount() {// setState: isLoading state try {const data = await fetchUser(); Catch (error) {// setState: isLoading, error}}Copy the code

Putting the request in redux later changed the way it was injected through Connect:

@Connect(...)
class App extends React.PureComponent {
  public componentDidMount() {
    this.props.fetchUser()
  }

  public render() {
    // this.props.userData.isLoading | error | data
  }
}
Copy the code

Which is the result of Hooks:

function App() {
  const { isLoading, error, data } = useFetchUser();
}
Copy the code

UseFetchUser can be easily written using the useAsync package above:

const fetchUser = id => fetch(`xxx`).then(result => { if (result.status ! == 200) { throw new Error("bad status = " + result.status); } return result.json(); }); function useFetchUser(id) { const asyncFetchUser = useAsync(fetchUser, id); return asyncUser; }Copy the code

Fill in the form

React Hooks are especially useful for forms, especially antD forms that support Hooks:

function App() { const { getFieldDecorator } = useAntdForm(); return ( <Form onSubmit={this.handleSubmit} className="login-form"> <FormItem> {getFieldDecorator("userName", { rules: [{ required: true, message: "Please input your username!" }] })( <Input prefix={<Icon type="user" style={{ color: "Rgba (0,0,0,.25)"}} />} placeholder="Username" />)} </FormItem> <FormItem> className="login-form-button"> Log in </Button> Or <a href="">register now! </a> </FormItem> </Form> ); }Copy the code

GetFieldDecorator, however, is based on the RenderProps idea. The thorough Hooks idea is to provide a set of component methods that can be destructed to the component using the component-assisted method described earlier.

Form component for Hooks thinking

Effect: useFormState to get the form value and provide a set of component helper methods to control the component state.

const [formState, { text, password }] = useFormState(); return ( <form> <input {... text("username")} required /> <input {... password("password")} required minLength={8} /> </form> );Copy the code

You can get the form value at any time via formState, and some validation information, pass it to the input component via password(” PWD “), and make it controlled. The input type is password, and the form key is PWD. Also, you can see that the forms used are native tags, so this form enhancement is quite decoupled.

Implementation: If you look at the structure carefully, you can easily understand how text and password act on the input component and get its input state by using the same ideas as in the component helper section “Get the value thrown by the component onChange”.

To simplify, Merge these states and aggregate them into formState via useReducer.

For simplicity, we’ll consider only input enhancements, and only 30 or so lines of source code:

export function useFormState(initialState) { const [state, setState] = useReducer(stateReducer, initialState || {}); const createPropsGetter = type => (name, ownValue) => { const hasOwnValue = !! ownValue; const hasValueInState = state[name] ! == undefined; function setInitialValue() { let value = ""; setState({ [name]: value }); } const inputProps = {name, // Add type: text or password get value() {if (! hasValueInState) { setInitialValue(); } return hasValueInState? state[name] : ""; }, onChange(e) {let {value} = e.target; setState({ [name]: value }); // Change the corresponding Key value}}; return inputProps; }; const inputPropsCreators = ["text", "password"].reduce( (methods, type) => ({ ... methods, [type]: createPropsGetter(type) }), {} ); return [ { values: state }, // formState inputPropsCreators ]; }Copy the code

The above 30 lines of code implement setting the input tag type, listening for value onChange, and finally aggregating to the large values returned as formState. React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks

In fact, a complete wheel also needs to consider the compatibility of checkbox radio, and the verification problem. These ideas are similar, and can be found in the react-use-form-state.

Simulation life cycle

The React15 API is sometimes useful, and you can simulate almost all of it using React Hooks.

componentDidMount

Effect: Use useMount to get the mount cycle callback.

useMount(() => {
  // quite similar to `componentDidMount`
});
Copy the code

Implementation: componentDidMount is equivalent to the useEffect callback (when executed only once), so just throw the callback out.

useEffect(() => void fn(), []);
Copy the code

componentWillUnmount

Effect: useUnmount to get the callback function to execute until the unmount cycle.

useUnmount(() => {
  // quite similar to `componentWillUnmount`
});
Copy the code

Implementation: componentWillUnmount is equivalent to useEffect callback function return value (only execute once), so directly throw the callback function return value out.

useEffect(() => fn, []);
Copy the code

componentDidUpdate

Effect: useUpdate to get the didUpdate callback.

useUpdate(() => {
  // quite similar to `componentDidUpdate`
});
Copy the code

Implementation: componentDidUpdate is equivalent to useMount logic every time it is executed, except for initialization the first time. Therefore, use mouting Flag (to determine the initial state) plus unlimited parameters to ensure that rerender is executed each time.

const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); }});Copy the code

Force Update

Effect: This is the most interesting, I want to get a function update that forces the current component to be refreshed every time.

const update = useUpdate();
Copy the code

Implementation: We know that the useState item with subscript 1 is used to update the data, and even if the data does not change, it will refresh the component, so we can return a setValue that does not change the value, so its function will only refresh the component.

const useUpdate = () => useState(0)[1];
Copy the code

For getSnapshotBeforeUpdate getDerivedStateFromError, componentDidCatch Hooks cannot be simulated.

isMounted

React provided this API a long time ago, but removed it because it could be derived from componentWillUnmount and componentWillUnmount. Support for isMount is a matter of minutes since React Hooks.

UseIsMounted To obtain the isMounted status.

const isMounted = useIsMounted();
Copy the code

UseEffect is set to true on the first call and false on component destruction. Note that you can add an empty array as the second argument to optimize performance.

const [isMount, setIsMount] = useState(false);
useEffect(() => {
  if (!isMount) {
    setIsMount(true);
  }
  return () => setIsMount(false);
}, []);
return isMount;
Copy the code

The online Demo

Save the data

The Built-in useReducer in React Hooks emulates the Reducer behavior of Redux, and the only thing that needs to be added is to persist the data. Let’s consider the minimum implementation, which is the global Store + Provider part.

Global Store

Create a global Store using createStore, inject a Store into the context of a child component using StoreProvider, obtain this from Hooks: useStore; useAction:

Const store = createStore({user: {name: "x ", setName: (state, payload) => {state.name = payload; }}}); const App = () => ( <StoreProvider store={store}> <YourApp /> </StoreProvider> ); function YourApp() { const userName = useStore(state => state.user.name); const setName = userAction(dispatch => dispatch.user.setName); }Copy the code

Implementation: The implementation of this example can be carried out in a separate article, so the author analyzes the implementation of StoreProvider from the perspective of storing data.

Yes, react-hooks do not fix providers, so global states must have providers, but this Provider can be easily fixed using the built-in createContext in React:

const StoreContext = createContext();

const StoreProvider = ({ children, store }) => (
  <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
);
Copy the code

That leaves useStore to fetch a persistent Store, using useContext and the Context object we just created:

const store = useContext(StoreContext);
return store;
Copy the code

For more source code, see Easy-Peasy, a library based on Redux that provides a set of Hooks apis.

Encapsulating the original library

Will all libraries need to be rewritten after React Hooks appear? Of course not. Let’s see how other libraries do it.

RenderProps to Hooks

Take the React-PowerPlug as an example.

For example, there is a library for renderProps that needs to be fixed to Hooks:

import { Toggle } from 'react-powerplug' function App() { return ( <Toggle initial={true}> {({ on, Toggle}) => (<Checkbox checked={on} onChange={toggle} />)} </ toggle >)} ↓ ↓ ↓ ↓ ↓ import {useToggle} from 'react-powerhooks' function App() { const [on, toggle] = useToggle() return <Checkbox checked={on} onChange={toggle} /> }Copy the code

Effect: If I were a maintainer of react- PowerPlug, how would it cost the least to support React Hook? To be honest, you can’t do it in one step, but you can do it in two.

Export function Toggle() {return Toggle() {return Toggle() {return Toggle() {return Toggle(); } const App = wrap(() => {// const App [on, toggle] = useRenderProps(toggle); // package useRenderProps});Copy the code

Implementation: To do this, Hooks must follow the React specification. We must write a function called useRenderProps that fits the format of Hooks. * * should take less than normal way, so settle for second best, will useRenderProps to Toggle to wrap, to wrap tectonic RenderProps execution environment to get on with Toggle, Const [on, toggle] = useRenderProps(toggle);

const wrappers = []; Wrappers export const useRenderProps = (WrapperComponent, wrapperProps) => {const [args, setArgs] = useState([]); const ref = useRef({}); if (! ref.current.initialized) { wrappers.push({ WrapperComponent, wrapperProps, setArgs }); } useEffect(() => { ref.current.initialized = true; } []); return args; // Get the value by calling setArgs with the wrap below. };Copy the code

Since useRenderProps executes before wrap, wrappers get Toggle first, and wrappers call wrappers.pop() when wrap executes to get the Toggle object. Then construct the execution environment for RenderProps:

export const wrap = FunctionComponent => props => { const element = FunctionComponent(props); const ref = useRef({ wrapper: wrappers.pop() }); // Toggle const {WrapperComponent, wrapperProps} = ref.current.wrapper; // Toggle const {WrapperComponent, wrapperProps} = ref. return createElement(WrapperComponent, wrapperProps, (... Args) => {// WrapperComponent => Toggle, this step is to construct the RenderProps execution environment if (! ref.current.processed) { ref.current.wrapper.setArgs(args); // Get on and toggle and pass it to args via setArgs. ref.current.processed = true; } else { ref.current.processed = false; } return element; }); };Copy the code

React-functions-render-props (); react-functions-props ();

Hooks to RenderProps

Well, if you want Hooks to support RenderProps, you want to support both sets of syntaxes.

Effect: A set of code that supports Hooks and RenderProps.

Let’s write the core code using Hooks. Let’s say we write the simplest Toggle:

const useToggle = initialValue => { const [on, setOn] = useState(initialValue); return { on, toggle: () => setOn(! on) }; };Copy the code

The online Demo

The RenderProps component can be easily encapsulated using the render props library:

const Toggle = ({ initialValue, children, render = children }) =>
  renderProps(render, useToggle(initialValue));
Copy the code

The online Demo

The second argument to the React component, renderProps, is this. State, which is the object returned by useToggle. Rerender, the Toggle component, is driven using the Hooks mechanism to rerender the child component.

Encapsulate the library that originally enhanced setState

Hooks are also particularly suited to encapsulating libraries that are already working on setState, such as immer.

Although useState is not setState, it can be understood as setState for controlling higher-order components. It is perfectly possible to encapsulate a custom useState and then build in the optimization of setState.

For example, the syntax of immer is generated by the produce wrapper, and the mutable code is represented as immutable by the Proxy:

const nextState = produce(baseState, draftState => {
  draftState.push({ todo: "Tweet about it" });
  draftState[1].done = true;
});
Copy the code

Produce can be hidden by encapsulating a useImmer:

function useImmer(initialValue) {
  const [val, updateValue] = React.useState(initialValue);
  return [
    val,
    updater => {
      updateValue(produce(updater));
    }
  ];
}
Copy the code

Usage:

const [value, setValue] = useImmer({ a: 1 });

value(obj => (obj.a = 2)); // immutable
Copy the code

3 summary

React Hooks are used in the following ways:

  • DOM side effects modification/listening.
  • Component assist.
  • Do the animation.
  • Send the request.
  • Fill in the form.
  • Simulate the life cycle.
  • Save the data.
  • Encapsulate the original library.

Welcome to continue to supplement.

4 More Discussions

How to build wheels with React Hooks · Issue #112 · dt-fe/weekly

If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.