1 the introduction

Recoil is a data flow management solution developed by Facebook, which is worth thinking about.

Recoil is an Immutable data stream management scheme, which is the most important reason why it is worth looking at. To manage React data streams in a Mutable way, just look at Mobx-React.

However, the predictability of React Immutable is very useful for debugging and maintenance:

  1. Breakpoint debugging is very predictable because the value of a variable is independent of the current execution location.
  2. The React framework has a single component update mechanism, and rerenders are triggered only by reference changes, without the mental burden of ForceUpdate in Mutable mode.

Of course, Immutable mode involves some encoding mental burden, so there are pros and cons.

But Recoil, like Redux, does not represent the React official data flow management solution, so it should not be viewed with an official halo.

2 brief introduction

Recoil solves the React global data flow management problem, uses decentralized atomic state management design mode, supports derived data and asynchronous query, and can override Redux in basic functions.

State scope

As with Redux, global data flow management requires the presence of scope RecoilRoot:

import React from "react";
import { RecoilRoot } from "recoil";

function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}
Copy the code

When RecoilRoot is nested, the innermost RecoilRoot overrides the configuration and status values of the outer layer.

Define the data

Unlike Redux, which defines initState centrally, Recoil uses Atom to define data in a decentralized manner:

const textState = atom({
  key: "textState".default: ""});Copy the code

The key must be unique in RecoilRoot scope.

Default defines the default value. Since the data definition is scattered, the default value definition is also scattered.

Read the data

Like Redux’s Connect or useSelector, Recoil reads data using Hooks:

import { useRecoilValue } from "recoil";

function App() {
  const text = useRecoilValue(textState);
}
Copy the code

UseRecoilValue and useSetRecoilState can both get data. The difference is that useRecoilState can also get functions that write data:

import { useRecoilState } from "recoil";

function App() {
  const [text, setText] = useRecoilValue(useRecoilState);
}
Copy the code

Modify the data

Unlike Redux, which defines data modified by a pure function reducer, Recoil uses Hooks to write data.

In addition to the useRecoilState mentioned above, there is another useSetRecoilState that can only get write functions:

import { useSetRecoilState } from "recoil";

function App() {
  const setText = useSetRecoilValue(useRecoilState);
}
Copy the code

UseSetRecoilState differs from useRecoilState and useRecoilValue in that changes in the data flow do not cause the component Rerender because useSetRecoilState only writes and does not read.

This has led to the criticism that the Recoil API is overrepresented, a mental strain of coding that exists in the Immutable mode and, while understandable, can only be addressed by splitting the API like useSelector or Recoil.

UseResetRecoilState is also provided to reset to the default and read.

Read only, not subscribe

Similar to ReactRedux’s useStore, Recoil provides useRecoilCallback for read-only, unsubscribe scenarios:

import { atom, useRecoilCallback } from "recoil";

const itemsInCart = atom({
  key: "itemsInCart".default: 0});function CartInfoDebug() {
  const logCartItems = useRecoilCallback(async ({ getPromise }) => {
    const numItemsInCart = await getPromise(itemsInCart);

    console.log("Items in cart: ", numItemsInCart);
  });
}
Copy the code

The useRecoilCallback defines the data to read through a callback, and this data change does not cause the current component to be rerendered.

The derived value

Similar to Mobx computed, Recoil provides selector support for derived values, which is more distinctive:

import { atom, selector, useRecoilState } from "recoil";

const tempFahrenheit = atom({
  key: "tempFahrenheit".default: 32});const tempCelcius = selector({
  key: "tempCelcius".get: ({ get }) = > ((get(tempFahrenheit) - 32) * 5) / 9.set: ({ set }, newValue) = > set(tempFahrenheit, (newValue * 9) / 5 + 32)});function TempCelcius() {
  const [tempF, setTempF] = useRecoilState(tempFahrenheit);
  const [tempC, setTempC] = useRecoilState(tempCelcius);
}
Copy the code

Selector provides a get and set definition of how to assign and take values, so it can be manipulated by the useRecoilState API as well as the Atom definition. You don’t even have to look at the source code to guess that Atom should be a specific wrapper based on selector.

Asynchronous read

Asynchronous data reads can be implemented based on selector by writing the get function as asynchronous:

