0%

本文翻译自社区medium,作者Sandro Dolidze

Introduction

React Hooks,不像Class Components那样,React Hooks提供了一种更low-level的优化和构建应用的方式,当然同时,这也会带来隐形的bug和资源泄露的问题。
这篇文章中,我列举了12个例子来说明这些常见的问题和怎么去修复他们

我在实习的这半个月接到过开发进度条的需求,用setInterVal+get来实现,由于对于Hooks的理解不深,使用也不甚熟练,写出了很多bug,正好看到这篇文章,分享出来,也是对知识的巩固

Case Study:Implementing Interval

先看一下这个例子,设置一个从0开始的计时器,然后每隔500ms加1,一共有三个button,分别是:开始||停止||清除

<!-- more -->

level0 Hello World的水平

1
2
3
4
5
6
7
8
9
10
11
export default function Level00() {
console.log('renderLevel00');
const [count, setCount] = useState(0);
return (
<div>
count => {count}
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
);
}

很好理解,用一个useState来生成一个State,用户点击来控制count+和-

level1 setInterval(这就是我写的bug版本。。)

1
2
3
4
5
6
7
8
export default function Level01() {
console.log('renderLevel01');
const [count, setCount] = useState(0);
setInterval(() => {
setCount(count + 1);
}, 500);
return <div>count => {count}</div>;
}

从代码逻辑上来看是每隔500ms给count增加1,但是呢这个代码有一个很严重的问题那就是会造成资源泄露,并且这种实现方式也是错误的。很容易就会造成浏览器页面崩溃,因为上面的函数会在页面每次重渲染的时候触发,组件与此同时也会在每次渲染触发的时候创建一个新的setInterval。

Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React’s render phase). Doing so will lead to confusing bugs and inconsistencies in the UI.

处理这种副作用我们要用到useEffect

level2 useEffect

1
2
3
4
5
6
7
8
9
10
export default function Level02() {
console.log('renderLevel02');
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 500);
});
return <div>Level 2: count => {count}</div>;
}

这次我们使用useEffect,大多数的的副作用都在useEffect中进行。但是上面的方法也会造成资源泄露。useEffect会在每次渲染完成后运行,所以新的setInterval会在每次count发生变化的时候被创建

level3 run only once

1
2
3
4
5
6
7
8
9
10
export default function Level03() {
console.log('renderLevel03');
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 300);
}, []);
return <div>count => {count}</div>;
}

给useEffect加上参数[],这样useEffect只会被在组件mount后调用一次,但是这样的话,count只会从0-1
另外一个问题就是,useEffect没有清除定时器

level4 cleanup

1
2
3
4
5
6
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 300);
return () => clearInterval(interval);
}, []);

这种方法在return里清除了定时器,避免了资源泄露,但是存在的问题依然和之前的那一种方式一样的,count只会从0-1

level5 use Count as dependency

1
2
3
4
5
6
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 500);
return () => clearInterval(interval);
}, [count]);

这次我们把count当做依赖传给useEffect的第二个参数,在这个例子中,useEffect会在mount后和每次count发生改变的时候调用,同时,return清除函数会在count发生变化的时候清除上一次的资源。

这次搞对了,没有bug,但是也有一点误区。setInterval每隔500ms就会创建/销毁。每个setInterval都只会被调用一次

level6 setTimeout

1
2
3
4
5
6
useEffect(() => {
const timeout = setTimeout(() => {
setCount(count + 1);
}, 500);
return () => clearTimeout(timeout);
}, [count]);

和level5的一样

level7 functional updates for useState

1
2
3
4
5
6
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 500);
return () => clearInterval(interval);
}, []);

在之前的例子中,我们在每次count发生变化的时候运行useEffect,我们每次都需要获取到最新的count值。

但是useState提供了一个API可以让我们在不用获取到现在的值的情况下就可以更新值,我们可以直接给useState传递一个函数就可以做到。

现在就比较完美了,我们仅仅使用了一个setInterval就做到了这一点,return后的clearInterval清除函数也就会在组件销毁时运行

level8 local vairable(这就是我写的那一坨屎)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default function Level08() {
console.log('renderLevel08');
const [count, setCount] = useState(0);
let interval = null;
const start = () => {
interval = setInterval(() => {
setCount(c => c + 1);
}, 500);
};
const stop = () => {
clearInterval(interval);
};
return (
<div>
count => {count}
<button onClick={start}>start</button>
<button onClick={stop}>stop</button>
</div>
);
}

我们添加了start和stop的按钮,但是这个程序是不正确的,stop按钮并不起作用。每次渲染的过程中就会创建出一个新的setInterval引用,所以,stop的引用就会变成null

level9 useRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default function Level09() {
console.log('renderLevel09');
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 500);
};
const stop = () => {
clearInterval(intervalRef.current);
};
return (
<div>
count => {count}
<button onClick={start}>start</button>
<button onClick={stop}>stop</button>
</div>
);
}

useRef就像是go-to hooks一样,如果你需要一个可变的变量,你可以用useRef获取。和局部变量不同的是,React确保了useRef在每次渲染的过程中返回的是相同的引用

这下代码看起来没问题了,但是还有一个隐形的bug,如果start被多次重复调用后,setInterval也会被重复调用,造成内存泄露

