提供者模式#
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>
);
}
高階組件模式#
高階組件的最佳使用案例:
- 相同的、未自定義的 行為需要被應用於應用程序中的許多組件。
- 組件可以獨立工作,而不需要額外的自定義邏輯。
Hooks 的最佳使用案例:
- 行為必須為每個使用它的組件自定義。
- 行為不遍佈於整個應用程序,只有一個或幾個組件使用該行為。
- 行為為組件添加了許多屬性。
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="攝氏溫度"
/>
{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>;
}
Hooks 模式#
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;
參考: