React是一个可重复使用的UI组件库,而自定义Hook让我们可以使用自己的逻辑来将一些行为等移到可重复使用的函数中,方便代码的复用和组合。
一、Hook的简介
React Hook是在React16.8版本新增的一项特性。它允许您在不编写 Class 组件的情况下使用状态(state)和其它 React 特性。它们是一个允许您在函数组件中添加状态和其他React功能的方法。
使用hooks,就可以在不编写Class组件的情况下,使用状态(state)等react特性, 无需改变组件的层级,更容易的实现复用和组合。
二、何为自定义Hook?
自定义hook使得你可以使用功能组件实现状态逻辑复用。自定义hook是一个函数,命名以use为前缀,函数内部可以调取别的hook。自定义hook可以将组件的状态逻辑提取到可重用的函数中。
function useCustomHook() {
//调用state或Effect等hook
const [count, setCount] = useState(0);
useEffect(() => {
//登场效果
});
//自定义函数
function handleCount() {
setCount(count + 1);
}
//返回数据
return {
count,
handleCount,
};
}
function CustomComponent() {
//调用自定义hook
const { count, handleCount } = useCustomHook();
//返回展示效果
return (
<div>
<p>The count is {count}</p>
<button onClick={handleCount}>Click me</button>
</div>
);
}
三、实现业务逻辑分离
使用自定义hook使得我们可以把共同的业务逻辑放在一起。例如,当我们需要处理本地存储的请求时,无需在每一个需要本地存储的组件重复去写hook获取存储值。
下面是一个实现LocalStorage逻辑分离的例子:
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
}
function CustomComponent() {
const [name, setName] = useLocalStorage("name", "");
//返回展示效果
return (
<div>
<p>My name is {name}</p>
<button onClick={() => setName("John Doe")}>Set name to John Doe</button>
</div>
);
}
四、组件与非组件hook的区别
自定义hook既可以作为组件的hook使用,也可以是非组件的hook。不过对于组件hook与非组件hook在实现上有一定的区别:
- 组件hook可以依赖参数进行更新,除此之外的非组件hook是用来实现通用逻辑。
- 组件hook可以返回待渲染的JSX,而非组件hook则是返回状态和操作函数。
//组件hook
function useScrollTop() {
const [scrollTop, setScrollTop] = useState(false);
useEffect(() => {
function handleScroll() {
setScrollTop(window.scrollY > 0);
}
document.addEventListener('scroll', handleScroll);
return () => {
document.removeEventListener('scroll', handleScroll);
}
}, []);
return <div style={{ position: 'fixed', top: '18px', right: '30px' }}>{scrollTop&&<a href="#top">UP</a>}</div>;
}
//非组件hook
function useNotification(title, options) {
if (!("Notification" in window)) {
return;
}
const [permission] = useState(Notification.permission);
const [notification, setNotification] = useState(null);
const handleNotification = () => {
const notification = new Notification(title, options);
setNotification(notification);
};
const checkNotificationPromise = () => {
try {
Notification.requestPermission().then();
} catch (e) {
return false;
}
return true;
};
const requestNotificationPermission = async () => {
if (permission === "granted") {
handleNotification();
} else if (permission !== "denied") {
const permission = await Notification.requestPermission();
if (permission === "granted") {
handleNotification();
}
}
};
useEffect(() => {
if (permission === "granted") {
requestNotificationPermission();
} else if (permission === "default") {
checkNotificationPromise();
}
return () => {
if (notification) {
notification.close();
}
};
}, []);
}
五、自定义Hook对流程控制的影响
自定义hook聚焦于某些行为,以让组件对他们进行使用而不需要关心实现细节。自定义hook让组件更容易组合、共享行为,而无需关心时间的实现细节。
而对于每一个使用Effect的组件,都需要在componentDidMount和componentDidUnMount中组合其中的一些行为。这些代码很容易出错、难以阅读、难以恢复,并且难以测试。
自定义Hook让我们可以将这种代码和行为分离,同时让我们的组件不那么臃肿。自定义Hook可以针对一个特定的行为,而不是一个组件,从而更好地实现复用和组合。
六、结语
自定义hook使得我们可以将代码结构更加清晰和易于复用。从我们的应用程序中抽象出类似于useEffect、useState、useRef和useReducer的概念,并使用它们构建更高层次的抽象,可以更好地表达我们的意图,发现隐式复杂性。
在任何情况下,请记住重构代码并不总是意味着把所有东西重新设计到新的地方。根据需要使用自定义hook或者组件可以使代码更易于维护和扩展。