728x90

useCallback특정 함수를 렌더링할 때마다 새로 만들지 않고, 재사용하기 위해 사용하는 Hook이다. 

 

아래 코드에는 useCallback이 사용된 change와 insert라는 함수가 있다.

change의존성 배열이 빈 배열이고, insert의존성배열에 string과 stringList가 들어가 있다. 

 

const change = useCallback((e) => {
	setString(e.target.value); 
}, []); 

const insert = useCallback((e) => {
	const newList = stringList.slice(); 
    	newList.push(string); 
    	setStringList(newList); 
}, [string, stringList]); 

 

둘의 차이가 무엇이길래, 하나는 의존성 배열이 비어있고, 하나는 값이 포함되어 있을까?

 

change는 두번째 인자에 빈 배열을 주었기 때문에, 최초 렌더링 시에만 함수가 생성되고 이후에는 생성되지 않는다. 

 

insertstring과 stringList가 변경될 때만 함수를 재생성한다. 

 

즉, insert의 경우 string과 stringList가 변하면 함수도 재생성되어야 한다. 즉, 두 state에 의존적이다.

하지만, change는 state를 사용하지 않고, 단지 state를 변경하는 것이므로, state에 의존적이지 않다.

state가 변함에 따라 change 함수를 재생성해줄 필요는 없는 것이다.

 

정리하자면, 해당 함수에서 state를 사용한다면, 말그대로 state에 의존하는 함수이므로 두번째 인자의 배열 안에 state를 추가해줘야 하고,

state에 의존적이지 않은 함수라면 빈 배열로 넣어주면 된다.   

 

 

 

 

728x90
728x90

a 클릭 영역 늘리기

a(anchor)의 기본 display는 inline이다. 그래서 부모의 영역이 넓더라도(예를 들어, 부모의 width가 500px이라고 하자), a태그의 width가 200px이라면, 클릭할 수 있는 영역의 width는 200px 밖에 되지 않는다. 

 

하지만, anchor의 클릭 영역이 작으면 클릭하기가 불편하기 때문에, anchor가 부모의 영역만큼 차지하게 해주는 것이 좋다. 

 

block은 width 값을 따로 부여하지 않을 경우, 부모의 100%만큼 width가 설정된다. (inline-block은 아님)

 

그래서, 다음과 같이 anchor의 display를 block으로 바꾸어주면, a의 클릭 영역을 늘릴 수 있다. 

 

a {
  display: block;
}

 

next에서 router.push()와 Link의 차이 

결론부터 말하자면, SEO(검색엔진최적화)가 중요하다면 Link를 사용하는 게 좋다

 

Link를 사용함으로써, 전체 페이지가 reload 되어 overriden(SSR)된다. 즉, Link를 사용했을 때 검색엔진의 크롤러는 Link가 다른 페이지로 이동하기 위함을 알 수 있고 검색엔진 최적화에 좋다

 

router.push는 주로 이벤트 핸들러(ex. onClick)에서 사용된다고 한다. 그렇지만 단지 다른 페이지로 이동하기 위해서는 사용하지 않는 게 좋다.

 

즉, Link의 기능만으로 부족해서 router의 특정 기능이 필요한 게 아니라면 Link를 사용하는 게 좋다. 

 

참고링크) https://stackoverflow.com/questions/65086108/next-js-link-vs-router-push-vs-a-tag?openLinerExtension=true

 

 

 

 

 

   

728x90
728x90

axios.defaults.baseURL

axios 요청을 보낼 때 아래처럼 url을 적어주어야 한다. 그런데 'http://localhost:3065' 부분은 공통이라 계속 적어주어야 한다. 나중에 배포를 하게 되어 url이 바뀌게 되면, 모든 url 부분을 다 바꿔줘야 하는데 굉장히 귀찮은 일이다. 

 

function loginAPI(data) {
  return axios.post('http://localhost:3065/user/login', data);
}

