blog
blog copied to clipboard
React hook
hook用了好一段时间,写个小总结记录下
为什么要用hook:
复用业务逻辑的已有方案比如 render props 和 高阶组件,需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”
=> React 需要为共享状态逻辑提供更好的原生途径。
Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。
hook之间相互独自
Effect hook:
-
useEffect 会在每次渲染后都执行
-
如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
-
它会在调用一个新的 effect 之前对前一个 effect 进行清理
class组件生命周期版
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate(prevProps) {
// 取消订阅之前的 friend.id
ChatAPI.unsubscribeFromFriendStatus(
prevProps.friend.id,
this.handleStatusChange
);
// 订阅新的 friend.id
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
function 组件hook版
function FriendStatus(props) {
// ...
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // 如同componentDidMount,componentDidUpdate里执行新的副作用函数
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); // 如同componentWillUnmount,componentDidUpdate里对已执行的副作用函数的清理函数
};
});
执行时间轴
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 运行第一个 effect
// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 运行下一个 effect
// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 运行下一个 effect
// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一个 effect
也就是不管是首次render还是后面的更新render,每次render都会执行一次useEffect这个hook函数;componentDidMount 和 componentWillUnmount 是一次render,componentDidUpdate 也是一次render,所以如下图
重新render会执行新的useEffect, 执行新的useEffect之前会执行旧的useEffect的return的方法,此方法用旧的state(需要的话)。
性能优化:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
在useEffect传第二个参数(是个array)相当于computed的作用,仅在这个参数变化时才会去执行这hook,也相当于class组件componentDidUpdate这个生命周期中比较prevState.count 和 this.state.coun不相等才执行这hook,不传就每次rerender都会执行。
如果要这个hook执行一次,那就传个[]即可
useEffect(() => {
document.title = `You clicked ${count} times`;
}, []); // 仅在首次render执行 只执行一次
State hook:
import React, { useState } from 'react';
function Example() {
// 左边第一个参数是state,右边是setState
// 右边的参数是state的初始值
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
等同于
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
因为hook之间相互独自,所以每个state都写一个useState来生成和管理
看我写的这个demo,能看到更新一个useState,这个函数组件会rerender,但是另外一个useState的值依旧保留之前的值不受影响。
useMemo useCallback
这两个之所以放一起说,是因为作用有点类似,都是用记忆计算结果来避免render的时候重新做一些计算,节省开销。不同的是,useMemo是记忆值,也就是返回值const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
;而useCallback是记忆函数,也就是返回一个函数,const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b],);
。
而且我原以为会缓存之前多次计算过的东西,没想到测试了下,只会缓存记忆上一次的,所以只有上一次和本次的依赖值相同时,才会读取上一次的计算结果,但是上一次之前的就算有跟本次的依赖值相同的,依旧要重新计算。如下图
写自己的hook
这个可以看看这个库react-use, 里面有写了很多好用的hook
reference
todo
hook源码分析?