코딩/Javascript

2편, 카카오톡 웹 버전 만들기 (React, Material-UI)

드리프트 2020. 12. 6. 22:11
728x170

 

 

 

안녕하세요?

 

1편에 이어 2편째 쓰고 있는 카카오톡 웹 버전 만들기 (React, Material-UI)입니다.

 

1편에서는 카카오 디벨로퍼 세팅하는 방법을 알아 보았는데요, 2편에서는 본격적인 코딩을 해보겠습니다.

 

일단 터미널창에서 다음과 같이 입력합니다.

 

create-react-app kakao-test
cd kakao-test
yarn start

 

리액트 앱을 "kakao-test"라는 이름으로 만들고 "yarn start"로 서버를 돌린 상태입니다.

 

정상적으로 돌아가는 모습은 다음과 같습니다.

 

create-react-app명령어가 없다면 다음과 같이 입력하여 설치하시면 됩니다.

 

npm install -g create-react-app

 

 

우리의 리액트 앱이 잘 실행되고 있네요.

 

그럼 본격적인 코딩에 들어가기 전에 Material-UI를 설치합시다.

 

yarn add @material-ui/core
yarn add @material-ui/icons

 

두번째는 SVG 아이콘인데 제 튜토리얼에서는 필요없으나 본인이 좋아하는 아이콘으로 데코레이션이 필요하시면 미리 설치해 두는 것도 좋습니다.

 

자, 터미널에서 Ctrl+C 를 눌러 리액트 디벨럽먼트 서버를 중지하고 다음과 같이  VS Code 를 실행합시다.

 

code .

 

일단 현재 폴더 구조는 다음과 같습니다.

 

 

먼저, 웹페이지의 타이틀을 변경하고 두번째, 카카오톡 웹 API를 위한 자바스크립트를 링크해야 겠죠.

 

먼저 public에서 index.html 파일을 불러오고  아래 그림과 같이 변경합니다.

 

 

<head>
	<title>카카오톡 웹</title>
</head>
<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
	<script src="//developers.kakao.com/sdk/js/kakao.min.js"></script>
</body>

 

카카오 SDK의 CDN 링크입니다. 이게 있어야 우리의 웹앱에서 카카오톡 API를 사용할 수 있는겁니다.

 

그러면 다음으로 본격적인 리액트 앱을 만들어 볼까요?

 

일단 Create-react-app이 만들어 놓은 불필요한 파일은 삭제합시다.

 

App.css
App.test.js
index.css
logo.svg

 

그리고 App.js를 클릭해서 다음과 같이 입력합시다.

 

import React from "react";
import Header from "./components/Header";
import Footer from "./components/Footer";
import KakaoLink from "./components/KakaoLink";

function App() {
  return (
    <div>
      <Header />
      <KakaoLink />
      <Footer />
    </div>
  );
}

export default App;

 

우리의 앱 구조는 간단합니다.

 

Header와 KakaoLink, 그리고 Footer 컴포넌트를 세개 연달아 배치하는 레이아웃입니다.

 

 

 

 

 

그럼 각각의 컴포넌트를 살펴 볼까요?

 

먼저, 쉬운 Header랑 Footer를 입력합시다.

 

src 폴더에 components 폴더를 만들고 그 밑에

 

Header.js랑 Footer.js 두개의 파일을 만듭니다.

 

import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles((theme) => ({
  margin: {
    margin: theme.spacing(1),
  },
  flex: {
    flex: 1,
  },
}));

const Header = (props) => {
  const classes = useStyles();

  return (
    <div className={classes.margin}>
      <AppBar position="static">
        <Toolbar>
          <Typography variant="h5" color="inherit" className={classes.flex}>
            카카오톡 웹
          </Typography>
        </Toolbar>
      </AppBar>
    </div>
  );
};

export default Header;

 

import React from "react";
import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles((theme) => ({
  root: {
    ...theme.mixins.gutters(),
    paddingTop: theme.spacing.unit * 2,
    paddingBottom: theme.spacing.unit * 2,
    textAlign: "center",
  },
  link: {
    textDecoration: "none",
  },
}));

const Footer = (props) => {
  const classes = useStyles();

  return (
    <div>
      <Paper className={classes.root} elevation={0}>
        <Typography component="p">
          <a href="https://cpro95.tistory.com/50" className={classes.link}>
            링크 : 튜토리얼 보기
          </a>
          <p>카카오톡 API와 React, Material-UI로 만들었습니다.</p>
        </Typography>
      </Paper>
    </div>
  );
};

export default Footer;

 

일단 위 코드도 쉽게 이해할 수 있는데요.

 