function logoutAPI() {
  return axios.post('http://localhost:3065/user/logout');
}

function signUpAPI(data) {
  return axios.post('http://localhost:3065/user', data);
}

 

이럴 때, axios.defaults를 사용한다. 'axios.defaults'를 통해 모든 요청에 적용될 구성 기본 값을 지정할 수 있다.

 

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

 

나의 경우는 redux-saga를 사용했기 때문에 saga 폴더의 index.js 파일에서 baseURL을 지정해주었다. 이렇게 해주면, 나중에 배포 후에 baseURL이 변하더라도 이 부분만 변경해주면 된다.

 

axios.defaults.baseURL = 'http://localhost:3065';

 

findOne

로그인한 유저를 db에서 꺼내오고 싶을 때, findOne을 사용해준다. findOne테이블의 데이터를 하나만 가져온다는 뜻이다. 

 

const user = await User.findOne({ where: { id } });
728x90
728x90

쓰로틀링(throttling)과 디바운싱(debouncing). 이름은 꽤 들어봤는데, 언제, 왜 쓰는지 잘 몰라서 정리해보려고 합니다. 

 

쓰로틀링마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것입니다. 예를 들어, 스크롤을 올리거나 내릴 때, 스크롤을 움직이는 내내 함수가 호출되면 곤란하겠죠? 이럴 때, 일정 시간이 지나기 전에는 다시 호출하지 않는 쓰로틀링을 사용합니다. 

 

디바운싱연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것입니다.

 

예를 들어, 검색창에 '돌고래'라는 검색어를 입력할 때 한 글자 한 글자('ㄷ', '도', '돌' ...) 칠때마다 ajax 요청이 실행됩니다. 

더보기

ajax 요청 - 쉽게 말해 서버로 (여기서는) 검색어 데이터를 보내고, 해당하는 결과물을 받아오기 위한 요청이라고 생각하면 됩니다. 

이렇게 한 글자 한 글자에 대해 모두 요청이 가면, 유료 API를 사용하게 될 경우 심각한 비용 문제를 낳을 수 있습니다. 던지는 쿼리 하나 하나가 다 돈이기 때문입니다. 

 

그러면 '돌고래' 라는 검색어를 다 입력한 후에 ajax 요청을 보내는 것이 좋겠죠

 

어떻게 구현하는 게 좋을까요? '돌고래'를 입력할 때, '돌' 치고 한참 후에 '고' 이런 식으로 입력하지 않고, 대부분의 사람들은 한 번에 '돌고래'라고 입력하지 않을까요? 그러면 이렇게 검색어의 입력이 다 끝난 후에 ajax 요청을 보내면 됩니다. 

 

검색어를 입력할 때(input 이벤트 발생)마다 타이머를 설정합니다. 이때, 예를 들어, 200ms동안 입력이 없으면, 입력이 끝난 것으로 치고, 만약, 200ms 이전에 타자 입력이 발생하면 이전 타이머는 취소하고 다시 새로운 타이머를 설정하면 됩니다. 

 

let timer; 
document.querySeletor('#input').addEventListener('input', function(e){
	if(timer){clearTimeout(timer);}
   	timer = setTimeout(function(){ 
    		console.log('200ms가 지났으니, 이제 ajax 요청을 보냅니다.', e.target.value); 
    	}, 200); 
}); 

 

이렇게 설정하면, 매번 검색어를 입력할때마다 호출되지 않고, 더 이상 입력이 없을 때, ajax 요청을 보냅니다. 이게 디바운싱입니다. 

 

쓰로틀링은 보통 성능 문제 때문에 많이 사용한다고 합니다. 스크롤을 올리거나 내릴 때를 생각해보면, scroll 이벤트가 매우 많이 발생합니다. scroll 이벤트가 발생할 때 복잡한 작업을 실행하도록 설정했다면, scroll 이벤트가 매우 빈번하게 실행되기 때문에 렉이 엄청나게 걸릴 것입니다. 이럴 때 쓰로틀링을 걸어둡니다. 몇 초에 한 번, 혹은 몇 밀리초에 한 번씩만 실행되게 제한을 걸어 두는 것입니다.  

 

