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, 歡迎老手鞭策, 菜鳥如我可以互相砥礪 😃


class version

這版本還沒實作觀測 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");
    }
}

hook version

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;
⚠️ **GitHub.com Fallback** ⚠️