background

In 2014Rethinking Web App Development at FacebookAt the conference, Facebook unveiled Flux and React for the first time. At that time, the Facebook team had to solve the question: “How do you keep the product quality high in the rapid iteration of the product?” .

So the Facebook team used Flux and React technology architectures. With Flux and React, the Facebook team increased the predictability of project code so that new engineers could quickly catch up, complete feature development, and fix problems.

MVC problem

The MVC architecture used by the Facebook team before Flux. Under the MVC architecture, the data flow process of the Facebook project is as follows:In this data flow, the View updates the Model directly.

When I saw this architecture diagram, I felt it was not a regular MVC architecture, so I looked it up in Google. The MVC architecture in Wikipedia looks like this:

In a regular MVC architecture, the View does not update the Model directly. So why is Facebook’s data flow so different?

Why does the View update the Model directly

Consider the React component below for an online Demo.

// List and total are two Model layers
// When data changes, the component is notified via forceUpdate to update it
let list = []
let total = 0

function useForceUpdate() {
  const setV = useState({})[1]
  return () = > setV({})
}

export default function App() {
  const forceUpdate = useForceUpdate()
  const handleClick = useCallback(() = > {
    // Use setTimeout to skip the React batch update mechanism
    setTimeout(() = > {
      // Update the list Model, after which the framework automatically triggers forceUpdate
      // Here is a manual trigger to simulate
      // order 1
      list.push(Math.random() >= 0.5 ? "item" : "moon")
      forceUpdate()

      // Update the Total Model, after which the framework automatically triggers forceUpdate
      // Here is a manual trigger to simulate
      // order 4
      total = total + 1
      forceUpdate()
    }, 0)
  }, [forceUpdate])

  useLayoutEffect(() = > {
    // Delete the moon item from the list Model
    if (list.includes("moon")) {
      // order 2
      list = list.filter(it= >it ! = ="moon")
      forceUpdate()

      // order 3
      total = list.length
      forceUpdate()
    }
  }, [list, total, forceUpdate])

  return (
    <div className="App">
      <button onClick={handleClick}>Add an item of data</button>
      <div>Total {total} data</div>
      <div>
        {list.map(it => (
          <div>{it}</div>
        ))}
      </div>
    </div>)}Copy the code

If the data generated after clicking the button is"moon", then the page will appear inconsistent data phenomenon.

The reasons for the inconsistent data are as follows:

  1. Add the data item “moon” to the List Model to trigger the component to rerender.
  2. inuseLayoutEffectUpdated list Model and Total Model.
  3. Go back tohandleClickExecute in functiontotal = total + 1At this time,totallist.lengthThe big one.

This is the data flow when Facebook uses the MVC framework. In the handleClick callback, list.push(…) is expected. Run this command together with total = total + 1. However, unexpectedly, a foot was inserted in the middle, resulting in the Model value has not met the expectation.

There is no Controller in MVC in the above example. Even if we wrapped the code to update the Model as a function and wrapped it in a Controller object to make it fully MVC compliant, it would still be the same as the above example. The Action notifies the Controller to update the Model.

Cascading Updates

After updating the Model in Action1 (see the handleClick callback in the example), the page is rerendered and Action2 (see the useLayoutEffect callback in the example) is triggered.Action1 does not end when Action2 is executed. That is, two actions are processed at the same time.This is what the Facebook team calls Cascading Updates. Updating one Model causes another Model to update.

Flux

In contrast to MVC, Flux names Controller as Dispatcher and Model as Store. Instead of updating the Model directly in the View, the Store is notified of the update by initiating an Action to the Dispatcher.

Looking at the diagram, the only difference between the Flux data flow and the MVC data flow is in naming. Let’s take a look at some more design ideas for the Flux architecture.

Avoid cascading updates

Flux avoids cascading update issues with Dispatcher. An Action must be processed by all stores before the next Action can be initiated, otherwise the Dispatcher will report an error.

