Intensive reading of Records & Tuples for React

Following the previous article Intensive reading of records & tuples proposal , some people have been thinking about what problems this proposal can help React solve, such as this article Records & Tuples for React , it was mentioned that many React pain points can be solved.

In fact, I'm worried about whether the browser can optimize the performance of records & tuples well enough, which will be the most critical factor for its large-scale application, or whether we can rest assured that the problem will be solved by it. This article is based on the premise that the browser can perfectly optimize its performance. Everything looks beautiful. We might as well take a look at what problems the records & tuples proposal can solve based on this assumption!

summary

Records & Tuples Proposal The proposal has been introduced in the last intensive reading. If you are not familiar with it, you can take a look at the proposal grammar first.

Guaranteed non variability

Although React can also be developed with Immutable idea, its security cannot be guaranteed in most cases, such as:

const Hello = ({ profile }) => {
  // prop mutation: throws TypeError
  profile.name = 'Sebastien updated';
  return <p>Hello {profile.name}</p>;
};

function App() {
  const [profile, setProfile] = React.useState(#{
    name: 'Sebastien',
  });
  // state mutation: throws TypeError
  profile.name = 'Sebastien updated';
  return <Hello profile={profile} />;
}

In the final analysis, we will not always use freeze to freeze objects. In most cases, we need to artificially ensure that references are not modified, and the potential risks still exist. However, if the Record is used to indicate the status, both TS and JS will report an error, which will immediately prevent the problem from spreading.

Partially replace useMemo

For example, in the following example, in order to ensure that the apiFilters reference remains unchanged, its useMemo needs to be modified:

const apiFilters = useMemo(
  () => ({ userFilter, companyFilter }),
  [userFilter, companyFilter],
);
const { apiData, loading } = useApiData(apiFilters);

However, the Record mode does not require memo, because the js engine will help you do similar things:

