1. The introduction

React-easy-state is an interesting library that uses proxies to create an easy-to-use way to manage global data flows.

import React from "react";
import { store, view } from "react-easy-state";

const counter = store({ num: 0 });
const increment = () => counter.num++;

export default view(() => <button onClick={increment}>{counter.num}</button>);
Copy the code

It’s easy to get started. Create a data object from the Store. When used by any React component, the object is automatically bound in both directions.

Of course, to do this, you need to wrap a view around all the components.

2. The intensive reading

The library uses NX-JS /observer-util as the Reaction base API. The other core functions are Store View Batch, so we will interpret them from these four points.

Reaction

This word is called “reaction,” and it is the most basic unit of functionality for implementing bidirectional binding libraries.

It has two basic words and one concept: Observable Observe and auto-triggered execution.

import { observable, observe } from "@nx-js/observer-util";

const counter = observable({ num: 0 });
const countLogger = observe((a)= > console.log(counter.num));

// This automatically triggers the countLogger callback.
counter.num++;
Copy the code

The implementation principle is described in detail in the section “Pulling the wool out of the wool to Implement Dependency Tracing” in the intensive Reading DoB – Framework Implementation issue 35, which will not be covered here.

With a reactive function and an object that can “trigger a reactive”, bidirectional binding to update the View is not far off.

store

The react-easy-state store is an Observable (obj) wrapped around it. The only difference is that it supports local data:

import React from 'react'
import { view, store } from 'react-easy-state'

export default view((a)= > {
  const counter = store({ num: 0 })
  const increment = (a)= > counter.num++
  return <button={increment}>{counter.num}</div>
})
Copy the code

So if we detect that a store is created inside the React component and the environment is Hooks, we will return:

return useMemo((a)= > observable(obj), []);
Copy the code

This is because Function Component in the React Hooks scenario recreates the Store every time it renders, causing an infinite loop. So using useMemo and setting the dependency to [] causes the code to execute only once during initialization during all render cycles.

For more Hooks in depth, read the close reading complete guide to useEffect.

view

According to the difference between Function Component and Class Component, there are two kinds of processing. This article mainly introduces how to handle Function Component, because THE author recommends using Function Component style.

First, the memo is placed on the outermost layer, which is similar to the PureComponent effect:

return memo(/ * * /);
Copy the code

Then construct a forceUpdate to force the rendering component:

const [, forceUpdate] = useState();
Copy the code

After that, simply use the OBserve-wrapped component. Note two points:

  1. Use what you just createdforceUpdatestoreCalled when modified.
  2. observeInitialization should not be performed because the initialization component will render itself once, and rendering again would be wasteful.

So the author uses scheduler lazy to do both things:

const render = useMemo(
  (a)= >
    observe(Comp, {
      scheduler: (a)= > setState({}),
      lazy: true}), []);return render;
Copy the code

Finally, don’t forget to unlisten on component destruction:

useEffect((a)= > {
  return (a)= >unobserve(render); } []);Copy the code

batch

This is also the classic problem that two-way binding data streams must solve, batch update merge.

Since changing objects triggers rendering, the process is so automated that we don’t even have a chance to tell the tool whether consecutive changes can be combined to trigger a single rendering. In particular, when the For loop modifies variables, the code is almost unusable in some scenarios without incorporating updates.

So Batch was created to solve this problem, giving us the opportunity to control the timing of merge updates:

import React from "react";
import { view, store, batch } from "react-easy-state";

const user = store({ name: "Bob".age: 30 });

function mutateUser() {
  // this makes sure the state changes will cause maximum one re-render,
  // no matter where this function is getting invoked from
  batch((a)= > {
    user.name = "Ann";
    user.age = 32;
  });
}

export default view((a)= > (
  <div>
    name: {user.name}, age: {user.age}
  </div>
));
Copy the code

React-easy-state uses the scheduler module to complete the batch function, with only five lines of core code:

export function batch(fn, ctx, args) {
  let result;
  unstable_batchedUpdates((a)= > (result = fn.apply(ctx, args)));
  return result;
}
Copy the code

Using unstable_batchedUpdates, you can ensure that none of the functions executed within it will trigger an update, i.e. the forceUpdate created earlier will be called but invalid, and will be batch updated when the callback completes.

At the same time, the code is also batch wrapped for setTimeout, setInterval, addEventListener, WebSocket and other public methods, so that these callback functions have their own batch effect.

4. To summarize

The react-easy-State magic has been explained, so hopefully you can understand the principle behind it when working with third-party libraries.

PS: Finally, I don’t recommend using any third-party libraries in Function Component mode at this time, because the official functionality is good enough!

The discussion address is: Intensive reading of React-Easy-State · Issue #144 · 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

special Sponsors

  • DevOps full process platform

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