Following the previous intensive reading "Records & Tuples Proposal" , some people are already thinking about what problems this proposal can help React to solve. For example, this Records & Tuples for React mentioned that many of the pain points of React can be solved.
In fact, I am more worried about whether the browser can optimize the performance of Records & Tuples well enough. This will be the most critical factor for whether it can be applied on a large scale, or whether we can trust it to solve the problem. This article is based on the premise that the browser can perfectly optimize its performance. Everything looks pretty good. Based on this assumption, let's see what problems the Records & Tuples proposal can solve!
Overview
Records & Tuples Proposal The proposal has already been introduced in the previous intensive reading. If you are not familiar with it, you can go to the proposal grammar first.
Guaranteed immutability
Although React can also be developed with Immutable ideas, 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 manually ensure that the reference is not modified, and the potential risks still exist. But if you use Record to represent status, both TS and JS will report an error, which immediately prevents the problem from spreading.
Partially replace useMemo
For example, in the following example, in order to keep the apiFilters
unchanged, it needs to be useMemo
:
const apiFilters = useMemo(
() => ({ userFilter, companyFilter }),
[userFilter, companyFilter],
);
const { apiData, loading } = useApiData(apiFilters);
But Record mode does not need memo, because the js engine will help you do similar things:
const {apiData,loading} = useApiData(#{ userFilter, companyFilter })
Used in useEffect
This paragraph is very verbose, but it is almost the same as replacing useMemo, namely:
const apiFilters = #{ userFilter, companyFilter };
useEffect(() => {
fetchApiData(apiFilters).then(setApiDataInState);
}, [apiFilters]);
You can treat apiFilters
as a primitive object with stable references. If it does change, then the value must have changed, so the fetch is triggered. If the #
above is removed, the number will be taken every time the component is refreshed, but it is actually redundant.
Used in props attribute
It is easier to define immutable props without usingMemo in advance:
<ExpensiveChild someData={#{ attr1: 'abc', attr2: 'def' }} />;
Convert the fetch result into Record
This is really impossible at present, unless you use the very poor performance JSON.stringify
or deepEqual
, the usage is as follows:
const fetchUserAndCompany = async () => {
const response = await fetch(
`https://myBackend.com/userAndCompany`,
);
return JSON.parseImmutable(await response.text());
};
That is, the 061d39c1fa8125 of the Record proposal is JSON.parseImmutable
convert the back-end return value to Record, so that even if the query is re-queried, if the returned result is completely unchanged, it will not cause re-rendering, or local changes will only cause local re-rendering, and currently we It can only be re-rendered in full in this case.
However, this puts very strict requirements on the new performance optimization of the browser to implement Record, because assuming that the data returned by the backend 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, then we should use JSON.parseImmutable
instead of JSON.parse
at all times.
Generate query parameters
It also uses the parseImmutable
method to allow the front end to send requests accurately, instead of sending a request every time qs.parse
generates a new reference:
// 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,
]);
};
Also mentioned an interesting point, namely that time supporting the tool library could provide similar qs.parseRecord(search)
method of the JSON.parseImmutable
packed away, that is, these eco-library wants "seamless" access Record API proposal actually need to do some renovation.
Avoid new references generated by cycles
Even if the original object reference does not change, but we write a few lines of code to .filter
reference will change, and no matter whether the returned result changes or not, the reference will definitely change:
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
is required, 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 a React key
This idea is more interesting. If the Record proposal guarantees that the reference is strictly immutable, then we can use item
itself as key
without any other means, so the maintenance cost will be greatly reduced.
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 cause the page that is not stuck to crash and exit.
TS support
Maybe ts will support the definition of immutable variables in the following ways:
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
is immutable. Because at the current stage, ts is completely unable to guarantee whether variable references will change at compile time.
Optimize css-in-js
What is the difference between css-in-js using Record and ordinary object as css attributes?
const Component = () => (
<div
css={#{
backgroundColor: 'hotpink',
}}
>
This has a hotpink background.
</div>
);
Since the css-in-js framework generates a new className for new references, if the reference is not actively guaranteed to be immutable, the className will always change during rendering, which not only affects debugging but also affects performance, and Record can avoid this worry.
intensive reading
To sum up, in fact, the Record proposal does not solve problems that could not be solved before, but uses a more concise native syntax to solve problems that can only be solved by complex logic. The main advantage of this is that "it is not easy to write problem code", or the cost of getting Immutable in js language is lower.
Looking at this specification now, there is a serious concern about performance, and stage2 does not require the browser to achieve performance, but gives some suggestions, and gives specific performance optimization suggestions before stage4.
Some specific methods are mentioned, including quick judgment of true and false, that is, optimization of data structure operations.
Fast judgment can be similar to hash-cons to quickly judge that the structure is equal. It may be that some key judgment information is stored in the hash table, and there is no need to really make a recursive judgment on the structure.
Fast false judgment can be quickly judged by maintaining a hash table, or I think some classic algorithms of data structure can also be used, such as bloom filters, which are used in efficient and fast false judgment scenarios.
What mental burden does Record reduce
In fact, if application development is of hello world complexity, then React can also fit immutable well. For example, the props we pass to React components are all boolean, string, or number:
<ExpensiveChild userName="nick" age={18} isAdmin />;
For example, in the above example, you don’t need to worry about the reference change at all, because the reference of the original type itself cannot be changed. For example, 18
cannot be mutated to 19
. If the child component really wants 19
, then it must only create a new one. Yes, in short, there is no way to change the original type we passed.
If we always develop in this environment, React combined with immutable will be very beautiful. But the good times are not long. We always have to face the scene of objects and arrays. However, these types are not primitive types in js grammar. We have learned that there is also a saying of "reference". Two objects with different values may be ===
congruent.
Can be considered, Record is to eliminate this concern from the grammatical level, namely #{ a: 1 }
can also be seen as 18
, 19
the same numbers, it was impossible to change it, so you will be like from the grammatical level of 19
this figure as assured #{ a: 1 }
not Will be changed.
Of course, the biggest problem facing this proposal is "how to treat the type with substructure as a primitive type". Maybe the JS engine treats it as a special string more in line with its principle, but the difficulty is that it violates the entire language. The system's default perception of substructures, Box packing syntax is particularly awkward.
Summarize
After reading this article, I think that 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". This is also relatively rare, and the performance requirements are so demanding. The characteristic, because if there is no performance blessing, its convenience will be meaningless.
The discussion address is: Intensive Reading "Records & Tuples for React" · Issue #385 · dt-fe/weekly
If you want to participate in the discussion, please click here , there are new topics every week, weekend or Monday release. Front-end intensive reading-to help you filter reliable content.
Follow front-end intensive reading WeChat public
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright notice: Freely reprinted-non-commercial-non-derivative-keep the signature ( Creative Commons 3.0 License )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。