위에서 디바운싱으로 구현했던 ajax 검색을 쓰로틀링으로 대체해도 됩니다. 똑같이 200ms 제한을 걸어둡니다. 타이머가 설정되어 있으면 아무 동작도 하지 않고, 타이머가 없다면 타이머를 설정합니다. 타이머는 일정 시간 후에 스스로 해제하고, ajax 요청을 날리게 하면 됩니다. 

 

let timer;
document.querySelector('#input').addEventListener('input', function(e){
	if(!timer){
    		timer = setTimeout(function(){
        		timer = null; 
            		console.log('200ms가 지나면 ajax 요청', e.target.value); 
         	}, 200);
   	}
}); 

 

이렇게 설정하면 200ms마다 요청을 보내게 됩니다. 물론, 디바운싱으로 구현하는 게 요청을 덜 보내게되겠지만, 중간 중간 검색 결과를 보여주고 싶다면 쓰로틀링도 나쁘지 않은 방법인 것 같습니다. 

 

아래 링크를 참고하여 작성했습니다.

www.zerocho.com/category/JavaScript/post/59a8e9cb15ac0000182794fa

728x90
728x90

table width 고정, 자동 줄바꿈

table에서 width를 따로 설정했음에도 불구하고, 컨텐츠의 내용이 길어지니 아래와 같이 width가 계속해서 늘어났다

 

 

 

이를 방지하기 위해서는 테이블의 사이즈에 맞게, 컨텐츠가 알아서 자동 줄바꿈을 해야한다. 

 

word-break: break-all; 속성을 사용하면 아래와 같이 알아서 줄바꿈을 할 수 있다. 

 

table > tr > td{
    word-break: break-all;
}

 

 

 

word-break텍스트가 자신의 content-box 밖으로 overflow할 때 줄을 어떻게 바꿀지 정해주는 속성이다. 

728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

React 버튼 클릭 효과 유지하기

페이지 버튼이든 카테고리 버튼이든, 아래처럼 버튼을 클릭했을 때 style을 유지하고 싶은 경우가 있다.

 

 

 

클릭 효과를 유지하기 위한 방법은 여러가지가 있겠지만, 나는 클릭 이벤트 리스너를 붙여 classList를 추가해주고 지우는 방식을 택했다.  

 

// 각 페이지 버튼마다 'pages'라는 클래스를 가지고 있다. 
const pages = document.getElementsByClassName('pages');  
    
const clickHandler = (e) => {
    // 처음 로딩되었을 때는 1번 페이지에 선택되어 있어야 하기 때문에, 
    // 1번 페이지 버튼은 'first-page' 클래스를 가지고 있다. 
    // 만약, 클릭된 클래스 리스트에 first-page가 없다면 첫번째 페이지는 
    // 계속 선택되어 있을 필요가 없기 때문에 remove 해준다. 
    if (!e.target.classList.contains("first-page")) {
        pages[1].classList.remove("first-page");
    }
    
	// 모든 페이지 버튼에서 활성 클래스인 'active-page'를 지워 초기화 해준다. 
    for (var i = 0; i < pages.length; i++) {
        pages[i].classList.remove("active-page");
    }
    // 현재 클릭된 버튼에만 활성 클래스인 'active-page'를 붙여준다. 
    e.target.classList.add("active-page");
}

// 모든 pages 클래스를 가진 요소에 클릭 이벤트 리스너를 붙여주었다. 
// 그래서 클릭 이벤트가 발생하면, clickHandler 함수가 실행된다. 
const init= () => {
    for (let i = 0; i < pages.length; i++) {
      pages[i].addEventListener("click", clickHandler);
    }
}