스타일링을 위해 makeStyles 함수를 사용해서 본인이 원하는 CSS 작성을 할 수 있습니다.

 

Material-UI는 많은 컴포넌트를 가지고 있는데 AppBar가 Header용으로 아주 좋습니다.

 

그리고 p 태그를 위해서는 Typography 를 사용하면 좋습니다.

 

자, 그러면 제일 중요한 KakaoLink.js 파일을 만들어 볼까요?

 

일단 src/components/KakaoLink.js 파일을 만듭시다.

 

import React from 'react';

const KakaoLink = () => {
    return (
        <div>
            KakaoLink
        </div>
    )
};

export default KakaoLink;

 

VS Code 의 코드 스니핏으로 가장 기본적인 function component를 만들었습니다.

 

자, 그럼 본격적인 작업을 해야 되는데 카카오 디벨로퍼의 Javascript API를 한번 보시고 갑시다.

 

 

우리가 웹상에서 자바스크립트를 이용해서 카카오톡 메시지를 보낼려면 Kakao.Link.sendDefault 함수를 사용해야 합니다.

 

이제 우리가 뭘 사용해야 되는지 알았으면 일단 테스트 코드를 만들어 볼까요?

 

이제 VS Code 에서 KakaoLink.js에 아래 같이 추가합니다.

 

import React, { useEffect } from "react";

const KakaoLink = () => {
  useEffect(() => {
    // 사용할 앱의 JavaScript 키를 설정해 주세요.
    window.Kakao.init("bbd935c78ee4dd0e200f13cfc9dc8384");
  }, []);
  return <div>KakaoLink</div>;
};

export default KakaoLink;

 

카카오톡 API를 사용할려면 Kakao.init("javascript Key")를 이용해 초기화 해야 합니다.

 

javascript Key는 1편에서 카카오디벨로퍼에서 만든 애플리케이션 세팅에 있습니다.

 

 

그럼 React(리액트)에서 Kakao.init을 어떻게 실행할까요?

 

그것은 우리가 public에서 입력한 아래 코드에 해답이 있습니다.

 

<script src="//developers.kakao.com/sdk/js/kakao.min.js"></script>

 

kakao.min.js를 로드하면서 우리는 자바스크립트 global 변수인 window를 통해 카카오 api를 액세스 할 수 있습니다.

 

window.Kakao.init("bbd935c78ee4dd0e200f13cfc9dc8384");

 

자, 이제 우리의 컴포넌트가 초기화 되면 Kakao.init을 해야 되는데 초기화는 한번만 하면 되니까

 

여기서 우리는 useEffect 훅을 이용해서 컴포넌트가 로드 될 때 한번만 실행하는 방식을 사용했습니다.

 

  useEffect(() => {
    // 사용할 앱의 JavaScript 키를 설정해 주세요.
    window.Kakao.init("bbd935c78ee4dd0e200f13cfc9dc8384");
  }, []);
  return <div>KakaoLink</div>;
};

 

자, 이제 카카오톡 API 초기화도 했겠다. 이제 텍스트를 써서 자신의 핸드폰 카카오톡으로 전송한 번 해볼까요?

 

그럼, 우리가 필요한 건 TextField tag랑 send button이겠죠.

 

UI부분은 머티리얼 디자인을 이용해서 좀 더 멋지게 만들었습니다.

 

import React, { useState, useEffect } from "react";
import Paper from "@material-ui/core/Paper";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles((theme) => ({
  root: {
    ...theme.mixins.gutters(),
    paddingTop: theme.spacing.unit * 2,
    paddingBottom: theme.spacing.unit * 2,
  },
  media: {
    height: 0,
    paddingTop: "56.25%", // 16:9
  },
  button: {
    margin: theme.spacing.unit,
  },
  input: {
    display: "none",
  },
}));

const KakaoLink = () => {
  const classes = useStyles();

  useEffect(() => {
    // 사용할 앱의 JavaScript 키를 설정해 주세요.
    window.Kakao.init("bbd935c78ee4dd0e200f13cfc9dc8384");
  }, []);

  const [text, setText] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    sendLink(""); // url to blank
  };

  const sendLink = (imageUrl) => {
    var url = "";
    if (imageUrl.length > 0) {
      url = imageUrl;
    }
    window.Kakao.Link.sendDefault({
      objectType: "feed",
      content: {
        title: "",
        description: text,
        imageUrl: url,
        link: {
          webUrl: url,
          mobileWebUrl: url,
        },
      },
    });

    // state 초기화
    setText("");
  };

  const clean = () => {
    setText("");
  };

  return (
    <Paper className={classes.root} elevation={1}>
      <TextField
        id="outlined-full-width"
        label="메시지"
        style={{ margin: 8 }}
        placeholder="여기에 카톡 내용을 써 주세요"
        fullWidth
        autoFocus
        multiline
        rows="5"
        margin="normal"
        variant="outlined"
        InputLabelProps={{
          shrink: true,
        }}
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <div>
        <Button variant="outlined" color="primary" onClick={(e) => clean()}>
          다시 쓰기
        </Button>
        <span> </span>
        <Button
          variant="outlined"
          color="primary"
          id="kakao-link-btn"
          onClick={(e) => handleSubmit(e)}
        >
          카톡 전송
        </Button>
      </div>
    </Paper>
  );
};

