跳到主要内容

2 篇博文 含有标签「React」

查看所有标签

· 阅读需 5 分钟
超级可爱加菲

为什么需要自定义hooks

  1. 在创建工具类的时候,我们可能遇到需要在工具类中创建调用一些其他的hooks。但是由于使用的是常规JavaScript创建的工具类,此时便会出现Invalid hook call的错误。这是因为React hooks只能在React Component或另一个hook中被调用。
  2. 在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]);
}

· 阅读需 4 分钟
超级可爱加菲

引言

此博客作为一份如何在 React 中使用 bing maps api 参考,其他地图 api 原理上也可参考此教程。

前期准备

首先需要在bing mpas dev center中,具体在 My accout 下 My keys 中创建 new key。

bing map create key

Application URL 作为可选项,若填写 url 相当于白名单。非此域名下的网站则无法通过此 key 获取对应的一些 api 操作。

创建后 key details 下面 key 默认隐藏点击 Show key 便可拿到所需要的 bing maps api 中的 key。

bing maps key detail

开发者文档:

加载 bing maps api script

该教程以 bing maps v8 组件为例子

首先需要在 React 中有两种方式引入第三方引入第三方 js 资源:

  • 通常 React 项目中直接在 index.html 中通过 script 标签引入
<script
type="text/javascript"
src="http://www.bing.com/api/maps/mapcontrol?callback=GetMap&branch=experimental&key=[YOUR_BING_MAPS_KEY]"
async
defer></script>
  • 由于某些情况该工程不包含 index.html 或者其他因素则需要 dom 操作。需要注意以下事项。
    • bing map script 资源推荐全局引用,若在对应组件再加载 scipt 资源此时会极大影响体验。操作起来也会更为繁琐(需要留意是在地图资源加载完成后才进行初始化地图;由于是组件挂载 bing maps script 资源,每次组件卸载 scipt 资源也会被卸载,意味着每次组件加载时都需要获取一遍 script 资源)
    • 推荐 dom 操作时,在 body 中插入 scipt 资源

以下是我的片段代码(自定义 hook)

/**
* useLoadMap hook
* @description 加载bing map script资源
* @returns {void}
*/
const useLoadMap = () => {
useEffect(() => {
if (!window.Microsoft) {
const script = document.createElement("script");
script.src = `https://www.bing.com/api/maps/mapcontrol?key=${BING_KEY}`;
script.async = true;
script.defer = true;
script.type = "text/javascript";
document.body.appendChild(script);
}
}, [window.Microsoft]);
};

export default useLoadMap;

引用

// ...app

// ...
useLoadMap();
// ...

bing maps script 参数

初始化 bing map

简易获取当前用户位置并在地图图钉显示

import { BING_KEY, PIN } from "../utils";

const useMap = () => {
const mapRef = useRef(null);

/**
* Init map
*/
const getMap = () => {
// Initialize the map
const map = new window.Microsoft.Maps.Map("#myMap", {
credentials: BING_KEY,
enableCORS: false,
ShowNavigationBar: false,
});
mapRef.current = map;

// Load the spatial math module
window.Microsoft.Maps.loadModule("Microsoft.Maps.SpatialMath", function () {
// Request the user's location
navigator.geolocation.getCurrentPosition(function (position) {
var loc = new window.Microsoft.Maps.Location(
position.coords.latitude,
position.coords.longitude
);

// Create an accuracy circle
var path = window.Microsoft.Maps.SpatialMath.getRegularPolygon(
loc,
position.coords.accuracy,
36,
window.Microsoft.Maps.SpatialMath.Meters
);
var poly = new window.Microsoft.Maps.Polygon(path);
mapRef.current.entities.push(poly);
});
});
};

return [getMap, mapRef];
};

export default useMap;

在对应组件调用

// ...
const [getMap, mapRef] = useMap();

// ...
useLayoutEffect(() => {
// init map
if (window.Microsoft) {
// delay to wait for map loaded
setTimeout(() => getMap(), 100);
}
}, [window.Microsoft]);

// ...

模块

使用 loadModule 可加载不同模块

window.Microsoft.Maps.loadModule("Microsoft.Maps.SpatialMath", function () {
// todo
});

图钉

通过 Maps Pushpin 实例化图钉,在创建的 mapRef entities 中 push() 可对其进行增加 pop()移除

var pin = new window.Microsoft.Maps.Pushpin(
{
latitude: 47.58106995,
longitude: -122.34111023,
},
{
icon: PIN,
draggable: false,
}
);

mapRef.current.entities.push([location, pin]);

事件

在初始化的时候调用 Events addHandler 为其添加不同的事件

const getMap = () => {
window.Microsoft.Maps.Events.addHandler(map, "mouseup", function (e) {
// todo mouse up handler
});
};