This is the author’s reflection on the whole framework of React at the beginning of its design. Personally, I was inspired by it.



reactjs/react-basic


Here is my translation

In this article, I tried to explain my understanding of the React model and how we used deductive derivation to help us get the final design.

Of course, there are many preconditions here that are debatable, and the examples in this article are flawed and buggy. But we’re formally denormalizing it. If you have a better idea to formalize it, feel free to send us a PR. Even without much library documentation and detail, a simple to complex deduction should make sense.

The final React implementation required a lot of pragmatic solutions, incremental iterations, algorithm optimizations, legacy code, debugging tools — all of which were updated too quickly if they were useful. So the react implementation is hard to deduce.

So I wanted to show you a simple conceptual model that I could stand on.

The Transformation (transform)

One of the core assumptions of React is that the UI is a mapping of data to attempts. The same input always leads to the same output.

function NameBox(name) { return { fontWeight: 'bold', labelContent: name }; } 'Sebastian Markbage '-> {fontWeight: 'bold', labelContent: 'Sebastian Markbage'};Copy the code

abstract

You can’t use complex UI in a single function. You need to abstract the interface into reusable parts first, and each part does not give away its implementation details (as a function). So you can call one function from another.

function FancyUserBox(user) { return { borderStyle: '1px solid blue', childContent: [ 'Name: ', NameBox(user.firstName + ' ' + user.lastName) ] }; } {firstName: 'Sebastian', lastName: 'Markbage'} -> {borderStyle: '1px solid blue', childContent: ['Name: ', {fontWeight: 'bold', labelContent: 'Sebastian Markbage '}]};Copy the code

This code is nested with FancyUserBox() and NameBox()

Composition (= Composition)

To achieve truly reusable features, it is not enough to simply reuse leaf nodes and construct new containers on top of them. You need to construct a combination of abstract containers underneath the abstract container. Composition is the merging of multiple abstractions into a new one.

function FancyBox(children) {
  return {
    borderStyle: '1px solid blue',
    children: children
  };
}

function UserBox(user) {
  return FancyBox([
    'Name: ',
    NameBox(user.firstName + ' ' + user.lastName)
  ]);
}
Copy the code

Small editor: In fact, the composition here refers to multiple components (arrays) can be rendered as a whole; By abstraction, we mean components.

State of affairs

The UI does not simply copy the business logic and state on the server side; there are many states for a particular projection. For example, text entered in a text box will not be projected to other tabs or devices. The scroll position is not multiplexed across multiple projections. Projection is the process by which state flows through component functions and is rendered. These states are too special to reuse from the server for a particular type of projection.

The data models we tend to choose are immutable, we concatenate functions and assume that the state-updating function is at the top.

function FancyNameBox(user, likes, onClick) { return FancyBox([ 'Name: ', NameBox(user.firstName + ' ' + user.lastName), 'Likes: ', LikeBox(likes), LikeButton(onClick) ]); } // Implementation Details var likes = 0; function addOneMoreLike() { likes++; rerender(); } // Init FancyNameBox({firstName: 'Sebastian', lastName: 'Markbage'}, likes, addOneMoreLike);Copy the code

NOTE: This example has side effects when updating the status. My real idea is that during the update process the function returns to the next state. Here is the simplest way to illustrate the principle, and we will update this example in the future.

Memoization (memory)

Calling a pure function over and over again is a waste of performance. We can cache the input and result of this calculation. So you don’t have to double count.

function memoize(fn) {
  var cachedArg;
  var cachedResult;
  return function(arg) {
    if (cachedArg === arg) {
      return cachedResult;
    }
    cachedArg = arg;
    cachedResult = fn(arg);
    return cachedResult;
  };
}

var MemoizedNameBox = memoize(NameBox);

function NameAndAgeBox(user, currentTime) {
  return FancyBox([
    'Name: ',
    MemoizedNameBox(user.firstName + ' ' + user.lastName),
    'Age in milliseconds: ',
    currentTime - user.dateOfBirth
  ]);
}
Copy the code

Lists

Many UIs are lists — each item in a list has different data, which makes for a natural structure.

