• Surprising polymorphism in React applications
  • Originally by Benedikt Meurer
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Candy Zheng
  • Proofreader: goldEli, old professor

React application performance glitch — amazing polymorphism

Modern Web applications based on the React framework often manage their state through immutable data structures. For example, use the well-known Redux state management tool. This model has many advantages and is gaining popularity even outside the React/Redux ecosystem.

The core of this mechanism is called reducers. They are functions that map an application from one state to the next based on a specific mapping action, such as a response to a user interaction. With this core abstraction, complex states and reducers can be composed of simpler states and reducers, which makes it easy to unit test the isolation of parts of code. Let’s take a closer look at the examples in the Redux documentation.

const todo = (state = {}, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        id: action.id,
        text: action.text,
        completed: false
      }
    case 'TOGGLE_TODO':
      if(state.id ! == action.id) {return state
      }

      returnObject.assign({}, state, { completed: ! state.completed }) default:return state
  }
}
Copy the code

The Reducer, named Todo, maps an existing state to a new state based on the given action. This state is just a normal JavaScript object. If we look at this code from a performance standpoint, it seems to comply with singlet rules, such as keeping the shape of the object (key/value) consistent.

const s1 = todo({}, {
  type: 'ADD_TODO',
  id: 1,
  text: "Finish blog post"
});

const s2 = todo(s1, {
  type: 'TOGGLE_TODO',
  id: 1
});

function render(state) {
  return state.id + ":" + state.text;
}

render(s1);
render(s2);
render(s1);
render(s2);
Copy the code

On the surface, the access properties in Render should be singleton, such that the state object should have the same object shape – map or hidden class form in V8 concepts – whenever, S1 and S2 both have id, text, and Completed attributes and they are ordered. However, when we run the code through D8 and trace the ICs of the code, we see that the object shape of the render is different, and the fetch of state.id and state.text becomes polymorphic:

So the question is, where does this polymorphism come from? It does look the same on the surface but there are minor differences, starting with how V8 handles object literals. In V8, every object literal (e.g. {a:va,… ,z: VB}) defines an initial map (map refers to the shape of the object in V8) that will be later migrated to other forms of map as properties change. So, if you use an empty object literal {}, the root of the transition tree is a map with no attributes, but if you use an object literal of the form {id:id, text:text, completed:completed}, The root of the transition tree will be one that contains these three properties. Let’s look at a simplified example:

let a = {x:1, y:2, z:3};

let b = {};
b.x = 1;
b.y = 2;
b.z = 3;

console.log("a is", a);
console.log("b is", b);
console.log("a and b have same map:", %HaveSameMap(a, b));
Copy the code

You can add the –allow-natives-syntax run to the node. js runtime command (enable to apply the internal method %HaveSameMap) as an example:

Although objects A and B look the same — they have the same type of properties in turn — their map structure is different. The reason is that they have different transition trees, which can be explained by the following example:

So when different object literals are assigned during object initialization, the transition tree is different, the map is different, and polymorphism is implicitly formed. This also applies to the popular object. assign, for example:

let a = {x:1, y:2, z:3};

let b = Object.assign({}, a);

console.log("a is", a);
console.log("b is", b);
console.log("a and b have same map:", %HaveSameMap(a, b));
Copy the code

This code still produces a different map, because Object B is created from an empty Object ({} literal), and attributes are assigned only after object. assign.

This also shows that you run into this polymorphism problem when you use the spread (extended operator) to process attributes and syntactically translate them through Babel. Because Babel (and probably other translators as well) uses object.assign for the spread syntax.

One way to avoid this problem is to always use object. assign, and all objects start with an empty Object literal. But this can also lead to performance bottlenecks in the state management logic:

let a = Object.assign({}, {x:1, y:2, z:3});

let b = Object.assign({}, a);

console.log("a is", a);
console.log("b is", b);
console.log("a and b have same map:", %HaveSameMap(a, b));
Copy the code

However, all is not lost when some code becomes polymorphic. For the most part, it doesn’t matter whether you’re monolithic or polymorphic. You should think about the value of optimization when deciding to optimize.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.