preface

Currently, every front-end team has its own preferences for Using React Hooks, and there may be no best practices for managing data flows based on Hooks, but there are some guidelines to follow during development. When you’re not familiar with something new, you should look at its documentation.

React Hooks are completely different mental models and should not be used in the same way as before from the start. The best thing to understand about Function Component is that it is more thoroughly state-driven, and while there has been a lot of writing about how to emulate the lifecycle using Hooks, the concept of a lifecycle is already dead in name only.

In practice, we just have to keep in mind the idea of state/flow, where all events, requests, and data changes depend on the “input flow” of that role. Grasping this point, we then consider the triggering effect of data flow changes, consider whether the effect needs to trigger? Is the input stream mutable? Think clearly and write boldly.

The ideas of the flow

You cannot step into the same river twice

How do you understand the use of Hooks in terms of flow? Here are a few examples:

The dead such as so, day and night

Props and state are both immutable constants! The Function Component is reexecuted each time render is performed. Each time it’s a new state, a new effect, and the historical state is kept in the React “snapshot.” The tihs of this.state can retain immutability and be modified multiple times; Instead, you can think of multiple states generated by multiple renders.

As in this example from the official documentation, effect is executed every time it is rendered without specifying a dependency. This is why React cleans up the previous effect before executing the current effect.

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() = > {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading... ';
  }
  return isOnline ? 'Online' : 'Offline';
}
Copy the code

The shape of water, avoiding high and descending

Just as water flows only from upstream to downstream, in React, the state is one-way. Therefore, for cross-component data sharing, since data can only be passed from parent to child nodes in the component tree, we need to provide special policies, such as Context.

const ThemeContext = React.createContext(themes.default);
/ / producer
function App() {
  const [color, setColor] = useState(themes.light);
  return (
    <ThemeContext.Provider value={color}>
      <Toolbar1 />
      <Toolbar2 />
    </ThemeContext.Provider>
  );
}
/ / consumer
function Toolbar(props) {
  const theme = useContext(ThemeContext);
  return (
    <div>
    	<button style={{ background: theme.background.color: theme.foreground}} >
        I am styled by theme context!
    	</button>
    </div>
  );
}
Copy the code

Water is flowable; hooks depend on the order in which they are called, so we can pass a state to the next hooks as an argument.

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);

  return (
  / /...)}Copy the code

The practice of the flow

In fact, using hooks in some application scenarios, you can achieve very concise writing. For example, to request a List, we treat “paging”, “querying”, and “brushing” as states, and the request operation only depends on these states. In this way, the usage here is actually very similar to Rxjs combineLatest.

const App = () = > {
  const [pagination, setPagination] = useState<object>(defaultPagination);
  const [searchValue, setSearchValue] = useState<object>(defaultSearchValue);
  const [refresh, setRefresh] = useState<number>(0);
  useEffect(() = > {
    // fetch
  }, [pagination, searchValue, refresh])
};
Copy the code

Hooks optimization Practices

Summary of personal practice, the optimization of hooks can generally be carried out from two aspects:

  • One is through comparison, rendering only when needed
  • The second is to skip rendering using dependencies/invariants

Compare to optimize

Because each render of the parent component can cause props to change, the values passed should be optimized using useMemo or useCallback.

  • useCompareEffect
// https://github.com/kentcdodds/use-deep-compare-effect
type Effect = typeof React.useEffect;
type EffectDeps = Parameters<Effect>[1];

export const useDeepCompareEffect: Effect = (effect, deps?) = > {
  const prevRef = useRef<EffectDeps>();
  const deepDep = useRef<number>(0);
  if(! deepEqual(prevRef.current, deps)) { prevRef.current = deps; deepDep.current = deepDep.current +1;
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  return useEffect(effect, [deepDep.current]);
};
Copy the code
  • useDeepCompareMemo
type Memo = typeof React.useMemo;
type MemoDeps = Parameters<Memo>[1];

export const useDeepCompareMemo: Memo = (factory, deps?) = > {
  const prevRef = useRef<MemoDeps>();
  const deepDep = useRef<number>(0);
  if(! deepEqual(prevRef.current, deps)) { prevRef.current = deps; deepDep.current = deepDep.current +1;
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  return useMemo(factory, [deepDep.current]);
};
Copy the code
  • usePrevious

If we want to get the state of the last render and compare it, usePrevious

// https://github.com/streamich/react-use
export default function usePrevious<T> (state: T) :T | undefined {
  const ref = useRef<T>();
  useEffect(() = > {
    ref.current = state;
  });
  return ref.current;
}

// compare
const App = () = > {
  const [count, setCount] = useState<number>(0);
  const prevCount = usePrevious<number>(count);
  useEffect(() = > {
    if(count > prevCount + 5) {
      / /...
    }
  }, [count])
}
Copy the code

Independent/invariant

  • defaultProps

For immutable default arguments, we can write them outside the function

const defaultValue = { status: "OPEN" };
const App = ({ value = defaultValue }) = > {
	/ /...
};
// or
App.defaultProps = {
  value: { status: "OPEN"}}Copy the code
  • useRef

For data that does not need to be relied on, ensure that the latest value is stored in ref

const App = ({ onChange }) = > {
  const callback = useRef(onChange);
  callback.current = onChange;
  useEffect(() = > {
    / /...
    dom.addEventListener("click", callback.current);
    / /...}} []);Copy the code
  • Functional setState
const App = () => { const [count, setCount] = useState<number>(0); useEffect(() => { setCount(count => count + 1); }} []);Copy the code

conclusion

Mainly about some personal practical experience, front-end data flow this part still have a lot of content to understand and learn, first make a record of it ~

Refer to the article

  • A Complete Guide to useEffect
  • Why Do React Hooks Rely on Call Order?
  • React