Project Fake Search 3-2登録と検索ページ設定の実施


開始します。


今回ブログの検索ページ設定部分をするのが一番難しい部分です.いくつかのライブラリを使用していますが、画像のアップロード、ドラッグ&ドロップによる順序変更、各部のJSONタイプのデータの追加/削除など、1ページで実現する必要がある機能が多いので、頭の中で考えているコードが正常に機能しているかどうか心配です.非同期リクエストに関連する部分で予期せぬエラーが発生しましたが、幸いなことにこれらのエラーが解決され、機能の実装が完了しました.

ログイン機能の実装

//Login.js
import React, { useEffect, useRef } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUserPlus } from '@fortawesome/free-solid-svg-icons';
import { GoogleLogin } from 'react-google-login';
import axios from 'axios';
import './Login.css';

export default function Login({ login, loginModal }) {
  const naverRef = useRef();

  const initializeNaverLogin = () => {
    const naverScript = document.createElement('script');
    naverScript.src =
      'https://static.nid.naver.com/js/naveridlogin_js_sdk_2.0.2.js';
    naverScript.type = 'text/javascript';
    document.head.appendChild(naverScript);
    naverScript.onload = () => {
      const naverLogin = new window.naver.LoginWithNaverId({
        clientId: process.env.REACT_APP_NAVER_CLIENT_ID,
        callbackUrl: process.env.REACT_APP_REDIRECT_URI,
        isPopup: false, 
        loginButton: { color: 'white', type: 3, height: '47' }, 
      });
      naverLogin.init();
      naverLogin.logout();
    };
  };

  const kakaoLoginHandler = () => {
    const { Kakao } = window;
    Kakao.Auth.login({
      scope: '',
      success: () => {
        Kakao.API.request({
          url: '/v2/user/me',
          success: async function (res) {
            axios
              .post(
                `${process.env.REACT_APP_SERVER_API}/users/kakao-login`,
                {
                  identification: res.id,
                },
                { withCredentials: true }
              )
              .then(() => {
                window.location.replace('/');
              });
          },
        });
      },
    });
  };

  const onSuccess = async (response) => {
    const { googleId } = response;
    axios
      .post(
        `${process.env.REACT_APP_SERVER_API}/users/google-login`,
        {
          identification: googleId,
        },
        { withCredentials: true }
      )
      .then(() => {
        window.location.replace('/');
      });
  };

  const onFailure = (error) => {
    console.log(error);
  };

  const guestLogin = () => {
    axios
      .post(`${process.env.REACT_APP_SERVER_API}/users/guest-login`, '', {
        withCredentials: true,
      })
      .then((res) => {
        window.location.replace('/');
      })
      .catch((err) => {
        console.log(err);
      });
  };

  useEffect(() => {
    initializeNaverLogin();
  }, []);

  const handleClick = () => {
    naverRef.current.children[0].click();
  };

  return (
    <div className={loginModal ? 'login-container' : 'hidden'}>
      <div className='box-login' ref={login}>
        <div className='box-login-btn'>
          <div className='btn-naver'>
            <div ref={naverRef} id='naverIdLogin' />
          </div>
          <img
            src='img/btn-login-naver.png'
            alt='naver-login'
            onClick={handleClick}
            className='btn-naver'
          />
          <img
            src='img/btn-login-kakao.png'
            alt='naver-kakao'
            className='btn-kakao'
            onClick={kakaoLoginHandler}
          />
          <GoogleLogin
            clientId={process.env.REACT_APP_GOOGLE_CLIENT_ID}
            render={(renderProps) => (
              <img
                src='img/btn-login-google.png'
                alt='naver-google'
                className='btn-google'
                onClick={renderProps.onClick}
              />
            )}
            responseType={'id_token'}
            onSuccess={onSuccess}
            onFailure={onFailure}
          />
          <div className='btn-guest' onClick={guestLogin}>
            <FontAwesomeIcon icon={faUserPlus} className='icon-user' />
            <span className='text-guest'>게스트 로그인</span>
          </div>
        </div>
      </div>
    </div>
  );
}
//게스트 로그인 응답
const { User } = require('../../models');
const jwt = require('jsonwebtoken')