To manage the state of each element, we can create a Map to store the state of each element.

function UserList(users, likesPerUser, updateUserLikes) {
  return users.map(user => FancyNameBox(
    user,
    likesPerUser.get(user.id),
    () => updateUserLikes(user.id, likesPerUser.get(user.id) + 1)
  ));
}

var likesPerUser = new Map();
function updateUserLikes(id, likeCount) {
  likesPerUser.set(id, likeCount);
  rerender();
}

UserList(data.users, likesPerUser, updateUserLikes);
Copy the code

NOTE: We passed several different parameters to FancyNameBox, which conflicts with the memory module because we can only remember one value at a time. We’ll talk about that later.

Continuations

Unfortunately, there are too many lists of lists in the UI, and you end up writing a lot of boilerplate code.

We can move some of the repetitive code out of the critical business by delaying the execution of functions. For example, we can use the bind in JavaScript method. This way we don’t encounter duplicate code when passing state to the core function from the outside.

This approach doesn’t eliminate repetitive code, but at least it moves it out of the core business logic.

function FancyUserList(users) { return FancyBox( UserList.bind(null, users) ); } const box = FancyUserList(data.users); const resolvedChildren = box.children(likesPerUser, updateUserLikes); const resolvedBox = { ... box, children: resolvedChildren };Copy the code

State Map

We’ve known for a long time that if we see a repetitive pattern, we can use a composite approach to avoid repeating the pattern. We can move the logic of extracting and passing state into an underlying function.

function FancyBoxWithState(
  children,
  stateMap,
  updateState
) {
  return FancyBox(
    children.map(child => child.continuation(
      stateMap.get(child.key),
      updateState
    ))
  );
}

function UserList(users) {
  return users.map(user => {
    continuation: FancyNameBox.bind(null, user),
    key: user.id
  });
}

function FancyUserList(users) {
  return FancyBoxWithState.bind(null,
    UserList(users)
  );
}

const continuation = FancyUserList(data.users);
continuation(likesPerUser, updateUserLikes);
Copy the code

Memoization Map

When we try to remember multiple items in a list, memory becomes more difficult. You need to find some sophisticated caching algorithm to balance memory usage and frequency.

Fortunately, the UI is relatively stable in the same location. The same position in the tree always gives the same result. This makes trees an effective strategy for memory.

So we can use the same technique to cache composition functions.

function memoize(fn) {
  return function(arg, memoizationCache) {
    if (memoizationCache.arg === arg) {
      return memoizationCache.result;
    }
    const result = fn(arg);
    memoizationCache.arg = arg;
    memoizationCache.result = result;
    return result;
  };
}

function FancyBoxWithState(
  children,
  stateMap,
  updateState,
  memoizationCache
) {
  return FancyBox(
    children.map(child => child.continuation(
      stateMap.get(child.key),
      updateState,
      memoizationCache.get(child.key)
    ))
  );
}

const MemoizedFancyNameBox = memoize(FancyNameBox);
Copy the code

Algebraic Effects

React looks like a PITA pie, layer upon layer, and a very small value is passed in this way. So we need a short circuit between the layers — “context”

Sometimes data dependencies don’t follow abstract trees very well, for example, in layout algorithms you need to know the size of the child elements in advance.

The following example is a bit beyond the scope of our current discussion. I’m using Algebraic Effects, ECMAScript. If you’re familiar with functional programming, they’re avoiding the intermediate ceremony by Monads.

function ThemeBorderColorRequest() { }

function FancyBox(children) {
  const color = raise new ThemeBorderColorRequest();
  return {
    borderWidth: '1px',
    borderColor: color,
    children: children
  };
}

function BlueTheme(children) {
  return try {
    children();
  } catch effect ThemeBorderColorRequest -> [, continuation] {
    continuation('blue');
  }
}

function App(data) {
  return BlueTheme(
    FancyUserList.bind(null, data.users)
  );
}
Copy the code

Comment on:

  1. Big coincidence does not work – by the author so said, complex things are actually very simple.
  2. A journey of a thousand miles begins with a single step. If you learn simple things well, you can do great things