In the example above, you need to define handleClick as an Action “addItem”. Both the List Store and the Total Store need to handle this Action. When the button is clicked, if the added data item is “moon”, the code executes as follows:

  1. initiate"addItem"Action, item is “moon”.
  2. List.push (“moon”) ‘updates the List Store and triggers the React component to re-render.
  3. inuseLayoutEffectTo initiate another Action to Dispatcher"removeMoon".
  4. The Dispatcher checks to the previous Action"addItem"No end, error reported.

As you can see from the above flow, Flux avoids cascading updates at run time. If a cascading update problem is not found during development, the problem is exposed in the online environment.

Unidirectional data flow

One-way data flow refers to status updates from the Action to the Dispatcher, to the Store, and finally to the Container View and View component. With one-way data flow, we can easily deduce Store data from Action to View interface.

The flip side of one-way data flow is bidirectional binding, where updating one Store causes another Store to update (also cascading updates). In Flux, multiple stores can have dependencies, but they must have a strict hierarchy and be updated synchronously through the Dispatcher.

Reference: We found that two-way data bindings led to cascading updates, where changing one object led to another object changing, which could also trigger more updates. As applications grew, these cascading updates made it very difficult to predict what would change as the result of one user interaction. When updates can only change data within a single round, the system as a whole becomes more predictable.

Dispatcher API

This code is from Flux Dispatcher.

register(fn)

Register (fn) is the code that updates the Store into the FN. The return value can be used for unregister(id) and waitFor(id[]).

CountryStore.dispatchToken = flightDispatcher.register(function(payload) {
  if (payload.actionType === "country-update") {
    CountryStore.country = payload.selectedCountry
  }
})
Copy the code

unregister(id)

Cancel registration.

waitFor(id[])

When an Action is triggered, the status of Store1 and Store2 are updated, and Store1 depends on the latest status of Store2. At this point, you need to call waitFor in Store1’s update method and let Store2 update first. WaitFor is segmented by Action granularity, and depending on the Action, the dependency between stores may vary. If waitFor has a cyclic dependency, the Dispatcher will report an error.

CityStore.dispatchToken = flightDispatcher.register(function(payload) {
  if (payload.actionType === "country-update") {
    // `CountryStore.country` may not be updated.
    flightDispatcher.waitFor([CountryStore.dispatchToken])
    // `CountryStore.country` is now guaranteed to be updated.

    // Select the default city for the new country
    CityStore.city = getDefaultCityForCountry(CountryStore.country)
  }
})
Copy the code

dispatch(…)

Dispatch event. Dispatching() is an error if dispatching () returns true when isDispatching() returns true. This mechanism is designed to avoid cascading updates.

isDispatching()

Check whether an event is being sent.

conclusion

It’s been seven years since Facebook introduced Flux in 2014. This paper explores the background of Flux architecture, the problems solved, and the solutions. It also explains one-way data flow and cascading updates. Flux is the ancestor of React data management, and Redux is based on it. Understanding it is very helpful for the development of data management.

Recommend more React articles

  1. The React performance optimization | including principle, technique, Demo, tools to use
  2. Talk about useSWR to improve your development – including useSWR design ideas, pros and cons, and best practices
  3. Why does React use the Lane solution
  4. Why does React Scheduler use MessageChannel
  5. Why does “immutable virtual DOM” avoid component rerender
  6. Learn more about the timing of useEffect and useLayoutEffect

, recruiting

The author works in Chengdu – Bytedance – private cloud, and the main technology stack is React + Node.js. Team expansion speed is fast, the technical atmosphere within the group is active. Public cloud Private cloud has just started, and there are many technical challenges.

Interested can resume through this link: job.toutiao.com/s/e69g1rQ

You can also add my wechat “moonball_CXY” to chat and make friends.

Original is not easy, don’t forget to praise oh ❤️