下記の記事をみてフロントエンドエンジニアになりたい気持ちが強くなったので、1選ずつコード見ていき素人ながらに分析しました。かいつまんで、こんな書き方してるんだーみたいな発見を書いていくだけなので、体系的な説明にはなっていないと思うので悪しからず。お願いします。
#Object.keys
オブジェクトがもっているキー名を配列として返す。
このプロジェクトではサーバーにおいているSVGへのパスをオブジェクトにまとめてあり、それをObject.keys
を用いて、img要素を作るために利用している。
const CONSTANTS = {
assetPath: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/184729",
}
const ASSETS = {
head: `${CONSTANTS.assetPath}/head.svg`,
waiting: `${CONSTANTS.assetPath}/hand.svg`,
stalking: `${CONSTANTS.assetPath}/hand-waiting.svg`,
grabbing: `${CONSTANTS.assetPath}/hand.svg`,
grabbed: `${CONSTANTS.assetPath}/hand-with-cursor.svg`,
shaka: `${CONSTANTS.assetPath}/hand-surfs-up.svg`
}
// Preload images
Object.keys(ASSETS).forEach(key => {
const img = new Image();
img.src = ASSETS[key];
});
けど、Object.keys
の部分を消しても正常に動作しているっぽいので、何のためにこれをやっているのかは理解できていない。
#カスタムフック Ref addEventListener
戻り値としてrefとstate(真偽値を示すhoveredという変数)の2つの値配列を返すuseHoverというカスタムフックを作成している。
GrabZoneという関数コンポーネントでuseHoverを呼び出し、refをouterRef変数,innerRef変数に、stateをouterHovered変数,innerHovered変数にそれぞれ分割代入している。
outerRef変数,innerRef変数がref属性に指定されたReact要素に、useHover関数内で定義されたaddEventListenerが設定され、カーソルがそこにホバーするたびにuseHover関数内のenter関数が呼び出され、カーソルが範囲外に行くことでleave関数が実行される。
それによってsetHoverdが実行されhoverd変数の真偽値が切り替わり、if文の条件分岐により、UIに変化をもたらしている。
const useHover = () => {
const ref = useRef();
const [hovered, setHovered] = useState(false);
const enter = () => setHovered(true);
const leave = () => setHovered(false);
useEffect(
() => {
ref.current.addEventListener("mouseenter", enter);
ref.current.addEventListener("mouseleave", leave);
return () => {
ref.current.removeEventListener("mouseenter", enter);
ref.current.removeEventListener("mouseleave", leave);
};
},
[ref]
);
return [ref, hovered];
};
const GrabZone = ({ cursorGrabbed, gameOver, onCursorGrabbed }) => {
const [outerRef, outerHovered] = useHover();
const [innerRef, innerHovered] = useHover();
const [isExtended, setExtendedArm] = useState(false);
let state = "waiting";
if (outerHovered) {
state = "stalking";
}
if (innerHovered) {
state = "grabbing";
}
if (cursorGrabbed) {
state = "grabbed";
}
if (gameOver) {
state = "shaka"
}
// If state is grabbing for a long time, they're being clever!
useEffect(() => {
let timer;
if (state === "grabbing") {
timer = setTimeout(() => {
// Not so clever now, are they?
setExtendedArm(true);
timer = null;
}, 2000);
}
return () => {
setExtendedArm(false);
if (timer) {
clearTimeout(timer);
}
};
},
[state]
);
return (
<div className="grab-zone" ref={outerRef}>
<div className="grab-zone__debug">
<strong>Debug info:</strong>
<p>Current state: {state}</p>
<p>Extended arm: {isExtended ? "Yes" : "No"}</p>
</div>
<div className="grab-zone__danger" ref={innerRef}>
<Grabber
state={state}
gameOver={gameOver}
extended={isExtended}
onCursorGrabbed={onCursorGrabbed}
/>
</div>
</div>
);
};
#イベントハンドラー
このプロジェクトで扱われているイベントは下記の4つ。
###mousemove
マウスなどのポインティングデバイスで、カーソルのホットスポットが要素内にある間に動いた時に発行されるイベントです。
ここではwindow内でカーソルが動くというイベントに対して、マウスの位置をプログラムが把握するために使用されている。
###mouseenter
ポインティングデバイス (通常はマウス) のホットスポットが最初にイベントが発生した要素の中に移動したときに Element に発生します。
###mouseleave
mouseleave イベントは、ポインティングデバイス (ふつうはマウス) のカーソルが Element 外に移動したときに発行されます。
mouseenter
とmouseleave
はある要素内へのカーソルの出入りを検知し、stateの真偽値を更新するために使用されていた。
const useHover = () => {
const ref = useRef();
const [hovered, setHovered] = useState(false);
const enter = () => setHovered(true);
const leave = () => setHovered(false);
useEffect(
() => {
ref.current.addEventListener("mouseenter", enter);
ref.current.addEventListener("mouseleave", leave);
return () => {
ref.current.removeEventListener("mouseenter", enter);
ref.current.removeEventListener("mouseleave", leave);
};
},
[ref]
);
return [ref, hovered];
};
###resize
windowサイズの変更を検知し、要素の寸法と位置を返すgetBoundingClientRect
を呼び出している。
#後編へつづく
コードみたら何をしているのかは推測がつくのですが、このコードを作り上げる発想がすごいと思いました。
引き続きコード読み解いていきます。