プロバイダーパターン#
import React, { useState } from "react";
import "./styles.css";
import List from "./List";
import Toggle from "./Toggle";
export const themes = {
light: {
background: "#fff",
color: "#000"
},
dark: {
background: "#171717",
color: "#fff"
}
};
export const ThemeContext = React.createContext();
export default function App() {
const [theme, setTheme] = useState("dark");
function toggleTheme() {
setTheme(theme === "light" ? "dark" : "light");
}
return (
<div className={`App theme-${theme}`}>
<ThemeContext.Provider value={{ theme: themes[theme], toggleTheme }}>
<>
<Toggle />
<List />
</>
</ThemeContext.Provider>
</div>
);
}
import React, { useContext } from "react";
import { ThemeContext } from "./App";
export default function Toggle() {
const theme = useContext(ThemeContext);
return (
<label className="switch">
<input type="checkbox" onClick={theme.toggleTheme} />
<span className="slider round" />
</label>
);
}
import React, { useContext } from "react";
import { ThemeContext } from "./App";
export default function TextBox() {
const theme = useContext(ThemeContext);
return <li style={theme.theme}>...</li>;
}
オブザーバーパターン#
class Observable {
constructor() {
this.observers = [];
}
subscribe(func) {
this.observers.push(func);
}
unsubscribe(func) {
this.observers = this.observers.filter((observer) => observer !== func);
}
notify(data) {
this.observers.forEach((observer) => observer(data));
}
}
import React from "react";
import { Button, Switch, FormControlLabel } from "@material-ui/core";
import { ToastContainer, toast } from "react-toastify";
import observable from "./Observable";
function handleClick() {
observable.notify("ユーザーがボタンをクリックしました!");
}
function handleToggle() {
observable.notify("ユーザーがスイッチを切り替えました!");
}
function logger(data) {
console.log(`${Date.now()} ${data}`);
}
function toastify(data) {
toast(data, {
position: toast.POSITION.BOTTOM_RIGHT,
closeButton: false,
autoClose: 2000
});
}
observable.subscribe(logger);
observable.subscribe(toastify);
export default function App() {
return (
<div className="App">
<Button variant="contained" onClick={handleClick}>
クリックしてください!
</Button>
<FormControlLabel
control={<Switch name="" onChange={handleToggle} />}
label="切り替えてください!"
/>
<ToastContainer />
</div>
);
}
HOC パターン#
HOC の最適な使用ケース:
- アプリケーション全体で多くのコンポーネントが使用する必要がある同じ、カスタマイズされていない動作。
- コンポーネントは追加のカスタムロジックなしで単独で機能できる。
フックの最適な使用ケース:
- 動作はそれを使用する各コンポーネントに対してカスタマイズする必要がある。
- 動作はアプリケーション全体に広がっておらず、1 つまたは数個のコンポーネントのみがその動作を使用する。
- 動作はコンポーネントに多くのプロパティを追加する。
import React from "react";
import withLoader from "./withLoader";
import useHover from "./useHover";
function DogImages(props) {
const [hoverRef, hovering] = useHover();
return (
<div ref={hoverRef} {...props}>
{hovering && <div id="hover">ホバー中!</div>}
<div id="list">
{props.data.message.map((dog, index) => (
<img src={dog} alt="犬" key={index} />
))}
</div>
</div>
);
}
export default withLoader(
DogImages,
"https://dog.ceo/api/breed/labrador/images/random/6"
);
import { useState, useRef, useEffect } from "react";
export default function useHover() {
const [hovering, setHover] = useState(false);
const ref = useRef(null);
const handleMouseOver = () => setHover(true);
const handleMouseOut = () => setHover(false);
useEffect(() => {
const node = ref.current;
if (node) {
node.addEventListener("mouseover", handleMouseOver);
node.addEventListener("mouseout", handleMouseOut);
return () => {
node.removeEventListener("mouseover", handleMouseOver);
node.removeEventListener("mouseout", handleMouseOut);
};
}
}, [ref.current]);
return [ref, hovering];
}
import React, { useEffect, useState } from "react";
export default function withLoader(Element, url) {
return props => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => setData(data));
}, []);
if (!data) {
return <div>読み込み中...</div>;
}
return <Element {...props} data={data} />;
};
}
レンダープロップパターン#
import React, { useState } from "react";
import "./styles.css";
function Input(props) {
const [value, setValue] = useState(0);
return (
<>
<input
type="number"
value={value}
onChange={e => setValue(e.target.value)}
placeholder="温度 (°C)"
/>
{props.children(value)}
</>
);
}
export default function App() {
return (
<div className="App">
<h1>☃️ 温度変換器 🌞</h1>
<Input>
{value => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
</Input>
</div>
);
}
function Kelvin({ value }) {
return <div className="temp">{parseInt(value || 0) + 273.15}K</div>;
}
function Fahrenheit({ value }) {
return <div className="temp">{(parseInt(value || 0) * 9) / 5 + 32}°F</div>;
}
フックパターン#
import React, { useState } from "react";
export default function Input() {
const [input, setInput] = useState("");
return (
<input
onChange={e => setInput(e.target.value)}
value={input}
placeholder="何かを入力してください..."
/>
);
}
componentDidMount() { ... }
useEffect(() => { ... }, [])
componentWillUnmount() { ... }
useEffect(() => { return () => { ... } }, [])
componentDidUpdate() { ... }
useEffect(() => { ... })
import React, { useState, useEffect } from "react";
export default function Input() {
const [input, setInput] = useState("");
useEffect(() => {
console.log(`ユーザーが${input}と入力しました`);
}, [input]);
return (
<input
onChange={e => setInput(e.target.value)}
value={input}
placeholder="何かを入力してください..."
/>
);
}
import React from "react";
import useKeyPress from "./useKeyPress";
export default function Input() {
const [input, setInput] = React.useState("");
const pressQ = useKeyPress("q");
const pressW = useKeyPress("w");
const pressL = useKeyPress("l");
React.useEffect(() => {
console.log(`ユーザーがQを押しました!`);
}, [pressQ]);
React.useEffect(() => {
console.log(`ユーザーがWを押しました!`);
}, [pressW]);
React.useEffect(() => {
console.log(`ユーザーがLを押しました!`);
}, [pressL]);
return (
<input
onChange={e => setInput(e.target.value)}
value={input}
placeholder="何かを入力してください..."
/>
);
}
import React from "react";
export default function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = React.useState(false);
function handleDown({ key }) {
if (key === targetKey) {
setKeyPressed(true);
}
}
React.useEffect(() => {
window.addEventListener("keydown", handleDown);
return () => {
window.removeEventListener("keydown", handleDown);
};
}, []);
return keyPressed;
}
コンパウンドパターン#
import React from "react";
import ReactDOM from "react-dom";
import ImagesList from "./Images";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<div className="App">
<ImagesList />
</div>
</React.StrictMode>,
rootElement
);
import React from "react";
import FlyOutMenu from "./FlyOutMenu";
const sources = [
"https://images.pexels.com/photos/939478/pexels-photo-939478.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/1692984/pexels-photo-1692984.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/162829/squirrel-sciurus-vulgaris-major-mammal-mindfulness-162829.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260"
];
function Image({ source }) {
return (
<div className="image-item">
<img src={source} alt="リス" />
<FlyOutMenu />
</div>
);
}
import React from "react";
import "./styles.css";
import { FlyOut } from "./FlyOut";
export default function FlyoutMenu() {
return (
<FlyOut>
<FlyOut.Toggle />
<FlyOut.List>
<FlyOut.Item>編集</FlyOut.Item>
<FlyOut.Item>削除</FlyOut.Item>
</FlyOut.List>
</FlyOut>
);
}
import React from "react";
import Icon from "./Icon";
const FlyOutContext = React.createContext();
export function FlyOut(props) {
const [open, toggle] = React.useState(false);
return (
<div>
{React.Children.map(props.children, child =>
React.cloneElement(child, { open, toggle })
)}
</div>
);
}
function Toggle() {
const { open, toggle } = React.useContext(FlyOutContext);
return (
<div className="flyout-btn" onClick={() => toggle(!open)}>
<Icon />
</div>
);
}
function List({ children }) {
const { open } = React.useContext(FlyOutContext);
return open && <ul className="flyout-list">{children}</ul>;
}
function Item({ children }) {
return <li className="flyout-item">{children}</li>;
}
FlyOut.Toggle = Toggle;
FlyOut.List = List;
FlyOut.Item = Item;
Reference: