hook是react16.8新加入的原生概念
将来不会取消class,hook是可选的、向后兼容的,可以渐进式使用hook。
使用hook的原因:
- hook为组件之间复用状态逻辑提供了更好的原生途径(虽然之前也有一些别的解决方案比如render props、高阶组件,但是需要重新组织你的组件结构,很麻烦) (通过自定义hook实现)
- 复杂组件充斥很多状态逻辑,难以理解和管理。相互关联且对照修改的部分(比如订阅和取消订阅)被拆分在不同的生命周期里。hook将组件相互关联的部分拆分成更小的函数,而并非强制按照生命周期划分。(通过effect hook实现)
- class组件难以学习理解,且给目前的优化带来了问题。hook使得在非class的情况下可以使用更多的react特性。
hook使用注意事项:
-
只能在函数最外层调用hook,不要在循环、条件语句或子函数中使用hook。(🤔️why? (因为hook是通过数组实现的,每次调用useState都会改变下标.react利用调用顺序正确更新状态,如果useState被包裹在循环或条件语句中,可能会引起调用顺序的错乱)
-
只能在react的函数组件和自定义hook中调用hook。不要在其他js函数中调用。
hook怎么知道哪个state对应哪个useState?
react靠的是hook的调用顺序,每次渲染中hook调用顺序都是一样的
常见Hook
useState Hook:让函数组件使用state
useState()返回一个数组,用到了数组解构
import React, { useState } from 'react';
function Exmaple(props) {
//声明一个叫count的state变量
const [count, setCount] = useState(0);
return(
<div>
//读取count
<p>you click {count} times</p>
//更新count。React重新渲染Example组件,把最新的count传给它。
<button onClick={() => setCount(count + 1)}>
click me
</button>
</div>
);
}
useEffect Hook:让函数组件进行副作用操作
-
相当于class组件componentDidMount,componentDidUpdate 和 componentWillUnmount三个生命周期函数的组合
-
effect函数在浏览器渲染结束后延迟执行
1、有时我们想在react更新dom之后(包括首次渲染)运行一些代码,比如发送网络请求
import React, { useState,useEffect } from 'react';
function Exmaple(props) {
const [count, setCount] = useState(0);
//useEffect能直接读取state变量,使用了闭包机制
useEffect(
//每次渲染dom都会执行这个函数(包括首次渲染)
() => {
document.title = 'you click {count} times';
}
);
return(
<div>
</div>
);
}
2、有时是需要清除的副作用,如订阅消息。我们在effect的返回函数中进行清除工作。
注意:每次重新渲染都会清除上一个effect,生成另一个新的effect。这避免了class组件中忘记处理更新逻辑而导致的bug
import React, { useState,useEffect } from 'react';
function Exmaple(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(
//订阅好友状态
() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
}
return function cleanup() {
//取消订阅
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
}
);
}
3、有时需要跳过effect,这可以优化性能。我们可以通过给useEffect的第二个可选参数传递数组。
-
数组中有一个元素变化,就会执行effect
-
如果数组为空,说明effect不依赖于任何值,不需要重复执行,也就是说仅在组件挂载和卸载时执行。
-
一般来说,传入空数组是不安全的。如果传入了空数组[],effect内部的state和props就会一直是初始值。所以我们推荐在effect内部声明需要用到的函数,这样容易看出effect依赖了哪些值。
useEffect(() => {
document.title = 'you click {count} times';
return () => {
.......
}
}, [count]); //仅在count更改时执行effect
useRef Hook:绑定某个dom节点
useRef()创建了一个通用容器。容器的current属性保存了一个任意值
绑定某个dom节点的useRef在每次渲染时都会返回同一个ref对象
变更current属性不会引发组件重新渲染
function TextInputWithFocusButton() {
//current属性被初始化为null
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
}
return (
<>
<input ref = {inputEl} type = "text" />
<button onClick = {onButtonClick}>focus the input</button>
</>
)
}
自定义Hook
-
名字以use开头,内部调用其他hook的函数称为自定义hook
-
自定义hook是一种重用状态逻辑的机制,它并不是共享state,每次使用自定义hook其中的state或副作用都是隔离的。
-
自定义hook并不是react的特性,而是一种自然遵循hook设计的约定
下面有一个例子,FriendStatus用于显示好友的在线状态Online/Offline
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
FriendListItem函数用来显示好友名字,并根据状态变换颜色
import React, { useState, useEffect } from 'react';
function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
可以看到上面两个函数共享逻辑和一部分代码。我们可以把共享逻辑提取到一个自定义hook中:
import React, { useState, useEffect } from 'react';
//自定义hook
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
return isOnline;
}
//使用自定义hook
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
//使用自定义hook
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
在多个hook之间传递信息
创建一个聊天接受者选择器,会显示当前好友是否在线,它和useFriendStatus之间传递信息
import React, { useState, useEffect } from 'react';
const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
function ChatRecipientPicker() {
//useState提供最新的recipientID,所以我们可以把它传给useFriendStatus自定义hook
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color = {isRecipientOnline ? 'green' : 'red'} />
<select
//当前选择的好友保存在recipientID这个state变量中,每次选择新的好友更新这个state
value = {recipientID}
onChange = {e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key = {friend,id} value = {friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}