react class component to hook style - NaClYen/blog GitHub Wiki
這幾天開始玩 react, 正在 survey 怎樣達到類似 vue 的 computed & watcher property 效果
爬了幾篇文章都導到 react 16.8 才釋出的新功能 Hook
試著把一個獨立的 modal 改寫, 留個紀錄~
改寫過程發現我本身的寫法就很類似 hook 的架構, 幾乎都在 render()
裡面完成大部分的撰寫, 所以轉移上基本上毫無壓力~
PS. 因為才剛玩幾天, 寫法都很 rough, 歡迎老手鞭策, 菜鳥如我可以互相砥礪 😃
這版本還沒實作觀測 srcUrl
& account
並刷新 finalUrl
的功能.
import React from "react";
import { Modal, InputGroup, FormControl, Button } from "react-bootstrap";
import { CopyToClipboard } from "react-copy-to-clipboard";
class AccountReplacer extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
srcUrl: "",
account: "",
finalUrl: ""
};
this.onSrcUrlChanged = this.onSrcUrlChanged.bind(this);
this.onClickOpenFinalUrl = this.onClickOpenFinalUrl.bind(this);
}
render() {
const handleClose = () => this.props.setShow(false);
const inputField = (title: string, value: string, onChange: React.ChangeEventHandler<HTMLInputElement>) => (
<InputGroup size="lg" className="mb-3">
<InputGroup.Prepend>
<InputGroup.Text>{title}</InputGroup.Text>
</InputGroup.Prepend>
<FormControl type="text" value={value} onChange={onChange}></FormControl>
</InputGroup>
);
const srcUrl = () => inputField("來源網址", this.state.srcUrl, this.onSrcUrlChanged);
const account = () => inputField("帳號", this.state.account, this.onAccountChanged);
const finalUrl = () => {
const copyBtn = () => (
<CopyToClipboard text={this.state.finalUrl} onCopy={() => console.log(`copy success!!`)}>
<Button variant="outline-success">Copy</Button>
</CopyToClipboard>
);
const openBtn = () => (
<Button variant="info" onClick={this.onClickOpenFinalUrl}>
Open
</Button>
);
return (
<InputGroup size="lg">
<InputGroup.Prepend>
<InputGroup.Text>輸出</InputGroup.Text>
</InputGroup.Prepend>
<FormControl type="text" value={this.state.finalUrl} readOnly></FormControl>
<InputGroup.Append>
{copyBtn()}
{openBtn()}
</InputGroup.Append>
</InputGroup>
);
};
return (
<Modal show={this.props.show} onHide={handleClose} backdrop="static" keyboard={false} size="lg">
<Modal.Header closeButton>
<Modal.Title>帳號更換器</Modal.Title>
</Modal.Header>
<Modal.Body>
{srcUrl()}
{account()}
{finalUrl()}
</Modal.Body>
<Modal.Footer>
<span></span>
</Modal.Footer>
</Modal>
);
}
onSrcUrlChanged(ev: React.ChangeEvent<HTMLInputElement>) {
if (ev) {
this.setState({ srcUrl: ev.target.value });
}
}
onAccountChanged(ev: React.ChangeEvent<HTMLInputElement>) {
if (ev) {
this.setState({ account: ev.target.value });
localStorage.setItem(kLastAccountKey, this.state.account);
}
}
onClickOpenFinalUrl() {
window.open(this.state.finalUrl, "_blank");
}
}
import React, { useEffect, useState } from "react";
import { Modal, InputGroup, FormControl, Button } from "react-bootstrap";
import { CopyToClipboard } from "react-copy-to-clipboard";
interface IProps {
show: boolean;
setShow(visible: boolean): void;
}
const kLastAccountKey = "last_replace_account";
function AccountReplacer(props: IProps) {
const [srcUrl, setSrcUrl] = useState<string>("");
const [account, setAccount] = useState<string>(localStorage.getItem(kLastAccountKey) || "");
const [finalUrl, setFinalUrl] = useState<string>("");
const handleClose = () => props.setShow(false);
const inputField = (title: string, value: string, onChange: React.ChangeEventHandler<HTMLInputElement>) => (
<InputGroup size="lg" className="mb-3">
<InputGroup.Prepend>
<InputGroup.Text>{title}</InputGroup.Text>
</InputGroup.Prepend>
<FormControl type="text" value={value} onChange={onChange}></FormControl>
</InputGroup>
);
const onSrcUrlChanged = (ev: React.ChangeEvent<HTMLInputElement>) => {
if (ev) {
setSrcUrl(ev.target.value);
}
};
const onAccountChanged = (ev: React.ChangeEvent<HTMLInputElement>) => {
if (ev) {
setAccount(ev.target.value);
}
};
const onClickOpenFinalUrl = () => {
window.open(finalUrl, "_blank");
};
const srcUrlDom = () => inputField("來源網址", srcUrl, onSrcUrlChanged);
const accountDom = () => inputField("帳號", account, onAccountChanged);
const finalUrlDom = () => {
const copyBtn = () => (
<CopyToClipboard text={finalUrl} onCopy={() => console.log(`copy success!!`)}>
<Button variant="outline-success">Copy</Button>
</CopyToClipboard>
);
const openBtn = () => (
<Button variant="info" onClick={onClickOpenFinalUrl}>
Open
</Button>
);
return (
<InputGroup size="lg">
<InputGroup.Prepend>
<InputGroup.Text>輸出</InputGroup.Text>
</InputGroup.Prepend>
<FormControl type="text" value={finalUrl} readOnly></FormControl>
<InputGroup.Append>
{copyBtn()}
{openBtn()}
</InputGroup.Append>
</InputGroup>
);
};
function combineFinalUrl(inUrl: string, inAccount: string) {
if (inUrl && inAccount) {
try {
const urlObj = new URL(inUrl);
// console.log(`onSrcUrlChanged!`);
if (urlObj.searchParams.has("account")) {
urlObj.searchParams.set("account", inAccount);
return urlObj.toString();
}
} catch (e) {
console.error(`遭遇錯誤:`, e);
}
}
return inUrl;
}
// 紀錄 account
useEffect(() => {
localStorage.setItem(kLastAccountKey, account);
}, [account]);
// 刷新 final url
useEffect(() => {
setFinalUrl(combineFinalUrl(srcUrl, account));
}, [srcUrl, account]);
return (
<Modal show={props.show} onHide={handleClose} backdrop="static" keyboard={false} size="lg">
<Modal.Header closeButton>
<Modal.Title>帳號更換器</Modal.Title>
</Modal.Header>
<Modal.Body>
{srcUrlDom()}
{accountDom()}
{finalUrlDom()}
</Modal.Body>
<Modal.Footer>
<span></span>
</Modal.Footer>
</Modal>
);
}
export default AccountReplacer;