const currentUserNameQuery = selector({
  key: "CurrentUserName".get: async ({ get }) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    if (response.error) {
      throw response.error;
    }
    returnresponse.name; }});function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>} ><CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}
Copy the code
  1. Asynchronous state can beSuspenseCapture.
  2. Asynchronous procedure error reporting can beErrorBoundaryCapture.

If you don’t want to block asynchrony with Suspense, you can use the useRecoilValueLoadable API instead to manage asynchrony state within the current component:

function UserInfo({ userID }) {
  const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
  switch (userNameLoadable.state) {
    case "hasValue":
      return <div>{userNameLoadable.contents}</div>;
    case "loading":
      return <div>Loading...</div>;
    case "hasError":
      throwuserNameLoadable.contents; }}Copy the code

Dependency on external variables

Like ResELECT, Recoil suffers from a state management problem that relies on external variables to read data, leading to complex cache calculations and even the emergence of re-ResELECT libraries.

Because Recoil itself is atomic state managed, this problem is relatively easy to solve:

const myMultipliedState = selectorFamily({
  key: "MyMultipliedNumber".get: (multiplier) = > ({ get }) => {
    returnget(myNumberState) * multiplier; }});function MyComponent() {
  const number = useRecoilValue(myMultipliedState(100));
}
Copy the code

If the external parameter pass multiplier and the dependent value myNumberState are unchanged, the recalculation is not performed.

Recoil does a better job of internally generating dependencies when the Get and set functions define Atom.

Dependencies on external variables using the Family suffix, such as selector -> selectorFamily; Atom – > atomFamily.

3 intensive reading

Recoil’s atomized separation of state management is an Immutable mode of programming, especially for caching. But in the programming world, strengths often become weaknesses when viewed from another perspective, so let’s evaluate Recoil objectively.

Immutable mental burden

As mentioned in the introduction, this is probably an Immutable weakness, not just a problem for Recoil.

In Immutable mode, data streams are only read and write, while declarative programming emphasizes that the UI Rerender is automatically Rerender after data changes. Therefore, reading data is expected to Rerender after data changes, but writing is different from reading. Why does setState emphasize writing data in callback mode? Since callback writes do not depend on reads, components with write requirements need not be associated with reads, i.e., writing components need not subscribe to the corresponding data.

Recoil provides useRecoilState as a dual read and write API, only for both read and write scenarios, while useRecoilValue is simply a simplification of the API. Replacing useRecoilState with useRecoilState does not cost performance. UseSetRecoilValue must be taken seriously and the API must be strictly used in write-only scenarios.

Why is useState read/write by default? Because useState is a single-component state management scenario, a state defined within a component cannot be written without being read, but Recoil is a global state solution. In the read-write separation scenario, it is necessary for write-only components to be detached from the subscription of data to maximize performance.

Conditional access data

This is also a common problem with Hooks, because Hooks cannot be written in conditional statements, so to retrieve data with conditional judgments using Hooks, you must go back to selector mode:

const articleOrReply = selectorFamily({
  key: "articleOrReply".get: ({ isArticle, id }) = > ({ get }) => {
    if (isArticle) {
      return get(article(id));
    }

    returnget(reply(id)); }});Copy the code

This code is quite redundant, but it is possible to use isArticle? Replies (8) replies (8) replies (8) store.articles[id] : store.replies (8) replies (8) replies (8) replies (8)

The essence of the Recoil

From the Hooks API to derived values, these two core features happen to encapsulate Context and useMemo.

First, useContext, based on Hooks, is lightweight enough to use, Atom, useRecoilState, useRecoilValue, and useSetRecoilValue correspond to encapsulated createContext and useContext, respectively.

UseMemo, for the most part we can use useMemo to make derived values, which correspond to Recoil’s selector and selectorFamily.

So Recoil is essentially more of a modular wrapper library, designed for data-driven scenarios that are easy to atomize and manage, with high performance.

3 summary

Whether you use Recoil or not, we can learn the basics of React state management from Recoil:

  1. Object read and write separation, achieve optimal on-demand rendering.
  2. Derived values must be strictly cached and references are guaranteed to be strictly equal if they hit the cache.
  3. The data stored in atoms are not related to each other, and all related data are derived using derived values.

The discussion address is recoil · Issue #251 · 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.

Pay attention to the front end of intensive reading wechat public account

Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)