module.exports = async (req, res) => {
  try {
    let duplication = true;
    let randomSet = '';
    while (duplication) {
      randomSet = Math.random().toString(36).slice(2, 15);
      const check = await User.findOne({
          where : { identification: randomSet }
      })
      duplication = !!check;
      console.log(duplication);
    }
      const user = await User.create({
        identification : randomSet,
        oauth: 'guest',
        siteName: 'FAKESEARCH',
        themeColor: '#2260FF'
      })
      const accessToken = jwt.sign(
        user.dataValues,
        process.env.ACCESS_SECRET,
        { expiresIn: "7d" }
      );
      res
        .cookie("accessToken", accessToken)
        .status(201)
        .end();

  } catch (err) {
		...
  };

ゲスト登録もソーシャル登録(NAVER,KACA)も以前のプロジェクトで実施されたことがあるので、実施は難しくなく、Googleは初めてですが、反応で使えるモジュールがあるので実現しやすいです.ゲストログインの場合、サーバはランダムに文字列を生成し、既存のユーザであるかどうかを確認し、ユーザ情報を生成し、ログイン時にデータを削除することによって実現する.
ホームページ上でstateに従ってログインモードウィンドウを表示するために、{modalLogin&&<Login/>}という形式でコードを記述したが、上のgif画像のようにレンダリングに時間がかかるというエラーがあったため、className変更時にCSSのdisplay:none設定を変更して解決した.

検索ページの設定



画像をアップロード

//이미지 업로드 요청
const onDrop = async (pictureFiles) => {
    const body = new FormData();
    body.append('files', pictureFiles[0]);
    const res = await axios.post(
      `${process.env.REACT_APP_SERVER_API}/post/upload_files`,
      body,
      {
        headers: { 'Content-Type': 'multipart/form-data' },
      }
    );
    dispatch(
      changeProfile({
        profileImg: `${process.env.REACT_APP_SERVER_API}/${res.data.filename}`,
      })
    );
  };
//index.js(서버)
		...
var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "public/");
  },
  filename: function (req, file, cb) {
    let ext = file.originalname.split(".");
    ext = ext[ext.length - 1];
    cb(null, `${Date.now()}.${ext}`);
  },
});
		...
const upload = multer({ storage: storage });
app.use([express.static("public"), upload.array("files")]);
		...
//이미지 업로드 응답
module.exports = (req, res) => {
    try {
        console.log(req.files)
      if (req.files.length > 0) {
        res.json(req.files[0]);
      }
    }
    catch (err) {
		...
    }
  };
すべての部分(プロファイル、ニュース、画像、音楽)から画像をアップロードできる機能が必要であるため、react-images-uploadというライブラリを使用して、画像を転送できるようにコンポーネントを個別に作成しました.このライブラリの機能は、画像の拡張と最大サイズを制限し、プレビュー機能を提供しますが、プレビューは必要な形では実現しにくいため、単独で作成します.react-images-uploadは、クライアントからファイルをパラメータとして渡す機能のみを提供するため、サーバはイメージを格納し、クライアントが変更したファイル名を再転送するために個別の部分を設定します.
最初に行われた多値項目では,ckeditorとmulterを用いて画像ロードなしを実現したことがあるため,参照してコードを記述した.ファイルの最大サイズを制限する部分はライブラリで提供されるため、個別に設定するのではなく、ファイル名の重複を回避し、共通フォルダに画像を格納し、ファイル名をクライアントに再渡すために個別の部分を設定します.

レイアウト順序の変更(Drag&Drop)


react-beautiful-dndを使用してドラッグアンドドロップ機能を実現し、検索語を異なる節順で検索語に変更するたびに、マッピング表示の節コンポーネントによって生成されるエラーが無限数に発生します.
最初はなぜエラーが発生したのか分かりませんでしたが、各部分のリピータが別々なので、順番に並べ替えると各部分のデータが変わるので、以前はデータを保持したままマッピングを実行したことによるエラーと考えられていました.
Reducerを1つに統一するだけで済むのですが、すでに各部分のデータ修正、追加、削除機能が実現されており、4つのReducerを統合するとデータの管理が難しく、後続の部分を追加するのも面倒なので別の方法が必要です.
解決策は思ったより簡単で、resetDragのstateで検索語が変わるたびに、データをインポートする前にドラッグした部分が消えてしまい({!resetDrag && (섹션 설정 관련 부분)})、データを受け取るとresetDragがfalseになり、再表示で解決します.
単語を変えるごとに、それぞれの部分が点滅するにつれて変化するので、プレイヤーの立場から見ても、単語によって部分的なデータが変わったかどうかは分かりやすいです.
		...
export default function SearchData({ themeColor }) {
const [resetDrag, setResetDrag] = useState(false);
		...
  const selectSearchData = async (e) => {
    setSelected(e);
    setResetDrag(true);
    setOpenProfile(false);
    setOpenNews(false);
    setOpenImage(false);
    setOpenMusic(false);
    const res = await axios.get(
      `${process.env.REACT_APP_SERVER_API}/search/word`,
      {
        params: { word: e.value },
        withCredentials: true,
      }
    );
    dispatch(changeProfile(res.data.profile));
    dispatch(changeNews(res.data.news));
    dispatch(changeImage(res.data.image));
    dispatch(changeMusic(res.data.music));
    setResetDrag(false);
  };
		...
  return (
    <div className='searchdata-container'>
		...
      <div className='box-section'>
		...
        <div className='setting-section'>
		...
          {!resetDrag && (각 섹션의 설정 컴포넌트가 보여지는 부분)}
  		...
    </div>
  );
}

の最後の部分


最も厄介な部分の機能実装が完了すると,実際の検索ページを実装するだけでよいため,今後いつプロジェクトを完了できるか予測できる.この部分では,JavaScriptがどのように機能しているかを考え,論理を記述すべきであると感じる.非同期処理を正しく理解するためには,関連知識を深く学ぶべきであると考えられる.