hooks 诞生的原因
类组件不不足
1 、状态逻辑复用困难
2、 复杂难以维护
- 生命周期函数混合不相干逻辑
- 相干逻辑分散在不同生命周期
3、this 指向麻烦
- 内联函数过度创建新句柄
- 类成员函数不能保证this
hooks 优势
- 函数组件无 this 问题
- 自定义 hook 逻辑复用方便
- 副作用关注点分离
与class的对比
对于一个按钮点击count加一,分别用 class 和 function 形式写一遍:
class 形式 codesandbox
import React from "react";
export default class App extends React.Component {
state = {
count: 0
};
render() {
const { count } = this.state;
return (
<button onClick={() => this.setState({ count: count + 1 })}>
Count{count}
</button>
);
}
}
function 形式codesandbox
import React, { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count{count}</button>;
}
可以看到,function 形式的组件比 class 形式的组件简洁很多。
useState
import React, { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(0)}>Click({count})</button>;
}
- 如果从 props 里面取值复杂,将
useState 参数作为函数返回值,只会执行一次codesandbox
import React, { useState } from "react";
export default function App(props) {
const [count, setCount] = useState(() => {
console.log("get props");
return props.defaultCount || 0;
});
return <button onClick={() => setCount(count + 1)}>Count{count}</button>;
}
useEffect
import React from "react";
class Count extends React.Component {
state = {
count: 0,
size: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
};
onResize = () => {
this.setState({
size: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
});
};
// resize 的监听和解绑分散在不同生命周期
componentDidMount() {
// title 和 resize 耦合在一起
document.title = this.state.count;
window.addEventListener("resize", this.onResize, false);
}
componentWillUnmount() {
console.log("remove listener");
window.removeEventListener("resize", this.onResize, false);
}
componentDidUpdate() {
document.title = this.state.count;
}
render() {
console.log("render");
const { count, size } = this.state;
return (
<button onClick={() => this.setState({ count: count + 1 })}>
Count({count}) Size: {size.width}*{size.height}
</button>
);
}
}
export default class App extends React.Component {
state = {
id: 1
};
render() {
const { id } = this.state;
return (
<div>
{id & 1 ? <button>111</button> : <Count />}
<button onClick={() => this.setState({ id: id + 1 })}>appClick</button>
</div>
);
}
}
import React, { useState, useEffect } from "react";
export default function App() {
const [id, setId] = useState(0);
return (
<div>
{id & 1 ? <button>111</button> : <Count />}
<button onClick={() => setId(id + 1)}>app click</button>
</div>
);
}
function Count() {
console.log("render");
const [count, setCount] = useState(0);
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
const onResize = () => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
};
// 关注点分离,title 和 resize 各自处理各自逻辑
useEffect(() => {
console.log("title");
document.title = count;
});
useEffect(() => {
console.log("resize");
window.addEventListener("resize", onResize, false);
return () => {
console.log("remove listener");
window.removeEventListener("resize", onResize, false);
};
}, []);
return (
<button onClick={() => setCount(count + 1)}>
Count({count}) Size({size.width}* {size.height})
</button>
);
}
useEffect 的一个错误的示例 codesandbox,本意是想让 count 累加到10,然后自动停止。
import React, { useState, useEffect } from "react";
export default function App() {
const [count, setCount] = useState(0);
let timer;
useEffect(() => {
timer = setInterval(() => {
console.log("setInterval", count);
setCount(count + 1);
}, 1000);
}, []);
useEffect(() => {
if (count > 10) {
clearInterval(timer);
}
});
return <button onClick={() => setCount(count + 1)}>Count({count})</button>;
}
需要像下面这样:codesandbox
import React, { useState, useEffect } from "react";
export default function App() {
const [count, setCount] = useState(0);
let timer;
useEffect(() => {
timer = setInterval(() => {
console.log("setInterval", count);
setCount(count => count + 1);
}, 1000);
}, []);
useEffect(() => {
if (count > 10) {
clearInterval(timer);
}
});
return <button onClick={() => setCount(count + 1)}>Count({count})</button>;
}
还有一个问题,如果上面代码中
useEffect(() => {
timer = setInterval(() => {
console.log("setInterval", count);
setCount(count => count + 1);
}, 1000);
}, []);
变成下面这样,会导致什么问题呢?
useEffect(() => {
timer = setInterval(() => {
console.log("setInterval", count);
setCount( count + 1);
}, 1000);
}, [count]);
useMemo
目前理解不是很深刻
有记忆功能,点击 age 的时候不会改变 count,所以就不会输出 calc count。codesandbox
import React, { useState, useMemo } from "react";
const Counter = function(props) {
console.log("Counter");
return <div>{props.count}</div>;
};
export default function App() {
const [count, setCount] = useState(0);
const [age, setAge] = useState(20);
const dobule = useMemo(() => {
console.log("calc count");
if (count > 3) {
return count * 2;
}
}, [count]);
return (
<div>
<Counter count={dobule} />
<button onClick={() => setAge(age + 1)}>age: {age}</button>
<button onClick={() => setCount(count + 1)}>
Count({count}) double({dobule})
</button>
</div>
);
}
不过有一个问题, Counter 组件会一直渲染,其实该组件只依赖于 count,不应该点击 age 时重新渲染它。可以用 memo 包裹一下就好了,这样你点击 age 就不会重渲染 Counter。 codesandbox
import React, { useState, useMemo, memo } from "react";
const Counter = memo(props => {
console.log("Counter");
return <div>Counter({props.count})</div>;
});
export default function App() {
const [count, setCount] = useState(0);
const [age, setAge] = useState(20);
const dobule = useMemo(() => {
console.log("calc count");
if (count > 3) {
return count * 2;
}
}, [count]);
return (
<div>
<Counter count={dobule} />
<button onClick={() => setAge(age + 1)}>age: {age}</button>
<button onClick={() => setCount(count + 1)}>
Count({count}) double({dobule})
</button>
</div>
);
}
useRef
自定义hook
把上面的例子用自定义hook重写:codesandbox
import React, { useState, useEffect, useRef } from "react";
function useCount1(count) {
return <h1>{count}</h1>
}
function useCount(defaultCount) {
const [count, setCount] = useState(defaultCount);
let timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
console.log("setInterval", count);
setCount(count => count + 1);
}, 1000);
}, []);
useEffect(() => {
if (count >= 10) {
clearInterval(timer.current);
}
});
return [count, setCount];
}
export default function App() {
const [count, setCount] = useCount(0);
const Count1 = useCount1(count)
return (
<div>
{Count1}
<button onClick={() => setCount(count + 1)}>Count({count})</button>;
</div>
)
}
hooks 诞生的原因
类组件不不足
1 、状态逻辑复用困难
2、 复杂难以维护
3、this 指向麻烦
hooks 优势
与class的对比
对于一个按钮点击count加一,分别用
class和function形式写一遍:class形式 codesandboxfunction形式codesandbox可以看到,
function形式的组件比class形式的组件简洁很多。useState
count为0,setCount(0)不会触发重新渲染 codesandboxuseState参数作为函数返回值,只会执行一次codesandboxuseEffect
class形式的democodesandboxfunction形式的democodesandboxuseEffect 的一个错误的示例 codesandbox,本意是想让
count累加到10,然后自动停止。需要像下面这样:codesandbox
还有一个问题,如果上面代码中
变成下面这样,会导致什么问题呢?
useMemo
有记忆功能,点击
age的时候不会改变count,所以就不会输出calc count。codesandbox不过有一个问题,
Counter组件会一直渲染,其实该组件只依赖于count,不应该点击age时重新渲染它。可以用memo包裹一下就好了,这样你点击age就不会重渲染Counter。 codesandboxuseRef
自定义hook
把上面的例子用自定义hook重写:codesandbox