跳到主要内容

uniapp 陷阱

· 阅读需 3 分钟
Klein
freelancer

Trap 1:v-bind 无法编译到小程序平台

小程序不支持 v-bind='$attrs' 将所有的未声明在组件内部的 props 传递给其他子组件。只能一个个手动声明需要传递的 props 给其他子组件。

Trap 2: <component :is='component' /> 无法编译到小程序平台

小程序不支持动态加载组件,如果在业务组件内部能事先知道可能会有哪些组件被加载,可以预先导入所有的组件。

使用 v-if 指令动态切换(虽然这个方案性能极低,如果导入的组件很多,那么模板代码里充斥着大量的 v-if)。

信息

尝试过使用 render 函数,编写多个 switch case 来实现 <component :is='' /> 但是发现编写的 render 函数在小程序端编译不通过。 因为 render 函数生成的 VNode 并不会编译成 wxml 文件,导致组件会导入失败。 orz...

Trap 3: iconfont 字体文件在 app 真机未能加载

可以用条件编译,在 app 端填写 https:// 前缀,否则会将 //xxxxx.woff 视作为一个 file 协议。

Trap 4: 组件只能在 vue 文件内导入。

这个 bug 通常是开发者希望将多个 vue 组件内用到的大量的相同的组件抽到一个单独的 js 文件,然后再单独导入 JS 文件即可,提高维护性。

例如很多页面用到的相同的组件抽到 foo.js

foo.js
export { default as Wformapplyfortender } from "./wformapplyfortender/index.vue";
export { default as Wformhrsclasschange } from "./wformhrsclasschange/index.vue";
export { default as Wformhrscurstate } from "./wformhrscurstate/index.vue";
export { default as Wformhrsmove } from "./wformhrsmove/index.vue";
export { default as Wformhrsposition } from "./wformhrsposition/index.vue";
export { default as Wformhrsquit } from "./wformhrsquit/index.vue";
export { default as Wformhrsrecruitment } from "./wformhrsrecruitment/index.vue";
export { default as Wformhrsrehiring } from "./wformhrsrehiring/index.vue";
export { default as Wformhrsrenewalcheck } from "./wformhrsrenewalcheck/index.vue";
export { default as Wformhrsrepost } from "./wformhrsrepost/index.vue";
export { default as Wformhrswagesvary } from "./wformhrswagesvary/index.vue";
export { default as Wformproinspection } from "./wformproinspection/index.vue";

然后希望在那些用到 foo.js 文件的组件直接 import 命名导入即可。

bar.vue
<script setup>
import * as FlowComponents from "@/components/foo";
</script>

但是这没有用,会编译失败找到不到对应的组件声明(也就是编译后不会生成这些组件的 JSON 文件)。个人猜测是因为普通 js 脚本不会视作为一个页面模块,所以也就不会将这个脚本内导入的组件编译声明。

滥用 Memo 真的没有问题吗?

· 阅读需 4 分钟
Klein
freelancer

useMemo 是 react 用于缓存计算结果提供的一个 hook,它有点类似于 Vuejs 的 computed 属性。能对计算后的结果起到缓存作用,这样能避免不必要的计算开销。

但是我在公司见过部分的同事完全是在滥用 Memo。无论是什么场景,只要想到值变化后才计算的逻辑就无脑的使用 useMemo。仿佛多重新渲染一次组件会要了整个系统的命。

在使用 useMemo 之前我们应该思考,这个组件 re-render 的开销到底大不大。我们都知道 React 的更新是粗粒度的。

无论是父组件的 state 发生变更,还是自身的 state 发生变更,都会使得组件本身 re-render。

但是大部分场景下 jsx 函数执行是很快的。除非你需要在组件内做出大量的逻辑计算,而且这种计算的耗时会阻塞渲染线程渲染视图。

那才需要真正考虑使用 useMemo 作为最后的性能优化手段。

思考下面这段 jsx 代码

const BestStudentsTable = (props) => {
const list = useMemo(() => {
return props.students
.filter((stu) => stu.age > 18 && stu.score === 100)
.map((item) => ({ ...item, badge: "excellent" }));
}, [props.students]);

return (
<ul>
{list.map((stu) => {
return <li key={stu.id}>{stu.name}</li>;
})}
</ul>
);
};

组件意图将父组件传递的 list 作为 prop,将数据按照业务要求格式化后在渲染。这里用到了 useMemo 将计算结果进行缓存,只有当依赖发生变化时才重新计算。

但是这样是多余的,首先这个函数内部的计算量不大。哪怕上万条数据, 也不会占多大的执行耗时。所以不用 useMemo 情况下,即便父组件其他的 state 产生了新的快照进而触发该组件的 re-render 也不会有什么大的开销。

毕竟执行完毕后,内存就被 GC 自行释放掉了。

const BestStudentsTable = (props) => {
const list = props.students
.filter((stu) => stu.age > 18 && stu.score === 100)
.map((item) => ({ ...item, badge: "excellent" }));

return (
<ul>
{list.map((stu) => {
return <li key={stu.id}>{stu.name}</li>;
})}
</ul>
);
};

useMemo 的优化代价:空间换时间

滥用 useMemo 反而得不偿失。useMemo 会将要记忆的值进行缓存,当 deps 发生改变时,才会重新计算新的结果再暂存。

因为每次组件 re-render,useMemo 会比较旧快照的 deps 和新快照的 deps 是否相同,这里就有了 diff 的开销。

如果一个组件充斥着大量的 useMemo 并承担着简易的计算工作,那么这是愚蠢的行为。