export default KakaoLink;

 

실행 화면은 아래와 같습니다.

 

 

일단은 Header랑 Footer랑 잘 나오고 있고, 중간에 메시지 넣는 TextField도 잘 나오고 있습니다.

 

그럼 본격적인 코드 설명에 들어가 볼까요?

 

UI 부분은 React(리액트)를 아시는 분이면 금방 이해 할 수 있습니다.

 

그럼 중요한 handleSubmit 함수를 살펴 볼까요?

 

const handleSubmit = (e) => {
    e.preventDefault();
    sendLink(""); // url to blank
  };

  const sendLink = (imageUrl) => {
    var url = "";
    if (imageUrl.length > 0) {
      url = imageUrl;
    }
    window.Kakao.Link.sendDefault({
      objectType: "feed",
      content: {
        title: "",
        description: text,
        imageUrl: url,
        link: {
          webUrl: url,
          mobileWebUrl: url,
        },
      },
    });

    // state 초기화
    setText("");
  };

 

handleSubmit 함수는 간단하게 sendLink 함수를 호출하는 역할을 합니다.

 

그러면 왜 sendLink 함수를 호출하게 만들었나면 handleSubmit함수에서 나중을 위해서 이미지 업로드를 위한 url 부분을 발췌하는 로직이 필요합니다.

 

그건 나중에 만들어 볼게요.

 

일단은 sendLink 함수는 imageUrl 인자를 받고 window.Kakao.Link.sendDefault() 함수를 호출합니다.

 

sendDefault() 함수의 인자는 object형식인데 다음과 같습니다.

 

{
      objectType: "feed",
      content: {
        title: "",
        description: text,
        imageUrl: url,
        link: {
          webUrl: url,
          mobileWebUrl: url,
        },
      },
}

 

제일 중요한 부분이 description 인데 여기에 우리의 text 변수를 지정했습니다.

 

text 변수는 React Hook을 이용해서 만들었는데요.

 

const [text, setText] = useState("");

 

TextField 컴포넌트에서 이 text 변수에 우리의 메시지를 저장합니다.

 

카톡 전송 버튼을 누르면 로그인창이 뜨고 카톡화면이 뜨면서 정상 작동하는 걸 볼 수 있을겁니다.

 

일단 여기까지만 해도 반은 성공한거죠.

 

그러나 여기서 추가할 게 이미지 파일을 전송하는 코드를 추가해 보겠습니다.

 

일단 전체 코드를 아래와 같이 바꿔줍시다.

 

import React, { useState, useEffect } from "react";
import Paper from "@material-ui/core/Paper";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import CardMedia from "@material-ui/core/CardMedia";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles((theme) => ({
  root: {
    ...theme.mixins.gutters(),
    paddingTop: theme.spacing.unit * 2,
    paddingBottom: theme.spacing.unit * 2,
  },
  media: {
    height: 0,
    paddingTop: "56.25%", // 16:9
  },
  button: {
    margin: theme.spacing.unit,
  },
  input: {
    display: "none",
  },
}));

