React hooks是什么?
hooks让函数组件能实现和类组件一样的功能(例如状态管理、副作用、ref),同时相比state和生命周期函数,hooks更利于逻辑的拆分与复用。
why hooks
React官方一致提倡函数式编程,但是函数组件的能力不足以支持、相对而言类组件的功能更为强大。hooks解决了函数组件无法做内部的状态管理、产生副作用等问题,让函数组件的能力边界几乎和类组件一致,目的是让开发者在使用React时更好的实践FP的编程思想。
内置hooks
状态hooks
状态hooks的功能是给函数组件内部添加一个状态值,内置的状态hooks有useState和useReducer。
两者的底层实现一致,useState可以理解为useReducer的一个特例。
function Counter() {
const [count, setCount] = useState(0)
const handleClick = () => setCount(count => count + 1)
return (
<>
<div>{{ count }}</div>
<button onClick={handleClick}>increase</button>
</>
)
}
ref hooks
当你需要在函数中持久化某些信息,而这些信息又不会触发rerender(状态会触发再渲染),此时需要使用ref, useRef这个hooks可以帮助开发者在函数组件中定义一个ref。
ref和状态区别就在于:ref的更新不会触发渲染,可以用来存储dom节点或者计时器ID。
ref解决了在函数组件中需要存储一些值,但这些值不影响渲染逻辑的需求。
ref.current存储dom节点
function Test() {
const ref = useRef(null)
return <div ref={ref}>test</div>
}
ref.current存储某个和ui无关的变量
function Counter() {
const ref = useRef(0);
const handleClick = () => {
ref.current = ref.current + 1;
alert('你点击了 ' + ref.current + ' 次!');
}
return (
<button onClick={handleClick}>
点击我!
</button>
);
}
effect hooks
effect hooks用来在特定的时机执行一些副作用,这些副作用是与组件外部的系统交互的(如DOM API, 网络请、BOM API等)
常用的effect hooks有:useEffect和useLayoutEffect
// 在组件第一次挂载后请求网络数据
useEffect(() => {
fetch(url)
.then(res => res.json)
.then(...)
}, [])
// 监听事件,需要返回一个函数用于取消监听(计时器同理)
useEffect(() => {
window.addEventListener('resize', fn)
return () => window.removeEventListener('resize', fn)
})
useEffect在dev模式下执行两次的问题
这是React为了验证useEffect的编写是否违反规则,特别是是否设置了正确的cleanup函数,同时useEffect中如果有数据请求,应当避免其会修改服务端数据。
useEffect与useLayoutEffect的区别
useEffect不会阻塞浏览器渲染,它是一个异步回调,如果一定要在浏览器更新前执行,则需要使useLayoutEffect(componentDidMount、componentDidUpdate也是在浏览器更新前执行)。这样的好处是避免2次浏览器绘制导致的“闪烁”,但同时阻塞渲染也可能带来性能问题。
useContext
context是组件树跨层级传递数据的一种方式,使用useContext可以向上查找最近的Provider提供的context值。
性能hooks
useMemo和useCallback这2个hooks用于性能优化,如果页面无性能问题没必要使用。
useMemo用于缓存一个计算代价非常高的结果,甚至可以是jsx
const jsx = useMemo(() => data.map(item => <ListItem {...item}/>), [data])
useCallback用于缓存一个函数值,这样传递给子组件的函数是用一个引用,子组件使用优化(如memo)时就可以跳过更新。
// count状态的更新时,Button接受到的handleClick和上一次渲染是同一个,可以跳过更新
function Counter() {
const [count, setCount] = useState(0)
const handleClick = useCallback(() => setCount(count => count + 1), [])
return (
<>
<div>{{ count }}</div>
<Button handleClick={handleClick}/>
</>
)
}
const Button = React.memo(function(props) {
return <div onClick={props.handleClick}>increase</div>
})