继前一篇 精读《Records & Tuples 提案》,曾经有人在思考这个提案能够帮忙 React 解决哪些问题了,比方这篇 Records & Tuples for React,就提到了许多 React 痛点能够被解决。

其实我比拟担心浏览器是否能将 Records & Tuples 性能优化得足够好,这将是它是否大规模利用,或者说咱们是否释怀把问题交给它解决的最关键因素。本文基于浏览器能够完满优化其性能的前提,所有看起来都挺美妙,咱们无妨基于这个假如,看看 Records & Tuples 提案能解决哪些问题吧!

概述

Records & Tuples Proposal 提案在上一篇精读曾经介绍过了,不相熟能够先去看一下提案语法。

保障不可变性

尽管当初 React 也能用 Immutable 思维开发,但大部分状况无奈保障安全性,比方:

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} />;}

归根结底,咱们不会总应用 freeze 来解冻对象,大部分状况下须要人为保障援用不被批改,其中的潜在危险仍然存在。但应用 Record 示意状态,无论 TS 还是 JS 都会报错,立即阻止问题扩散。

局部代替 useMemo

比方上面的例子,为了保障 apiFilters 援用不变,须要对其 useMemo:

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

但 Record 模式不须要 memo,因为 js 引擎会帮你做相似的事件:

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

用在 useEffect

这段写的很啰嗦,其实和代替 useMemo 差不多,即:

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

你能够把 apiFilters 当做一个援用稳固的原始对象对待,如果它的确变动了,那肯定是值扭转了,所以才会引发取数。如果把下面的 # 号去掉,每次组件刷新都会取数,而实际上都是多余的。

用在 props 属性

能够更不便定义不可变 props 了,而不须要提前 useMemo:

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

将取数后果转化为 Record

这个目前还真做不到,除非用性能十分差的 JSON.stringifydeepEqual,用法如下:

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

即利用 Record 提案的 JSON.parseImmutable 将后端返回值也转化为 Record,这样即使从新查问,但如果返回后果齐全不变,也不会导致重渲染,或者部分变动也只会导致部分重渲染,而目前咱们只能放任这种状况下全量重渲染。

然而这对浏览器实现 Record 的新能优化提出了十分严苛的要求,因为假如后端返回的数据有几十 MB,咱们不晓得这种内置 API 会导致多少的额定开销。

假如浏览器应用十分 Magic 的方法做到了简直零开销,那么咱们应该在任何时候都用 JSON.parseImmutable 解析而不是 JSON.parse

生成查问参数

也是利用了 parseImmutable 办法,让前端能够准确发送申请,而不是每次 qs.parse 生成一个新援用就发一次申请:

// 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,  ]);};

还提到一个乏味的点,即到时候配套工具库可能提供相似 qs.parseRecord(search) 的办法把 JSON.parseImmutable 包装掉,也就是这些生态库想要 “无缝” 接入 Record 提案其实须要做一些 API 革新。

防止循环产生的新援用

即使原始对象援用不变,但咱们写几行代码轻易 .filter 一下援用就变了,而且无论返回后果是否变动,援用都肯定会扭转:

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>));

要防止这个问题就必须 useMemo,但在 Record 提案下不须要:

const AllUsers = #[  #{ id: 1, name: 'Sebastien' },  #{ id: 2, name: 'John' },];const filteredUsers = AllUsers.filter(() => true);AllUsers === filteredUsers;// true

作为 React key

这个想法更乏味,如果 Record 提案保障了援用严格不可变,那咱们齐全能够拿 item 自身作为 key,而不须要任何其余伎俩,这样保护老本会大大降低。

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

当然这仍然建设在浏览器十分高效实现 Record 的前提,假如浏览器采纳 deepEqual 作为初稿实现这个标准,那么下面这坨代码可能导致原本不卡的页面间接解体退出。

TS 反对

兴许到时候 ts 会反对如下形式定义不可变变量:

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} />;};

那咱们就能够真的保障 usersFilters 是不可变的了。因为在目前阶段,编译时 ts 是齐全无奈保障变量援用是否会变动。

优化 css-in-js

采纳 Record 与一般 object 作为 css 属性,对 css-in-js 的区别是什么?

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

因为 css-in-js 框架对新的援用会生成新 className,所以如果不被动保障援用不可变,会导致渲染时 className 始终变动,不仅影响调试也影响性能,而 Record 能够防止这个担心。

精读

总结下来,其实 Record 提案并不是解决之前无奈解决的问题,而是用更简洁的原生语法解决了简单逻辑能力解决的问题。这带来的劣势次要在于 “不容易写出问题代码了”,或者让 Immutable 在 js 语言的上手老本更低了。

当初看下来这个标准有个重大担心点就是性能,而 stage2 并没有对浏览器实现性能提出要求,而是给了一些倡议,并在 stage4 之前给出具体性能优化倡议计划。

其中还是提到了一些具体做法,包含疾速判断虚实,即对数据结构操作时的优化。

疾速判真能够采纳相似 hash-cons 疾速判断构造相等,可能是将一些要害判断信息存在 hash 表中,进而不须要真的对构造进行递归判断。

疾速判假能够通过保护散列表疾速判断,或者我感觉也能够用上数据结构一些经典算法,比方布隆过滤器,就是用在高效疾速判否场景的。

Record 升高了哪些心智累赘

其实如果利用开发都是 hello world 复杂度,那其实 React 也能够很好的符合 immutable,比方咱们给 React 组件传递的 props 都是 boolean、string 或 number:

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

比方下面的例子,齐全不必关怀援用会变动,因为咱们用的原始类型自身援用就不可能变动,比方 18 不可能渐变成 19,如果子组件真的想要 19,那肯定只能创立一个新的,总之就是没方法扭转咱们传递的原始类型。

如果咱们永远在这种环境下开发,那 React 联合 immutable 会十分美好。但好景不长,咱们总是要面对对象、数组的场景,然而这些类型在 js 语法里不属于原始类型,咱们理解到还有 “援用” 这样一种说法,两个值不一样对象可能是 === 全等的。

能够认为,Record 就是把这个顾虑从语法层面打消了,即 #{ a: 1 } 也能够看作像 1819 一样的数字,不可能有人扭转它,所以从语法层面你就会像对 19 这个数字一样释怀 #{ a: 1 } 不会被扭转。

当然这个提案面临的最大问题就是 “如何将领有子结构的类型看作原始类型”,兴许 JS 引擎将它看作一种特地的字符串更贴合其原理,但难点是这又违反了整个语言体系对子结构的默认认知,Box 装箱语法尤其顺当。

总结

看了这篇文章的畅想,React 与 Records & Tulpes 联合的肯定会很好,但前提是浏览器对其性能优化必须与 “援用比照” 大致相同才能够,这也是较为少见,对性能要求如此刻薄的个性,因为如果没有性能的加持,其便捷性将毫无意义。

探讨地址是:精读《Records & Tuples for React》· Issue #385 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

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

版权申明:自在转载-非商用-非衍生-放弃署名(创意共享 3.0 许可证)