백엔드를 구현하다보니, 결국 프론트에서 DB까지의 징검다리 역할을 하는 것만 같았다.

그리고 모든 작업은 프론트에서 가능해보였다.

 

그래서 궁금해졌다.

왜 프론트에서 DB에 직접적으로 접근하지 않을까?

 

결과부터 말하자면, 전부 가능한 것이었다. 그러나 분리되어야만 하는 이유가 있었다.

  • 보안과 관련된 것들이 전부 사용자에게 노출된다.
  • 알고리즘이 노출된다.
  • DB 역시 노출되어서, 다른 사람들이 마음대로 접근할 수 있다.
  • 서버에서 작업들을 분산적으로 처리하지 않고, 브라우저로 한 번에 쏴서 모든 역할을 전가한다면 브라우저의 부담이 엄청나진다. 등등 …

생각해보면 1, 2, 3번은 간단히 생각해봐도 떠올릴 수 있는 것들이었다.

근데 보안을 생각하다보니, 배포할 땐 환경변수를 어떻게 관리하는지 궁금해졌다.

오늘 찾아보고 글 작성해서 링크 걸어야겠다.

 

Is it possible to directly connect frontend to the database?

 

Is it possible to directly connect frontend to the database?

I came across this joke the other day. Someone on the internet was asking why do we need a backend, just connect the frontend to the database. It seemed funny at first, but really is it possible to

stackoverflow.com

Why do we need a back-end in web development? Can't the front-end directly send requests to the database?

 

Why do we need a back-end in web development? Can't the front-end directly send requests to the database?

Answer (1 of 6): While you could theoretically send requests directly from the front-end to the database that’s something of a gross oversimplification. Let’s look at some of the problems: Security is the biggest and it alone should stop the line of th

www.quora.com

 

카카오 쇼핑하기를 구현하면서, 체크박스를 어떻게 관리하는 것이 효과적일까? 하는 생각이 들었다.

체크박스를 사용하는 상황은 다음과 같았다.

- 전체 동의의 체크 상태에 따라서 나머지 2개도 체크가 적용 / 해제
- 나머지 2개의 체크 상태에 따라서 전체 동의도 체크 적용 / 해지

- 전체 동의가 되어있지 않다면 결제하기 버튼 클릭 불가

대충 이 정도의 구현이 필요한 상황이었다.

 

구현만 놓고 보면 매우 간단하지만, 어떻게 구현하는 게 더 효과적일까 하는 궁금증이 있었다.

 

첫 번째로 떠올린 것은 다음과 같았다. 

  const checkAllRef = useRef();
  const checkPaymentRef = useRef();
  const checkPrivacyRef = useRef();

  const handleCheckAllChange = () => {
    const checkAllChecked = checkAllRef.current.checked;
    checkPaymentRef.current.checked = checkAllChecked;
    checkPrivacyRef.current.checked = checkAllChecked;
  }

  const handleCheckChange = (e) => {
    checkAllRef.current.checked = checkPaymentRef.current.checked && checkPrivacyRef.current.checked;
  }
  
  /* 결제 버튼 */
  const handleOnClick = async () => {
    if(checkAllRef.current.checked === false) {
      alert("약관에 동의해주세요.");
      return;
    }
    try {
      const res = await orderProducts();
    } catch (e) {
      alert('장바구니가 비어있는지 확인해주세요');
    }
  }
  
  /* ... */
  
  <div className="border">
    <div className="font-bold text-xl border-b py-5 px-3">
      <input type="checkbox" ref={checkAllRef} id="checkAll" onChange={handleCheckAllChange} />
      <label htmlFor="checkAll">전체동의</label>
    </div>
    <div className="p-3">
      <input type="checkbox" ref={checkPaymentRef} id="checkPayment" onChange={handleCheckChange} />
      <label htmlFor="checkPayment">구매조건 확인 및 결제 진행 동의</label>
    </div>
    <div className="p-3">
      <input type="checkbox" ref={checkPrivacyRef} id="checkPrivacy" onChange={handleCheckChange} />
      <label htmlFor="checkPrivacy">개인정보 제3자 제공 동의</label>
    </div>
  </div>

useRef로 요소를 하나하나 찍어와서 관리하는 방법이다.

체크박스 변경과정에서 리랜더링은 이뤄지지 않는다.

당연히 잘 작동했으나, 체크박스 하나가 더 늘어난다면 추가해야 할 코드가 꽤 늘어난다는 문제가 있다. (handleCheckChange, handleCheckAllChange가 더러워질 수 있다.)

 

그래서 확장성에 좋은 다른 방법을 떠올려봤다.

