搬砖笔记

子组件监听滚动

React 子组件生命周期中 addEventListener 的时候,需要将第三个参数设置为 true。因为 React 对事件做了合成处理,子组件滚动是捕获事件,父组件滚动是冒泡事件。addEventListener 第三个参数默认为 false,只监听冒泡事件,所以子组件的捕获事件默认监听不到,需要改成 true

useEffect(() => {
window.addEventListener("scroll", (e) => {
const {top, bottom, left, right} = e.target.getBoundingClientRect();
}, true);
}, []);

浮层禁止背景滚动

rxpi-pad 浮动面板有个坑,面板滚动事件和背景滚动事件做了绑定,通过 autoLockScrolling 属性只能全部开启或关闭。一般我们需要让背景禁止滚动,而面板内容可以滚动,只能 hack 一下:

useEffect(() => {
const body = document.body;
if (visible) {
body.style.overflow = "hidden";
} else {
body.style.overflow = "visible";
}
}, [visible]);

在数组指定项插入数组

JavaScript 中的 splice 只能在数组中删除或插入一个元素,而不能插入数组,可借助 apply 赋能。

const arr1 = ['a', 'b', 'c']; 
const arr2 = ['1', '2', '3'];
arr2.unshift(1, 1);
Array.prototype.splice.apply(arr1, arr2);
console.log(arr1) // ["a", "1", "2", "3", "c"]

相当于:

const arr1 = ['a', 'b', 'c']; 
const arr2 = ['1', '2', '3'];
arr1.splice(1, 1, '1', '2', '3’);

标签换行截断

标签组都会设置一个宽度,当标签过多或过长时,最后一项会被“砍掉”,很尴尬。给容器设置一个固定高度和换行,将超出部分顶到第二行隐藏,干掉总比破掉好。

超出截断

超出隐藏

.tags-wrapper {
height: 40rpx; /* 标签高度 */
flex-wrap: wrap;
overflow: hidden;
}

iphoneX 适配

iphoneX 这破手机就是反人类,丑得要死,给前端小哥哥烦的要死,适配 iphoneX 也是老生常谈的事。一次业务中遇到个非主流坑,淘宝端 iphoneX 底部会多出一块白条,其他客户端和机型都没问题,最后师兄一句 height: 100vh 解决。

“这不是 iphoneX 适配标准解法吗???”

“666!!!”

安全距离

在 HTML 头部 viewport 标签中,将 viewport-fit 值设置为 contain。(视口完全包含网页内容,头部位置固定元素将被包含在ios11的安全区域内)

在 CSS 中使用以下布局常数,将布局限定在安全容器内:

  • constant(safe-area-inset-top)
  • constant(safe-area-inset-bottom)
  • constant(safe-area-inset-left)
  • constant(safe-area-inset-right)
.container {
padding: constant(safe-area-inset-top) constant(safe-area-inset-right) constant(safe-area-inset-bottom) constant(safe-area-inset-left);
}

设置了 viewport-fit=cover 后,height: 100% 不能撑满整个视口,高度并不等于视口高度,而是留出了 constant(safe-area-inset-top) 的高度,实际上就是整个页面被往上提了 constant(safe-area-inset-top) 的高度。并且这时 fixed 定位的元素设置 bottom: 0 以后也会距离底部constant(safe-area-inset-top) 的高度,通过给 ** 设置 height: 100vh 可以解决。

useState 同步获取最新状态值

React 中 this.setState() 的第二个参数可以传入一个回调函数用于获取实时更新后的状态值,例如:

this.setState({
username: '兆兆'
}, () => {
console.log(this.state.username);
});

但是 Hooks 中的 useState 官方却把它干掉了,我们可以结合 useState、useRef、useEffect 来模拟一个:

const useCallbackState = (state) => {
const ref = useRef();
const [data, setData] = useState(state);

useEffect(() => {
ref.current && ref.current(data);
}, [data]);

return [data, function (val, callback) {
ref.current = callback;
setData(val);
}];
}

似乎蕴含了点哲学:

  • useState:异步存储数据
  • useRef:同步缓存最新数据
  • useEffect:监测数据状态变更

Taro 点击穿透

在使用 Taro 开发小程序时,被点击穿透坑了。背景如图,在一个评论浮层中点击输入框,点击事件会穿透到底部的浮层上,直接把浮层关了。Taro 文档汇总说明需要使用 e.stopPropagation() 代替小程序中的 catchEvent,然并卵。查看源码后发现,Taro 在编译过程中会直接将 e.stopPropagation() 转换为 catchEvent 打到 wxml 上,这套路…后来发现,在自定义组件中,这条逻辑不生效。解决方案是只需要在自定义组件外层再套一层 View,并加上 e.stopPropagation() 即可。

评论浮层

forEach 异步调用

最近开发的小程序发布评论提审被驳回了,需要接入安全风控中心。整个流程如下:1、首先通过 LeanCloud 接口获取获取存储在 BaaS 上的评论数据。2、遍历评论列表,将每条评论内容上传至云函数。3、通过审核接口获取内容敏感程度,剔除不正能量的内容。过程中多次使用 forEach 遍历,并且在遍历过程中调用了异步接口。forEach 本身是同步的,但是在遍历中如果调用了异步代码,并不会阻塞进程,很有可能还没拿到结果就执行了后续的代码。有如下解决方案:

内部同步,外部异步

comments.forEach(async (item, index) =>{
await secCheck(item);
filterRes(item);
return item;
})

内部异步,外部同步

const asyncFunc = [];
comments.forEach((item, index) =>{
asyncFun.push(secCheck(item));
filterRes(item);
})
Promise.all(asyncFun).then((res) => {
return res;
});

内部外部都同步

Promise.all(
comments.map( item =>{
return new Promise(async (resolve, reject) =>{
await secCheck(item);
filterRes(item);
resolve();
})
})
).then((res) =>{
return res;
})

常用 Hooks 封装

缓存状态

获取上一次执行的状态。

const usePrevious = (value: any) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}

批量设置状态

this.setState() 一样批量设置状态。

const useSet = (initState: any) => {
return useReducer((state: any, newState: any) => {
return { ...state, ...newState };
}, initState);
}

节流

const useThrottle = (fn: any, delay: number, dep = []) => {
const { current } = useRef({ fn, timer: null });
useEffect(
function () {
current.fn = fn;
},
[fn],
);

return useCallback(function f(...args: any) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer;
}, delay);
current.fn(...args);
}
}, dep);
}
查看评论