const {apiData,loading} = useApiData(#{ userFilter, companyFilter })

Used in useEffect

This paragraph is very wordy. In fact, it is similar to replacing useMemo, that is:

const apiFilters = #{ userFilter, companyFilter };

useEffect(() => {
  fetchApiData(apiFilters).then(setApiDataInState);
}, [apiFilters]);

You can treat apiFilters as an original object with stable reference. If it does change, the value must change, so it will trigger data retrieval. If the # number above is removed, the data will be retrieved every time the component is refreshed, which is actually redundant.

Used in props attribute

It is more convenient to define immutable props without using memo in advance:

<ExpensiveChild someData={#{ attr1: 'abc', attr2: 'def' }} />;

Convert the retrieval result to Record

This is really impossible at present, unless you use JSON with very poor performance Stringify or deepEqual, as follows:

const fetchUserAndCompany = async () => {
  const response = await fetch(
    `https://myBackend.com/userAndCompany`,
  );
  return JSON.parseImmutable(await response.text());
};

That is, using JSON proposed by Record Parseimmutable also converts the back-end return value into Record. In this way, even if the query is re queried, if the return result is completely unchanged, it will not lead to re rendering, or local changes will only lead to local re rendering. At present, we can only allow full re rendering in this case.

However, this puts forward very strict requirements for the browser to implement the new performance optimization of Record, because assuming that the data returned by the back end is tens of MB, we don't know how much additional overhead this built-in API will cause.

Assuming that the browser uses a very Magic method to achieve almost zero overhead, we should use JSON at any time Parseimmutable parses instead of JSON parse.

Generate query parameters

The parseImmutable method is also used, so that the front end can accurately send requests instead of QS every time Parse generates a new reference and sends a request:

// This is a non-performant, but working solution.
// Lib authors should provide a method such as qs.parseRecord(search)
const parseQueryStringAsRecord = (search) => {
  const queryStringObject = qs.parse(search);
  // Note: the Record(obj) conversion function is not recursive
  // There's a recursive conversion method here:
  // https://tc39.es/proposal-record-tuple/cookbook/index.html
  return JSON.parseImmutable(
    JSON.stringify(queryStringObject),
  );
};

const useQueryStringRecord = () => {
  const { search } = useLocation();
  return useMemo(() => parseQueryStringAsRecord(search), [
    search,
  ]);
};

An interesting point was also mentioned that the supporting tool library may provide similar QS The method of parserecord (search) converts JSON Parseimmutable is packaged, that is, if these ecosystems want to "seamlessly" access the Record proposal, they actually need to make some API modifications.

Avoid new references from loops

Even if the original object reference remains unchanged, we can write a few lines of code casually The reference of the filter will change at once, and the reference will change no matter whether the returned result changes:

const AllUsers = [
  { id: 1, name: 'Sebastien' },
  { id: 2, name: 'John' },
];

const Parent = () => {
  const userIdsToHide = useUserIdsToHide();
  const users = AllUsers.filter(
    (user) => !userIdsToHide.includes(user.id),
  );
  return <UserList users={users} />;
};

const UserList = React.memo(({ users }) => (
  <ul>
    {users.map((user) => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
));

To avoid this problem, useMemo must be used, but it is not required under the Record proposal:

const AllUsers = #[
  #{ id: 1, name: 'Sebastien' },
  #{ id: 2, name: 'John' },
];

const filteredUsers = AllUsers.filter(() => true);
AllUsers === filteredUsers;
// true

As React key

This idea is more interesting. If the Record proposal ensures that the reference is strictly immutable, we can take the item itself as the key without any other means, which will greatly reduce the maintenance cost.

const list = #[
  #{ country: 'FR', localPhoneNumber: '111111' },
  #{ country: 'FR', localPhoneNumber: '222222' },
  #{ country: 'US', localPhoneNumber: '111111' },
];
<>
  {list.map((item) => (
    <Item key={item} item={item} />
  ))}
</>

Of course, this is still based on the premise that the browser implements Record very efficiently. Assuming that the browser uses deepEqual as the first draft to implement this specification, the above code may lead to the direct crash and exit of the page that is not stuck.

TS support

Maybe ts will support the following ways to define immutable variables:

const UsersPageContent = ({
  usersFilters,
}: {
  usersFilters: #{nameFilter: string, ageFilter: string}
}) => {
  const [users, setUsers] = useState([]);
  // poor-man's fetch
  useEffect(() => {
    fetchUsers(usersFilters).then(setUsers);
  }, [usersFilters]);
  return <Users users={users} />;
};

Then we can really guarantee that usersFilters are immutable. Because at this stage, the compile time ts is completely unable to guarantee whether the variable reference will change.

Optimize CSS in JS

Using Record and ordinary object as css attributes, what is the difference between css in JS?

const Component = () => (
  <div
    css={#{
      backgroundColor: 'hotpink',
    }}
  >
    This has a hotpink background.
  </div>
);

Because the CSS in JS framework generates new classnames for new references, if you do not actively ensure that the references are immutable, the classnames will always change during rendering, which will not only affect debugging but also affect performance. Record can avoid this worry.

intensive reading

To sum up, the Record proposal is not to solve problems that cannot be solved before, but to solve problems that can only be solved by complex logic with more concise native syntax. The advantage of this is that it is "not easy to write problem code", or the cost of Immutable in js language is lower.

Now it seems that this specification has a serious concern about performance, while stage2 does not put forward requirements for browser implementation performance, but gives some suggestions, and gives specific performance optimization suggestions before stage4.

Some specific methods are also mentioned, including rapid judgment of true and false, that is, the optimization of data structure operation.

Fast truth judgment can be similar to hash cons fast judgment, and the structure is equal. It may be that some key judgment information is stored in the hash table, so it is not necessary to make recursive judgment on the structure.

Fast false judgment can be quickly judged by maintaining hash table, or I think some classical algorithms of data structure, such as Bloom filter, can also be used in efficient and fast false judgment scenarios.

What mental burden does Record reduce

In fact, if the application development is of hello world complexity, React can also fit immutable well. For example, the props we pass to React components are boolean, string or number:

<ExpensiveChild userName="nick" age={18} isAdmin />;

For example, in the above example, we don't care about the change of the reference at all, because the reference of the original type we use can't change. For example, 18 can't mutate into 19. If the sub component really wants 19, it must only create a new one. In short, there is no way to change the original type we pass.

If we always develop in this environment, React combined with immutable will be very wonderful. However, the good times are not long. We always have to face the scene of objects and arrays. However, these types do not belong to the original types in js syntax. We have learned that there is a saying of "reference". Two objects with different values may be = = = congruent.

It can be considered that Record eliminates this concern from the grammatical level, that is, #{a: 1} can also be regarded as a number like 18 and 19, and it is impossible for someone to change it. Therefore, from the grammatical level, you will be as assured that #{a: 1} will not be changed as you are about the number 19.

Of course, the biggest problem faced by this proposal is "how to regard the type with substructure as the original type". Perhaps the JS engine regards it as a special string, which is more in line with its principle, but the difficulty is that it violates the default cognition of the whole language system on substructure, and the Box packing syntax is particularly awkward.

summary

According to the imagination of this article, the combination of React and records & tulpes will be very good, but the premise is that the browser's performance optimization must be roughly the same as the "reference comparison", which is also a rare feature with such strict performance requirements, because its convenience will be meaningless without the support of performance.

The discussion address is: Intensive reading of records & tuples for react · Issue #385 · DT Fe / weekly

If you want to participate in the discussion, please click here , there are new themes every week, published on weekends or Mondays. Front end intensive reading - help you filter reliable content.

Focus on front-end intensive reading WeChat official account

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

Copyright notice: free reproduction - non commercial - non derivative - keep signature( Creative sharing 3.0 License)

Keywords: Javascript Front-end

Added by Unforgiven on Tue, 04 Jan 2022 19:38:38 +0200