const [checkboxes, setCheckboxes] = useState([
    { id: "checkAll", label: "전체동의", checked: false },
    { id: "checkPayment", label: "구매조건 확인 및 결제 진행 동의", checked: false },
    { id: "checkPrivacy", label: "개인정보 제3자 제공 동의", checked: false },
    // 필요에 따라 체크박스 추가 가능
  ]);

  const handleCheckAllChange = (event) => {
    const checkAllChecked = event.target.checked;
    setCheckboxes((prevCheckboxes) =>
      prevCheckboxes.map((checkbox) => ({ ...checkbox, checked: checkAllChecked }))
    );
  };

  const handleCheckChange = (event) => {
    const { id, checked } = event.target;
    setCheckboxes((prevCheckboxes) =>
      prevCheckboxes.map((checkbox) => (checkbox.id === id ? { ...checkbox, checked } : checkbox))
    );
  };

  const handleOnClick = async () => {
    if (!checkboxes[0].checked) {
      alert("약관에 동의해주세요.");
      return;
    }
    try {
      const res = await orderProducts();
    } catch (e) {
      alert("장바구니가 비어있는지 확인해주세요");
    }
  };
  
  /* ... */
  
   <div className="border">
    <div className="font-bold text-xl border-b py-5 px-3">
      <input
        type="checkbox"
        id={checkboxes[0].id}
        checked={checkboxes[0].checked}
        onChange={handleCheckAllChange}
      />
      <label htmlFor={checkboxes[0].id}>{checkboxes[0].label}</label>
    </div>
    {checkboxes.slice(1).map((checkbox) => (
      <div key={checkbox.id} className="p-3">
        <input
          type="checkbox"
          id={checkbox.id}
          checked={checkbox.checked}
          onChange={handleCheckChange}
        />
        <label htmlFor={checkbox.id}>{checkbox.label}</label>
      </div>
    ))}
  </div>

첫 번째 방법과 작동 방식은 거의 유사하다.

 

체크박스 자체를 상태로 관리한 점이 좋았다.

체크박스를 컴포넌트로 바꿔서 상태의 초기값을 props로 넘겨줘서 관리하기에 쉬워지는 장점이 있었다.

반복되는 map 함수에서 가독성이 아주 살짝 떨어질 수 있다는 단점이 있지만, 항목이 늘어날 경우에도 대응이 매~~우 쉽다는 장점이 있다.

그러나 리랜더링이 매번 이뤄진다.

 

나는 첫 번째 방법으로 완성했다.

결제 과정에서 체크해야할 항목은 딱히 변화하지 않을 것 같다는 것이 이유였다.

 

내 결정이 옳았는지는 모른다.

이번주 코드리뷰에서 멘토님께 여쭤보는 편이 좋을 것 같다.

발단

카카오테크캠퍼스 울 팀원 분의 과제를 도와주는데, 뭔가 이상한 점이 있었다.

useQuery에 사용하는 api 호출 함수에서 async await을 사용하지 않는 것이었다. 그리고 정상적으로 작동하고 있었다.

문제

// 1번
export const apiCall = (method, path, data) => {
    return axios({method, url: API_PREFIX + path, data})
        .then(resp => resp.data)
};

// 2번
export const apiCall = async (method, path, data) => {
    const response = await axios({method, url: API_PREFIX + path, data})
    return response.data
};

제가 여태 알던 것은 위의 두 가지와 같은 형태가 리액트 쿼리의 함수로 들어가야한다고 알고 있었는데, 밑의 코드 또한 리액트 쿼리에 의해 자동으로 정상작동 한다.. 라고 이해하면 맞을까요??

export const apiCall = (method, path, data) => {
    const response = axios({method, url: API_PREFIX + path, data})
    return response
};

위의 내용은 내가 2단계 질문 방에 올린 내용이었다.

나는 axios의 요청을 기다리다가 response에 undefined 값이 들어와서 그대로 리턴될 줄 알았다.

내 생각엔 비동기적으로 처리하는 과정에서 axios 처리는 따로 하고, 그렇다면 response는 아직 undefined일 테고, 정상적인 값이 반환되면 안 되지 않나?
리액트 쿼리가 undefined를 처리하는게 가능한가? 싶었다.
https://stackoverflow.com/questions/70388520/why-are-async-api-calls-necessary-with-react-query

 

Why are async api calls necessary with react-query?

Every piece of documentation shows async calls being used with react-query, but I am a little confused why these are necessary, as the following code works with or without async/await: export const

stackoverflow.com

스택오버플로우에서 비슷한 글을 찾았으나, 역시 나처럼 사용하고 있었다. 나는 내가 맞는 줄 알았다.

해결

axios에 의해 리턴되는 값은 Pending 상태의 Promise 였다.

async await을 통해 비동기 처리를 해주면 해당 값이 제대로 출력되며, 비동기 처리를 해주지 않아도 Promise를 반환하는 것이었다!!

그리고 Pending 상태의 Promise를 React Query에서 처리해주는 것이었다.

 

axios가 프로미스를 리턴한다는 것은 어떻게 보면 매우 당연한 것이었는데, 프로미스에 대한 이해가 많이 떨어졌던 것 같다.

이젠 완벽 이해했다. return new Promise((resolve, reject) => {...})의 형태도 왜 이렇게 생긴 건지 이해해버렸다....... 매우 값진 하루이다.

+ Recent posts