为什么需要自定义hooks
- 在创建工具类的时候,我们可能遇到需要在工具类中创建调用一些其他的hooks。但是由于使用的是常规JavaScript创建的工具类,此时便会出现Invalid hook call的错误。这是因为React hooks只能在React Component或另一个hook中被调用。
- 在React Component中,页面有许多逻辑代码包含多个hook。因为在最开始我们知道hooks只能在hooks被调用,所有我们这时最好的办法便是创建一个hook用来抽离这些逻辑代码。这是一段常见代码:
// react component
const TestPage = () => {
const [data, setDate] = useState();
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const fetchData = async (params) => {
setLoading(true);
setError("");
try {
const res = await fetch(params);
setDate(res.data);
} catch (e) {
setError(e.error);
}
setLoading(false);
};
useEffect(() => {
fetchData();
return () => {
setLoading(false);
setError("");
setDate();
};
}, []);
const handler = (params) => {
fetchData(params);
};
return (
<div>
{data}
<button onClick={handler}></button>
</div>
);
};
我们可以对这个React component进行简单的抽离:
// customize hook -- useFetchData
const useFetchData = () => {
const [data, setDate] = useState();
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const fetchData = async (params) => {
setLoading(true);
setError("");
try {
const res = await fetch(params);
setDate(res.data);
} catch (e) {
setError(e.error);
}
setLoading(false);
};
useEffect(() => {
fetchData();
return () => {
setLoading(false);
setError("");
setDate();
};
}, []);
const handler = (params) => {
fetchData(params);
};
return { data, error, loading, handler };
};
在React component中调用这个hook
// react component
const TestPage = () => {
const { data, error, loading, handler } = useFetchData();
return (
<div>
{data}
<button onClick={handler}></button>
</div>
);
};
通过这种方式我们可以将React项目中的逻辑代码提取出来,或者就是当前component/hook逻辑复杂,代码量大,不便于理解,可以复用。我们都可以进行抽取。
创建规则
命名
hook必须以小写字母开头,use后跟大写字母逻辑 例如useOnLineStatus。当此函数不调用任何hooks(此时就是普通函数),避免使用use前缀。
理想情况下自定义hook,名称需要清晰。通过名字能猜出作用,并知道入参和返回值。因为当你无法选择一个清晰的名称意味着你的组建过于耦合,并未进行提取。
共享状态逻辑
自定义hook允许共享状态逻辑,但不能共享状态本身。 对于hook的每次调用都完全独立于对同一hook的所有其他调用。
import { useFormInput } from "./useFormInput.js";
export default function Form() {
const firstNameProps = useFormInput("Mary");
const lastNameProps = useFormInput("Poppins");
return (
<>
<label>
First name:
<input {...firstNameProps} />
</label>
<label>
Last name:
<input {...lastNameProps} />
</label>
<p>
<b>
Good morning, {firstNameProps.value} {lastNameProps.value}.
</b>
</p>
</>
);
}
import { useState } from "react";
export function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
const inputProps = {
value: value,
onChange: handleChange,
};
return inputProps;
}
hook之间传递reactive values
自定义hook的代码将在每次重新渲染组建期间运行。需要像函数一样,自定义hook需要保证纯粹。
export function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId,
};
const connection = createConnection(options);
connection.connect();
connection.on("message", (msg) => {
showNotification("New message: " + msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
export default function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState("https://localhost:1234");
useChatRoom({
roomId: roomId,
serverUrl: serverUrl,
});
return (
<>
<label>
Server URL:
<input
value={serverUrl}
onChange={(e) => setServerUrl(e.target.value)}
/>
</label>
<h1>Welcome to the {roomId} room!</h1>
</>
);
}
传递handler到自定义hook
export default function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState("https://localhost:1234");
useChatRoom({
roomId: roomId,
serverUrl: serverUrl,
onReceiveMessage(msg) {
showNotification("New message: " + msg);
},
});
return (
<>
<label>
Server URL:
<input
value={serverUrl}
onChange={(e) => setServerUrl(e.target.value)}
/>
</label>
<h1>Welcome to the {roomId} room!</h1>
</>
);
}
export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
const onMessage = useEffectEvent(onReceiveMessage);
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId,
};
const connection = createConnection(options);
connection.connect();
connection.on("message", (msg) => {
onMessage(msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl]);
}