关于javascript:精读Records-Tuples-for-React

3次阅读

共计 5277 个字符,预计需要花费 14 分钟才能阅读完成。

继前一篇 精读《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 许可证)

正文完
 0