Posted on:
Last modified:
React 在 function 组件的 fiber 节点中加入了 memorizedState 属性用来存储数据,然后在 function 组件里面通过 api 来使用这些数据,这些 api 被叫做 hooks api。
因为是使用 fiber 节点上的数据,就把 api 命名为了 useXxx。
每个 hooks api 都要有自己存放数据的地方,怎么组织呢?有两种方案,一种是 map,一种是数组。 用 map 的话那么要 hooks api 要指定 key,按照 key 来存取 fiber 节点中的数据。 用数组的话顺序不能变,所以 hooks api 不能出现在 if 等逻辑块中,只能在顶层。 为了简化使用, hooks 最终使用了数组的方式。当然,实现起来用的是链表。 每个 hooks api 取对应的 fiber.memoriedState 中的数据来用。
hooks api 可以分为 3 类:
第一类是数据类的:
useState 是存储值最简单的方式,useMemo 基于 state 执行函数并且缓存结果,useCallback 是 针对值为函数的情况的简化,useReducer 通过 action 来触发值的修改。useRef 包了一层对象, 每次对比都是同一个,所以可以放一些不变的数据。
不管形式怎么样,这些 hooks 的 api 的作用都是返回值的。
第二类是逻辑类的:
这两个 hooks api 都是用于执行逻辑的,不需要等渲染完的逻辑都可以放到 useEffect 里。
第三类是 ref 转发专用的:
数据可以通过各种方案共享,但是 dom 元素就得通过 ref 转发了,所谓的 ref 转发就是在父组件 创建 ref,然后子组件把元素传过去。传过去之前想做一些修改,就可以用 useImperativeHandle 来改。
通过这 3 类 hooks api,以及之后会添加的更多 hooks api ,函数组件里面也能做 state 的存储, 也能在一些阶段执行一段逻辑,是可以替代 class 组件的方案了。
而且更重要的是,hooks api 是传递参数的函数调用的形式,可以对 hooks api 进一步封装成功能 更强大的函数,也就是自定义 hooks。通过这种方式就可以做跨组件的逻辑复用了。
useState
可以用来管理一个组件比较简单的一组状态,如果状态多了不适合使用 useState 管理,
可以使用 useReducer.
import React, {useState} from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(c => c + 1)}> Click me </button>
</div>
);
}
export default Counter;
setState
在更新函数中可能表现为异步,保险起见,应该使用一个函数作为它的参数,而不是直接
使用 state 值作为参数。比如在上面的例子中:
// 正确
<button onClick={() => setCount(c => c + 1)}> Click me </button>
// 错误
<button onClick={() => setCount(count + 1)}> Click me </button>
还有一个比较常见的模式是使用一个 object 保存多个状态,在部分更新 state 的时候,可以这样做:
const [state, setState] = useState({
"showText": true,
"showShadow": true
})
// 使用 {...s, foo: bar} 避免重复代码
<button onClick={() => setState(s => {...s, showText: false})}>Hide Text</button>
useRef
通常用于保存 DOM 节点的引用
// 创建 ref
const anchor = useRef(null)
function getStyles() {
// 使用 ref.current 访问引用
return window.getComputedStyle(anchor.current)
}
// 使用 ref={xxx} 绑定引用
return <a ref={anchor}>hello</a>
useRef
钩子和 useState
有些类似,都用于在不同的重绘之间保存状态。区别也很明显:
useState
中的 setState 会导致重绘,而改变 useRef 的 .current
则不会。
useEffect
hook 用来实现一些副作用,可以在某些变量更新时重绘组件。
默认情况下,每次 state 有改变的时候,都会调用 useEffect 函数。如果需要更改触发的时机,
那么需要使用 useEffect 的第二个参数来指定监听的事件或者说状态。当第二个参数只使用一个空
数组 []
的时候就只在组件加载的时候调用。数组的变量表示观察这些变量。
import React, {useState, useEffect} from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(() => console.log('tick'), 1000);
}
return () => clearInterval(interval);
}, [isOn]);
...
}
export default App;
在 useEffect 中返回的函数会被用来做垃圾清理。另外,组件初始化的时候总会触发一次 useEffect.
建议把不闭包依赖 props 和 state 的函数提到组件外,只在 effect 中使用的函数放到 effect 里。
用来保证子组件 props 的 Referential equality,实际很少用到。
useCallback(fn, []) // is equal to
useMemo(() => fn, [])
网上有些博客中说到的「useCallback 改善性能」大多属于误用。
通过灵活组合 useState, 和 useEffect, 我们完全可以创建自己的钩子。
import React from 'react';
function useOffline() {
const [isOffline, setIsOffline] = React.useState(false);
function onOffline() { setIsOffline(true); }
function onOnline() { setIsOffline(false); }
React.useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline);
};
}, []);
return isOffline;
}
function App() {
const isOffline = useOffline();
if (isOffline)
return <div>Sorry, you are offline ...</div>;
return <div>You are online!</div>;
}
export default App;
© 2016-2022 Yifei Kong. Powered by ynotes
All contents are under the CC-BY-NC-SA license, if not otherwise specified.
Opinions expressed here are solely my own and do not express the views or opinions of my employer.
友情链接: MySQL 教程站