const KakaoLink = (props) => {
  const classes = useStyles();

  useEffect(() => {
    // 사용할 앱의 JavaScript 키를 설정해 주세요.
    window.Kakao.init("1598359c558c0e811105006367eb346d");
  }, []);

  const [text, setText] = useState("");
  const [files, setFiles] = useState([]);
  const [localUrl, setLocalUrl] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    // console.log("files length: " + files.length);
    // console.log("localUrl length:" + localUrl.length);
    // console.log(localUrl);
    if (files.length > 0) {
      // console.log("업로드할 이미지가 있는 경우....");
      var localFiles = files;
      window.Kakao.Link.uploadImage({
        file: localFiles,
      }).then((res) => {
        // console.log("image url : " + res.infos.original.url);
        sendLink(res.infos.original.url);
      });
    } else {
      sendLink(""); // url to blank
    }
  };

  const handleUpload = (e) => {
    e.preventDefault();
    // console.log(e.target.files);
    const imagesLocalUrl = URL.createObjectURL(e.target.files[0]);
    // console.log("localUrl : " + imagesLocalUrl);
    setLocalUrl(imagesLocalUrl);
    setFiles(e.target.files);
  };

  const sendLink = (imageUrl) => {
    var url = "";
    if (imageUrl.length > 0) {
      url = imageUrl;
    }
    if (imageUrl.length > 0) {
      url = imageUrl;
      // 이미지를 보낼때는 objectType 을 feed로 해야 보낼 수 있다.
      window.Kakao.Link.sendDefault({
        objectType: "feed",
        content: {
          title: "",
          description: text,
          imageUrl: url,
          link: {
            webUrl: url,
            mobileWebUrl: url,
          },
        },
      });
    } else {
      // 이미지가 없을 때
      // Kakao.Link.sendDefault 의 text 보내기가 버그 픽스가 되었다.
      // 아래처럼 objectType 으로 'text'를 넣으면 최대 200자까지 메시지를 전달할 수 있다.
      window.Kakao.Link.sendDefault({
        objectType: "text",
        text: text,
        link: {
          mobileWebUrl: url,
          webUrl: url,
        },
      });
    }

    // state 초기화
    setText("");
    setFiles([]);
    setLocalUrl("");
  };

  const clean = () => {
    setText("");
    setFiles([]);
    setLocalUrl("");
  };

  return (
    <Paper className={classes.root} elevation={1}>
      <TextField
        id="outlined-full-width"
        label="메시지"
        style={{ margin: 8 }}
        placeholder="한번에 200자까지만 전송 가능 (이미지 첨부할 경우 100자만 가능)"
        fullWidth
        autoFocus
        multiline
        rows="5"
        margin="normal"
        variant="outlined"
        InputLabelProps={{
          shrink: true,
        }}
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <div>
        <Button variant="outlined" color="primary" onClick={(e) => clean()}>
          다시 쓰기
        </Button>
        <span> </span>
        <Button
          variant="outlined"
          color="primary"
          id="kakao-link-btn"
          onClick={(e) => handleSubmit(e)}
        >
          카톡 전송
        </Button>
        <input
          className={classes.input}
          accept="image/*"
          id="button-file"
          // KakaoLink not support multiple file sending yet.
          // multiple
          type="file"
          onChange={(e) => handleUpload(e)}
        />
        <label htmlFor="button-file">
          <Button
            variant="outlined"
            color="primary"
            component="span"
            className={classes.button}
          >
            이미지 올리기
          </Button>
        </label>
        {localUrl.length > 0 ? (
          <div>
            <Card>
              <CardMedia
                className={classes.media}
                image={localUrl}
                title="Uploaded Image"
              />
            </Card>
          </div>
        ) : (
          <span />
        )}
      </div>
    </Paper>
  );
};

export default KakaoLink;

 

간단하게 이미지 업로드를 위한 input 태그와 이미지 올리기 버튼을 추가했고

 

	<input
          className={classes.input}
          accept="image/*"
          id="button-file"
          // KakaoLink not support multiple file sending yet.
          // multiple
          type="file"
          onChange={(e) => handleUpload(e)}
        />
        <label htmlFor="button-file">
          <Button
            variant="outlined"
            color="primary"
            component="span"
            className={classes.button}
          >
            이미지 올리기
          </Button>
        </label>

 

handleSubmit 함수에 이미지 부분을 추가할 State를 추가했습니다.

 

const [files, setFiles] = useState([]);
const [localUrl, setLocalUrl] = useState("");

 

files 스테이트는 이미지 파일의 Url을 저장하는 변수고

 

localUrl은 이미지 파일이 로드 됐을 때 화면 하단에 Card와 CardMedia를 이용해서 이미지를 화면에 뿌려주기 위한 변수입니다.

 

	{localUrl.length > 0 ? (
          <div>
            <Card>
              <CardMedia
                className={classes.media}
                image={localUrl}
                title="Uploaded Image"
              />
            </Card>
          </div>
        ) : (
          <span />
        )}

 

localUrl의 길이가 0 보다 클때 이미지를 보여준다는 로직입니다.

 

handleSubmit 함수에서 console.log부분을 주석처리했는데 주석처리를 치우고 테스트해보면 전체적인 로직은 쉽게 이해 할 수 있습니다.

 

그리고 카카오톡 API에서 이미지 여러장을 업로드 하는것을 시도해 봤는데 잘 안됩니다.

 

누가 아시는 분 있으시면 댓글로 알려주시면 감사하겠습니다.

 

이제 카카오톡 웹 버전이 완성되었습니다.

 

다음편에서는 Netlify에 웹호스팅 하는 방법을 알아보겠습니다.

그리드형