init();

 

 

  • addEventListener - 특정 이벤트가 발생했을 때 특정 함수를 실행할 수 있게 해주는 기능. 여기서는 click 이벤트가 발생했을 때 clickHandler 함수를 실행해준다. 
  • document.getElementsByClassName(className) - HTML에서 있는 요소 중, 주어진 클래스명을 가진 모든 요소들을 배열에 담아 리턴해준다. 
  • e.target - 이벤트 객체의 타깃, 예를 들어, 2번 페이지를 눌렀다면 <li class="pages active-page" style="user-select: auto;">2</li>가 리턴된다. 
728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

React 게시판 최신순으로 정렬

아래와 같이 게시글들이 최신순으로 정렬되기 바란다면 어떻게 해야할까? 

 

 

 

서버에서 그대로 데이터를 받아온 뒤, 컴포넌트에서 reverse를 하려고 하면 리렌더링될때마다 reverse 될 수있다

 

그래서 Load User action에 성공한 뒤, data를 Users에 담을 때 아예 reverse()를 하여 담으면 된다. 

 

728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

React에서 map 사용 시 key를 설정해줘야 하는 이유는

아래와 같이 컴포넌트를 반복하여 렌더링해줘야 하는 경우, map을 사용한다. 

 

 

 

그런데 이 때, 배열 내부의 엘리먼트에 key 값을 지정해주지 않으면, 'Each child in a list should have a unique key prop.' 경고문이 뜨게 된다. 

 

 

리액트 공식문서에서는, key어떤 아이템이 변화되거나 추가 또는 삭제되었는지를 알아차리기 위해 필요하다고 한다. 

 

리액트는 state에서 변경된 부분만 찾아 리렌더링해주는데, 이때 가상 dom과 비교하여 바뀐 부분만 리렌더링해준다. 만약, 배열에 요소가 추가되었다면 배열 전체가 변경된 것이라고 생각하고 전체를 리렌더링 하게 된다. 마지막 요소만 변경되었으니, 전체를 리렌더링할 필요는 없는데도 말이다. 

 

그래서 배열 내부 엘리먼트에 key를 지정해줘야 한다. key 값을 지정해주면 리액트는 배열에 추가된 요소에 대해서만 리렌더링한다. 즉, key는 배열의 어떤 원소에 변동이 있었는지 알아내고자 할 때 사용되는 것이다. 따라서, 데이터가 가진 고유의 값을 key로 설정해야 한다. 

 

const PostList = ({ posts }) => {
    return(
        posts.map((post) => {
            return(
                <tr key={post.id}>
                    <td>{post.id}</td>
                    <td>{post.nickname}</td>
                    <td>{post.role}</td>
                    <td>{post.phone}</td>
                </tr>
            );
        })
    ); 
}

export default PostList; 

 

 

key 값으로 index를 설정하는 경우도 있는데, 어떤 경우에는 key 설정의 장점을 하나도 살리지 못하고 배열 전체가 리렌더링 될 수 있다.  배열에 첫 번째 위치에 새로운 element를 넣게 되면, 기존 배열의 index가 하나씩 밀리게 된다. 그럼 결국, 배열 전체가 변경되었다고 생각하기 때문에 배열 전체가 리렌더링 되는 것이다. 

 

// 기존 배열 
key 0, {id: 0, title: 'hello', content: 'olleh'}, 
key 1, {id: 1, title: 'my', content: 'ym'}, 
key 2, {id: 2, title: 'name', content: 'eman'}, 

// 배열 첫 번째 위치에 새로 element를 넣음 
key 0, {id: 3, title: 'is', content: 'si'}, 
key 1, {id: 0, title: 'hello', content: 'olleh'}, 
key 2, {id: 1, title: 'my', content: 'ym'}, 
key 3, {id: 2, title: 'name', content: 'eman'}, 

 

그래서, element를 고유하게 식별할 수 있는 unique한 값을 key로 설정해주는 게 좋다. 가장 사용하기 쉬운 것은 Database의 id이다. AUTO_INCREMENT된 id 값을 사용하거나, id가 아니라도 unique한 값이라면 key로 사용하면 된다. 

 

