Hooks动机
在过去,函数式组件不支持state,所以被称为functional stateless components FSC
随着react hooks的发展,函数式组件可以支持state和side-effect
那么 Hooks 的出现是为了解决什么问题呢?我们可以试图总结一下类组件颇具代表性的痛点:
- 令人头疼的 this 管理,容易引入难以追踪的 Bug
- 生命周期的划分并不符合“内聚性”原则,例如 setInterval 和 clearInterval 这种具有强关联的逻辑被拆分在不同的生命周期方法中
- 组件复用(数据共享或功能复用)的困局,从早期的 Mixin,到高阶组件(HOC),再到 Render Props,始终没有一个清晰直观又便于维护的组件复用方案
下面是一个比较简单的函数式组件的demo
import React from "react";
import { v4 as uuidv4 } from "uuid";
import "./style.css";
const App = () => {
const greeting = "Hello Function Component!";
return <h1>{greeting}</h1>;
};
export default App;
可以看到,与类组件相比 :
- (props) => JSX.
- 没有render => return JSX
- statless无状态
- 没有this
- 没有生命周期
理解函数式组件的运行过程
我们知道,Hooks 只能用于 React 函数式组件。因此理解函数式组件的运行过程对掌握 Hooks 中许多重要的特性很关键,请看下图:
可以看到,函数式组件严格遵循 UI = render(data) 的模式。当我们第一次调用组件函数时,触发初次渲染;然后随着 props 的改变,便会重新调用该组件函数,触发重渲染。
你也许会纳闷,动画里面为啥要并排画三个一样的组件呢?因为我想通过这种方式直观地阐述函数式组件的一个重要思想:
每一次渲染都是完全独立的。
函数式组件和类组件区别
- 书写形式
- 状态维护 hooks useState
- 副作用处理 - 生命周期 useEffect
- this指向
- ref
- 状态异步更新处理
看下面例子:
import React from "react";
import "./style.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
handleIncrease = () => {
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
}, 2000);
};
render() {
return (
<div>
{this.state.count}
<button onClick={this.handleIncrease}>Increase</button>
</div>
);
}
}
export default App;
import React from "react";
import "./style.css";
export default function App() {
const [count, setCount] = React.useState(0);
const ref = React.useRef(0);
const handleIncrease = () => {
setTimeout(() => {
setCount(count + 1);
}, 2000);
};
console.log("RE-RENDER");
return (
<div>
{count}
<button onClick={handleIncrease}>Increase</button>
</div>
);
}
发现类组件 连续点击5次,2s后,数字依次变化0,1,2,3,4
函数式组件 连续点击5次,2s后,数字从0变化1
为什么?
快照(闭包) vs 最新值(引用)
class 组件里面可以通过 this.state 引用到 count,所以每次 setTimeout 的时候都能通过引用拿到上一次的最新 count,所以点击多少次最后就加了多少。
在 function component 里面每次更新都是重新执行当前函数,也就是说 setTimeout 里面读取到的 count 是通过闭包获取的,而这个 count 实际上只是初始值,并不是上次执行完成后的最新值,所以最后只加了1次。
如何使得行为和类组件一致?
import React from "react";
import "./style.css";
export default function App() {
const [count, setCount] = React.useState(0);
const ref = React.useRef(0);
const handleIncrease = () => {
setTimeout(() => {
//setCount(count => count + 1);
setCount((ref.current += 1));
}, 2000);
};
console.log("RE-RENDER");
return (
<div>
{count}
<button onClick={handleIncrease}>Increase</button>
</div>
);
}
- 给useState传递一个回调函数
- useRef 在每次重新渲染后都保持不变
(为什么useRef可以访问到最新值?为什么useState传一个回调函数就可以访问最新值?参看Hooks源码)
Reference:
https://www.robinwieruch.de/react-function-component
https://zhuanlan.zhihu.com/p/130299058
https://mp.weixin.qq.com/s/fF-H3Lr0aP3Ld8jfJrrwQg