level10 useCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export default function Level10() {
console.log('renderLevel10');
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const start = () => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 500);
};
const stop = () => {
if (intervalRef.current === null) {
return;
}
clearInterval(intervalRef.current);
intervalRef.current = null;
};
return (
<div>
count => {count}
<button onClick={start}>start</button>
<button onClick={stop}>stop</button>
</div>
);
}

在Interval已经开始的时候,我们就不去触发了。当然cleanrInterval(null)也不会产生任何bug。这样也避免了资源泄露。

但是这种方法也有一个问题就是,可能会有性能问题。

memorization是React性能优化的一个主要工具,React.memo进行浅比较,如果引用是相同的话,就不会进行重渲染。但是呢,如果把stop和start都传递给一个memorized组件的话,整个memorization就会失效,因为每次return都会返回新的引用。

React Hooks: Memoization

level11 useCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export default function Level11() {
console.log('renderLevel11');
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const start = useCallback(() => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 500);
}, []);
const stop = useCallback(() => {
if (intervalRef.current === null) {
return;
}
clearInterval(intervalRef.current);
intervalRef.current = null;
}, []);
return (
<div>
count => {count}
<button onClick={start}>start</button>
<button onClick={stop}>stop</button>
</div>
);
}

现在组件我们使用useCallback来包裹,现在每次我们返回的都是相同的引用。现在代码没有资源泄露,运行正确,同时也没有性能问题,但是代码复杂度增加了

level12 custom Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function useCounter(initialValue, ms) {
const [count, setCount] = useState(initialValue);
const intervalRef = useRef(null);
const start = useCallback(() => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, ms);
}, []);
const stop = useCallback(() => {
if (intervalRef.current === null) {
return;
}
clearInterval(intervalRef.current);
intervalRef.current = null;
}, []);
const reset = useCallback(() => {
setCount(0);
}, []);
return { count, start, stop, reset };
}

为了简化代码,我们把复杂的逻辑封装到useCounter中,同时向外暴露出{ count, start, stop, reset } API,然后我们就可以这么使用useCounter了

1
2
3
4
5
6
7
8
9
10
11
12
export default function Level12() {
console.log('renderLevel12');
const { count, start, stop, reset } = useCounter(0, 500);
return (
<div>
count => {count}
<button onClick={start}>start</button>
<button onClick={stop}>stop</button>
<button onClick={reset}>reset</button>
</div>
);
}

Summary

总的来说还是见的业务太少了,对React Hooks的使用场景不熟悉,还是要多写,多练多思考,才能真正融会贯通

本文译自技术社区dev.to 原作者 Lydia Hallie
原文链接:https://dev.to/lydiahallie/cs-visualized-cors-5b8h

Introduction

每一个开发人员都会在控制台里碰到这种烦人的问题Access to fetched has been blocked by CORS policy😬 尽管我们有一万种方法可以解决这种错误,但是呢,让我们今天来看一下CORS的机制是怎么运作的

❗️在这篇博客中我将不会讲解HTTP的基础细节

在前端,我们经常有要展示存储在其他地方数据的需求。在我们渲染数据以前,浏览器要先向服务器发送一个HTTP请求去获取数据。

让我们看一下这个过程,我们要在www.mywebsite.com上获取一些用户的信息,这些信息储存在api.website.com

OK了!我们刚刚向服务器发送了一个HTTP请求,然后我们拿到了我们要请求的JSON数据

好了,让我们再试一次,不同的是,我们这次请求的是另外一个域名www.anotherdomain.com

这次我们拿到了CORS错误,让我们来研究一下~

————————
Read more »

本文翻译自技术社区dev.to,原作者Lydia Hallie
原文链接:https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke

Introduction

当我们在写JavaScript的时候,我们经常会遇到要处理一些任务嵌套的问题,比如当前的任务要依赖另外一个任务才可以继续,可以看这样一个例子,我们要拿到一张图片,对它进行压缩,设置滤镜,最后再进行保存。

首先,我们使用getImage函数拿到我们要修改的图片,当图片成功被加载的时候,我们把它传递给resizeImage函数,接下来传递给applyFilter函数,当以上动作都完成以后,我们在控制台打印出最后成功的消息。

然后我们的代码就变成了这个样子:

Emmm…虽然看上去代码没什么问题,但是我们用了很多恶心的回调函数callback function,每一个回调函数都依赖前一个回调函数。这就是我们常说的回调地狱,最后代码将会变得难以理解。

不过好在ES6中引入了Promise,可以帮助我们解决上面的问题

Read more »

最近在复盘代码的过程中思考了这个问题,顺便复习bind()的用法和JavaScript中this指向的问题

在我们使用React进行开发的时候,我们必须使用.bind(this)方法在组件的构造函数中把事件处理函数绑定到组价实例上,例如这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Foo extends React.Component{
constructor( props ){
super( props );
this.handleClick = this.handleClick.bind(this);
}

handleClick(event){
// your event handling logic
}

render(){
return (
<button type="button"
onClick={this.handleClick}>
Click Me
</button>
);
}
}

ReactDOM.render(
<Foo />,
document.getElementById("app")
);

这篇文章中,我们将深入探究其中的原因。

在这之前,如果你对.bind()函数不了解的话,你可以阅读这篇文档

Read more »