Robin Pokorny에 의하면, 아래 3가지를 만족하는 경우 index를 key로 써도 된다고 한다. (key 값으로 index를 꼭 설정해야겠다면, 아래의 조건을 확인하자)

  1. 배열과 각 요소가 static이며 computed 되지 않고, 변하지 않아야 한다. 
  2. 데이터 내부에 id로 쓸만한 unique 값이 없다. 
  3. 데이터가 결코 reordered 또는 filtered 되지 않는다. 
728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

Public key retrieval is not allowed 에러(allowPublicKeyRetrieval=true 추가해도 에러가 발생한다면)

지난주에 'Public key retrieval is not allowed 에러 해결 방법'에 관한 글을 작성했다. 이 에러를 해결하기 위해서는 jdbc url allowPublicKeyRetrieval=true 설정을 추가하면 되었다. 

 

그런데 오늘 또 아래와 같이 'Public key retrieval is not allowed 에러'가 발생하였다. 

 

 

이미 jdbc urlallowPublicKeyRetrieval=true 설정이 되어 있었지만 'useSSL=false'가 가장 끝에 설정되어 있었다. 그래서 'allowPublicKeyRetrieval=true'를 가장 마지막 자리로 옮겨주었더니 에러가 해결되었다. 

 

// 수정 전
spring.datasource.url=jdbc:mysql://localhost:3306/db-name?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false

// 수정 후
spring.datasource.url=jdbc:mysql://localhost:3306/db-name?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다.

JavaScript 정규표현식

MDN에서는 정규표현식문자열에 나타나는 특정 문자 조합과 대응시키기 위해 사용되는 패턴이라고 소개한다. 

 

예를 들어, '/chat/write'에서 '/chat'만 추출하고 싶을 때 정규표현식을 사용한다. 즉 문자열에서 특정 패턴의 문자열만 추출하고 싶을 때 사용한다고 생각하면 된다.

 

정규표현식은 내가 찾고자 하는 패턴을 표현해주면 된다. 모두 외우고 있을 수 없기 때문에 그때그때 정규표현식 테스트 사이트(regexr.com/)에서 찾아보면 된다. 

 

나는 location이 '/chat/write'인 페이지에서, '목록으로'라는 버튼을 클릭했을 때 '/chat/list'로 라우팅되기를 원했기 때문에 '/chat/write'에서 '/chat'만 추출하고자 했다. 그래서 '/'로 시작하는 단어 중 첫번째 것만 추출하면 되었다. 

 

정규표현식은 '/'로 감싸야 한다. 즉, /내가 찾고자 하는 패턴/ 으로 표현해준다. 나는 현재 /와 영어 단어 조합으로 이루어진 패턴을 추출하고 싶으므로, 두 가지 조건을 모두 포함하기 위해 '()'로 묶는다. /에 해당하는 문자열을 찾아야 하기 때문에 대괄호 안에서 감싼다. [/] 이 뒤에 영어 단어(한 글자 아님)가 와야 하므로 \w+로 표현한다. 즉 합치면, /([/]\w+)/가 된다. 

 

이제 문자열에서 이 패턴과 매치하는 단어를 뽑아주면 된다. 이럴 때 match()를 사용한다. 이 때, match()배열을 리턴하므로 0번째 인덱스에 우리가 찾고자 하는 단어가 들어가 있다. 그래서 아래와 같이 작성해주면 된다. 

 

const location = useLocation().pathname; // ex) /chat/list 
const upperLocation = location.match(/[/]\w+/)[0]; // ex) /chat

// 클릭했을 때 /chat/write로 라우팅 되기를 원한다면 아래와 같이 표현해주면 된다.
<button onClick={() => history.push(`${upperLocation}/write`)}>글쓰기</button>

 

 

728x90

+ Recent posts