반응형

잘 납품되었던 프로젝트 뼈대를 가져와서 다른 프로젝트를 또 만들고 만들고... 이렇게 하다보니 이전에 배웠던 스프링 개념도 많이 흐릿해지고 헷갈리게 되는것 같습니다.

시간이 지나면서 기존 개념도 많이 잊어먹었고, 요구사항에 맞게 스프링 설정을 하면서 어려움을 겪는 느낌이 들어 다시한번 공부를 하고 정리를 해보고자 합니다.(기초지만 역시 주기적으로 다시 봐주고 정리할 필요가 있는 것 같습니다.😎)

 

구동순서

1. was(톰캣 등)를 통해 프로젝트를 구동을 하게되면 해당 프로젝트 내부의 web.xml을 로드합니다.

web.xml은 서블릿 배포 기술자로 DD(Deployment Descriptor)라고도 불립니다. WEB-INF 디렉토리에 존재하며 웹 어플리케이션의 설정을 구성하고 있습니다.

webapp-WEB-INF에 위치합니다.

 

 

2. web.xml 파일을 로드하면서 Servlet Container가 구동됩니다.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:egovframework/spring/context-*.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/egovframework/springmvc/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

<welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

 

3. Servlet Container가 구동되면서 ContextLoaderListener클래스가 자동으로 메모리에 생성됩니다. 

ContextLoaderListener는 서블릿 이전에 서블릿을 초기화 시켜주며 contextConfigLocation을 설정파일들의 위치를 지정시켜줍니다.

 

4. contextConfigLocation는 공통으로 사용할 의존성 설정파일들을 지정하며, <param-value>부분에 파일 경로를 입력해주면 됩니다.

저는 전자정부프레임워크를 사용하면서 기본 위치가 'egovframework/springmvc/'으로 되어있고, dispatcher-servlet.xml읽도록 설정했습니다.

 

5. dispatcher-servlet.xml을 읽어서 새로운 Spring Container를 생성하고, Controller 객체를 메모리에 올립니다.

 

6. dispatcher-servlet.xml(servlet-context.xml) 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:task="http://www.springframework.org/schema/task"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
                http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                http://www.springframework.org/schema/task
                http://www.springframework.org/schema/task/spring-task-3.0.xsd">
                
    <task:annotation-driven></task:annotation-driven>

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <context:component-scan base-package="egovframework"/>

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/jsp/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
</beans>

- annotation-driven: xml이 길고 복잡해지면서 대안으로 나타났습니다. 클래스, 메소드 등에 @를 달아 기능을 수행할 수 있도록 동작하기 위해 설정합니다. (xml Bean 등록이 더 우선순위가 높습니다.)

- mvc:annotation-driven: @Controller @RequestMapping 등 컨트롤러에서 해당하는 기능을 설정할 수 있게 해줍니다. 

- task:annotation-driven: @Scheduled 어노테이션을 사용을 위해 처리합니다.(스케줄러 관련)

- org.springframework.web.servlet.view.InternalResourceViewResolver: 컨트롤러가 모델을 리턴하고 DisapatcherServlet이 해당하는 파일을 처리할 때 필요한 정보를 기술하는 태그 접미사가 'WEB-INF/jsp'이면서 '.jsp'로 끝나는 파일들을 찾습니다. 

WEB-INF/jsp/login/login.jsp

- context:component-scan: 특정 패키지의 클래스들을 스캔하여 어노테이션을 확인하고 bean 등록을 한다. @Component @Controller @Service @Repository등이 클래스에 설정해야 등록된다.

 

 

 

 

 

웹 요청 동작순서

1. 사용자가 브라우저를 통해 주소를 입력합니다. - ( http://localhost/login.do )

web.xml에서 설정했던 정보에 맞는 요청일 경우 DispatcherServlet이 해당 요청을 먼저 가로채서 적합한 Controller에 매핑을 해줍니다.

 

2. <servlet-mapping/>부분이 있는데, 설정된 <servlet-name/>에 해당하는 매핑 설정을 할 수 있습니다.

예를들어 <url-pattern/>부분이 '/'으로 처리되어 있다면 모든 요청을 가로챕니다. 하지만 설정부분은 '*.do'로 되어있어서 'login.do'와 같은 형태만 적용이 됩니다.

 

3. 가로챈 매핑정보를 통해 처리할 Controller를 찾은 후, 적용된 코드에 따라 기능이 처리된다.

   3-1. Controller(mapping) -> View

   3-2. Controller(mapping) -> Service -> DAO -> mapper -> DB -> DAO -> Service -> Controller -> Return

 

4. 3-1과 같은 처리가 수행되면 ViewResolver에 의해 해당하는 view를 반환한다.

<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/jsp/" />
    <beans:property name="suffix" value=".jsp" />
</beans:bean>

dispatcher-servlet.xml에 설정된 ViewResolver부분에 맞는 prefix, suffix의 정보를 토대로 view페이지를 제공합니다.

(저는 tiles3를 적용해서 다르게 설정이 되어있습니다.)

@RequestMapping(value = "/login.do")
public ModelAndView login(ModelAndView mv) {
    mv.setViewName("login/login");
    return mv;
}

http://localhost/login.do -> '/WEB-INF/jsp/login/login.jsp'

 

5. 해당하는 viewDispatcherServlet에 보내면 DispatcherServlet은 클라이언트에게 데이터를 전송한다.

이때, 모델의 속성값이 있으면 같이 전송된다. (해당하는 값은 .jsp파일에서 ${변수명}을 통해 사용이 가능하다)

 

 

 

반응형
반응형

웹 개발을 진행하다보면 웹 접근성이라는 말이 나옵니다.

장애인분들이나 고령자분들도 웹 사이트를 사용하는데 불편함이 없도록 차별없게 기능을 제공하는 것을 말하는데, 탭키를 통한 모든 메뉴를 이동하는 기능이라던지 색맹, 색약분들을 위한 디자인 개선이라던지 다양한 방법으로 제공이 되도록 보장하는것을 말합니다.

 

그럼 개발자분들은 이런분들의 입장이 되어 개발을 진행해야하는데, 사실 내가 개발중인 사이트가 색맹, 색약인 분들이 봤을때, 어떤식으로 보일지 가늠이 안되는 경우가 있는데, 이럴때 크롬의 자체 지원 기능으로 간접적으로 경험을 하고 개발을 진행할 수 있습니다.

 

색맹모드 설정방법

1. F12 키등을 통해 관리자 도구를 실행합니다.

2. 상단 더보기(점 3개) - More tools - Rendering을 차례대로 누릅니다.

 

3. 추가된 Rendering 탭을 위로 올립니다.

 

4. 다양한 옵션들 중 Emulate vision deficiencies 찾습니다.(색맹 에뮬레이션)

* 관리자도구 옵션을 한글로 변경하여 보시면 설정이 더 편하게 처리 가능합니다.

변경하는방법은 최하단에 추가로 기재하였습니다.

 

5. 변경하고 싶은 옵션을 선택합니다.

  • Blurred vision : 흐릿하게 변경해줍니다. 근시효과
  • Protanopia : 적색맹
  • Deuteranopia : 녹색맹
  • Tritanopia : 청황색맹
  • Achromatopsia : 색맹

 

 

적용 테스트

기존 화면 색맹 모드(Achromatopsia)

파란계열 주황계열로 차트 구분을 해두었지만 색맹인분들에게는 거의 구분이 되지 않는다.

이를 해결하기 위해 차트에 질감처리를 추가하였다.

 

 

-색 구분을 위한 질감처리 추가

기존 화면(질감처리 추가) 색맹 모드(Achromatopsia)

질감 처리 추가로 2개의 비교 차트가 좀 더 구분되는 모습을 볼 수 있습니다.

 

 

 

크롬 브라우저 한글로 언어 변경하기

1. F12 관리자도구 열기

2. 톱니바퀴(환경설정)

3. 환경설정 - 언어 - 한국어 선택

반응형
반응형

어떤 블로거분 글을 보다가 너무 이쁘게 꾸며져 있어서 이건 뭘까하고 개발자 정신이 발동되어 f12부터 눌렀는데 자꾸 티스토리 페이지로 강제 이동이 되었다...

 

오잉 뭐지?! 하고 또 시도해봤는데.. 2차 팅...

이번엔 컨트롤 쉬프트 c로 강제로 열어봤는데 또 튕긴다...

관리자모드를 열어둔상태로 가봤는데도 튕기고 옵션에 찾아가서 클릭으로 열어도 튕긴다

 

... 우와 신기하다!!

 

어떻게 한건지 찾아보니 이미 감시하는 이벤트를 만들어두신분들이 있었다.

 

출처: https://dev.to/composite/a-simple-way-to-detect-devtools-2ag0

 

A simple way to detect devtools.

After I made this implementation, I found related issue on stack overflow....

dev.to

!function() {
  function detectDevTool(allow) {
    if(isNaN(+allow)) allow = 100;
    var start = +new Date(); // Validation of built-in Object tamper prevention.
    debugger;
    var end = +new Date(); // Validates too.
    if(isNaN(start) || isNaN(end) || end - start > allow) {
      // input your code here when devtools detected.
    }
  }
  if(window.attachEvent) {
    if (document.readyState === "complete" || document.readyState === "interactive") {
        detectDevTool();
      window.attachEvent('onresize', detectDevTool);
      window.attachEvent('onmousemove', detectDevTool);
      window.attachEvent('onfocus', detectDevTool);
      window.attachEvent('onblur', detectDevTool);
    } else {
        setTimeout(argument.callee, 0);
    }
  } else {
    window.addEventListener('load', detectDevTool);
    window.addEventListener('resize', detectDevTool);
    window.addEventListener('mousemove', detectDevTool);
    window.addEventListener('focus', detectDevTool);
    window.addEventListener('blur', detectDevTool);
  }
}();

if문쪽에 처리하고 싶은 로직을 넣으면 된다.

alert, location.href  등등

 

 

2022/05/12 추가----------------

당시 브라우저 설정이나 문제가 있었던거 같습니다. 결국 해당 코드도 디버거 효과로 중간에 강제로 읽게하고 꺼지는 기능인지라... 사실 관리자도구로 찍어볼만한건 다 찍어보고 끌수 있습니다.(즉 별 의미 없다는 소리입니다...ㅜㅠ)

반응형
반응형

Mssql로 동작하는 프로젝트에서 게시판 검색 기능이 잘 되지 않는다는 확인 요청건이 들어왔다.

 

실제로 '[공지]' 라는 키워드로 검색을 해보니 원하는 결과가 나오지 않았다.

 

아래는 작성한 쿼리이다.

SELECT * FROM 테이블명
WHERE 컬럼 LIKE '%' + 검색어(@PARAM) + '%' 
OFFSET 0 ROW FETCH NEXT 5 ROW ONLY

간단한 like절 검색 쿼리인데, 여기에 [공지]를 검색하게되면 와일드카드가 적용되면서 아래와 같은 쿼리로 변경되어 동작된다.

 

SELECT * FROM 테이블명
WHERE 컬럼 LIKE '%[공지]%' 
OFFSET 0 ROW FETCH NEXT 5 ROW ONLY

like절에는 %%라는 와일드카드뿐만 아니라 []라는 와일드카드도 존재한다.

 

https://docs.microsoft.com/ko-kr/sql/t-sql/language-elements/wildcard-character-s-to-match-transact-sql?view=sql-server-ver15 

 

하나 이상의 문자를 찾는 [] 와일드카드 - SQL Server (Transact-SQL)

와일드카드를 사용하여 하나 이상의 문자를 찾습니다.

docs.microsoft.com

ms에서 설명하고 있듯이 [a-z][0-9]와 같이 사용하여 우편번호라던지 특정 키워드 글자가 있는것을 찾아오는 기능인데,

해당방식으로 찾기때문에 우리가 원하는 결과가 나오지 않는다.

 

 

 

 

[]와 같은 와일드카드 강제 문자열 처리하기

[와 ]의 키워드를 강제 문자열로 처리하기 위해 REPLACE함수로 강제 치환 처리를 추가하였다.

(프로시져를 생성해서 return함수를 사용해도 좋을 것 같다.)

 

아래는 사용한 결과물이다.

SELECT * FROM 테이블명
WHERE 컬럼 LIKE '%' + REPLACE(REPLACE(검색어(@PARAM),'[','\['),']','\]') + '%' ESCAPE '\'
OFFSET 0 ROW FETCH NEXT 5 ROW ONLY

 

1. [로 시작하는 문자가 존재하면 \+[ 조합으로 변경한다.

2. ]로 시작하는 문자가 존재하면 \+[ 조합으로 변경한다.

3. ESCAPE를 통해 \를 제외처리하여 와일드카드가 동작하지 않도록 변경하고 \[공지\]라는 문자열로 자동 처리가 되도록 한다.

 

이후 정상적으로 1개가 검색이 되는 것을 확인했다.

검색이 된다!

 

 

 

REPLACE 변환처리 함수 생성하여 적용하기

와일드카드인 [, ] 뿐만 아니라 %, ^ 등도 추가를 해야하면 매번 REPLACE항목이 늘어나서 아래처럼 변경될 것이다.

REPLACE(REPLACE(REPLACE(REPLACE(@str,'[','\['),']','\]'), '%', '\%'), '^', '\^')

이런 쿼리를 mybatis등에 정의해두면 가독성도 안좋을 뿐더러, 수정도 어렵고 추가 사항이 발생하면 여간 골치 아픈게 아니다.

 

이런 REPLACE 기능 자체를 함수로 만들어서 like절마다 함수로 치환하면 좀 더 가독성도 좋고, 유지보수도 쉽게 처리 할  수 있다.

 

- 함수 만들기

먼저 REPLACE 치환 처리를 해주는 함수를 생성한다.

CREATE FUNCTION SP_ESCAPE_PROC(@str VARCHAR(300))
RETURNS VARCHAR(400)
AS
BEGIN
	DECLARE @RESULT VARCHAR(400)
	SET @RESULT = REPLACE(REPLACE(@str,'[','\['),']','\]')
RETURN @RESULT
END;

SP_ESCAPE_PROC이라는 함수명으로 함수를 생성했다.

1개의 Input을 받고 1개의 문자열을 Output하도록 만들었다. (문자열(300자 제한) 하나를 받고, 문자열(400자 제한)을 리턴한다.)

 

SET부분에선 실제로 처리할 로직을 입력한다. 여기선 '[]' 만 치환하도록 하였다.

F5키등을 통해 실행한다.

정상적으로 생성되었다.

 

실제로 생성한 함수를 통해 사용한 쿼리이다.

SELECT * FROM 테이블명
WHERE 컬럼 LIKE '%' + dbo.SP_ESCAPE_PROC('[공지]') + '%' ESCAPE '\'
OFFSET 0 ROW FETCH NEXT 5 ROW ONLY

깔끔해지기도 했고, 추가사항이 생기면 함수를 수정해주면 된다.

반응형
반응형

자바스크립트 공부를 하다보면 심심치 않게 만나는 호이스팅이라는 개념...😲

들어보기도 하고 어느정도는 알고 있었지만 혼란스러운 부분이 있어서 정리가 필요했습니다!

 

먼저 간단한 자바스크립트 문법부터 보겠습니다.

 

 

함수의 호이스팅 동작

console.log("결과는? >" + test());
function test(){
    return "hi";
}

 

해당 코드의 결과는?! 아래와 같습니다!

함수 test를 호출함으로써 "hi"라는 문자열이 정상적으로 반환되어서 콘솔에 hi가 잘 찍히는걸 볼 수 있습니다.

 

실질적으로 정의된 test함수는 출력하는 콘솔로그보다 아래에 위치하지만 신기하게도 동작하는걸 볼 수 있는데 이러한 동작을 호이스팅이라고 합니다.

변수와 함수간에 동작이 조금 다른데, 이번엔 변수 동작부분을 확인해보겠습니다.

 

 

변수의 호이스팅 동작

var a = "테스트입니다~";
function test(){
    console.log("a1 >",a);
    var a = "테스트 함수에서 a변수를 재정의합니다!";
    console.log("a2 >",a);
}
test();

이번에 작성한 코드의 결과는 어떨까요? 저도 이부분에 처음에 당황을 했습니다.

 

결과는 아래와 같습니다.

예상한 결과와 같나요?

제가 처음 예상했던 답은 "테스트입니다~"가 먼저 출력되고, 재정의되었다는 문자열이 출력 될것이라고 생각했지만, 호이스팅으로 인해 전혀 다른 결과가 나왔습니다.

 

아래는 호이스팅 방식을 기반으로 해석한 코드입니다.

var a = "테스트입니다~";
function test(){
    var a;
    console.log("a1 >",a);  //undefined
    a = "테스트 함수에서 a변수를 재정의합니다!";
    console.log("a2 >",a);  //테스트 함수에서 a변수를 재정의합니다!
}
test();

해석한 코드와 기존 코드의 차이가 보이나요?

호이스팅으로 인해 test함수 최상단에 a라는 변수가 선언되었고, 콘솔로그에서 출력이 되면서 undefined라는 결과물이 나왔고, 그 이후 대입연산자를 통해 재정의라는 문자열이 대입된 것을 볼 수 있습니다.

 

이처럼 블록 구문(함수) 최상단에 변수 또는 함수명인 식별자를 최상단으로 끌어올리는 효과를 보여주는 것을 호이스팅이라고 부릅니다.

 

 

복잡한 호이스팅 동작과 전역 스코프

이번엔 복잡해진 호이스팅을 확인해보겠습니다.

var a = 1;
function outer () {
    function inner () {
        console.log(a);
        var a = 3;
        console.log(a);
        var a;
        console.log(a);
        var a = 5;
        console.log(a);
    }
    inner();
    console.log(a);
}
outer();
console.log(a);

복잡한 결과물

 

복잡하지만 차근차근 호이스팅을 분석해보겠습니다.

var a;
a = 1;
function outer () {
    function inner () {
        var a;
        var a;
        var a;

        console.log(a); //undefined
        a = 3;
        console.log(a); //3
        console.log(a); //3
        a = 5;
        console.log(a); //5
    }
    inner();
    //내부에 a변수가 없으므로 외부 스코프영역에서 a가 존재하는지 찾음
    console.log(a); //1
}
outer();
console.log(a); //1

어떤가요? 분석구문과 실제 결과와 동일한가요?

내부 스코프에 해당 변수의 선언된게 없으면 외부 스코프에서 해당 변수가 선언되었는지 확인을 합니다.

아우터부분의 a에서 그래서 1이 찍히는걸 볼 수 있습니다.

 

 

정리

다만, 변수와 함수는 또 다른 차이점이 있습니다.

- 변수는 변수명만 최상단에 정의 된 후 선언 위치에서 값이 대입되는 모습을 보여줍니다.

- 함수는 정의부 자체와 함께 최상단에 선언되는 것을 볼 수 있습니다.

 

이러한 차이점때문에 함수는 처음에 호출하여도 정의부가 잘 동작 후 출력되는것을 볼 수 있엇고, 변수는 최초 선언만 되었기 때문에, 결과 호출시 undefined와 같은 결과를 만날 수 있습니다.

 

 

다만, 함수 동작의 호이스팅에서 함수 정의 방법에 따라 달라는 결과를 볼 수 있습니다.

 

 

다양한 함수 표현방법의 호이스팅 결과

console.log(test1());
console.log(test2());
console.log(test3());

function test1(){
    return "test1";
}

var test2 = function(){
    return "test2";
}

var test3 = () => {
    return "test3";
}

function 키워드를 통해 함수를 정의하지만 변수에 함수를 대입하여 함수의 동작을 처리할 수도 있고, 화살표 함수를 통해 표현할 수도 있습니다.

다만 호이스팅 처리방식의 변수와 함수 표현식의 차이로 인해 test2, test3는 정상적으로 동작하지 않습니다.

 

아래는 동작 결과입니다.

초점을 동작하는 행위에 두지 않는다면 당연한 결과일수도 있습니다. (변수로 시작했는지, 함수로 시작했는지)

상단에 정의된 내용까지 호이스팅하는 동작은 function키워드로 정의한 부분만 동작하는 것을 볼 수 있습니다.

반응형
반응형

npm을 통해 리액트 프로젝트를 생성하려고 했는데, 아래와 같이 반복적으로 오류가 발생하고 있었다.

 

업데이트도 안되고 npm의 버전조차 나오지 않았다.

(여러 강의나 다른글의 모듈등을 설치하면서 버전간에 문제가 있게되었고 설정이 꼬인거 같았다...😣)

 

해결방법

1. npm 재설정 처리를 위해 다음과 같이 기존 캐시 파일들을 삭제하였다.

C:/Users/계정명/AppData/Roaming/ 이동

npm, npm-cache 디렉토리 2개 삭제

 

 

2. 다음 명령어를 입력한다.

npm cache clean

 

해결!😎

정상적으로 설치버전까지 나온다!

 

 

반응형
반응형

늦은감이 있지만 리액트 스터디를 재시작했습니다.

 

언어를 습득하는데 작은 프로젝트라도 해보는게 머리에 잘 남기에 개인 이력이 담긴 포트폴리오를 작성하고 깃허브에 올리는 프로젝트를 수행 중이였습니다.

 

여러가지 최대한 아는 기능선에서 만들어보고 있었는데, 자기 소개를 하는 페이지를 작성하는 부분의 텍스트를 실시간으로 타이핑하는 효과를 주기 위해 이미 작성 된 데이터를 담아둔 배열을 setInterval을 통해 표현하면 될 거라고 생각하고 작성을 시작했습니다.

 

useEffect를 통해 로드 시 한번만 setInterval이 동작하도록 하였고, 생각보다 많은 시행착오를 겪고 나서 처리까지 완료가 되었습니다.

 

아래는 처음 작성해 본 예제입니다.

Introduce.js

import { useState, useEffect } from "react";
import {MdKeyboardReturn} from  "react-icons/md";
import "./Introduce.scss";
import bg_img from "../../../assets/imgs/monet.jpg";

const Introduce = () => {
    const OPTION = {
        writeArr: [],
        arrIndex: 0,
        index: 0
    };
    const WORD_TYPING_SPEED = 80;
    const msgArr = [
        " 안녕하세요? 웹 개발자 박신우입니다.",
        " 저를 소개하기 위한 페이지를 만들었습니다.",
        " 저의 정보를 확인해주세요.",
    ];

    const [introduce, setIntroduce] = useState([]);

    const onChangeMsg = (o) => {
        const msg = msgArr[o.arrIndex];
        o.writeArr[o.arrIndex] = msg.substring(0, ++o.index);
        if(o.index > msg.length) {
            o.index = 0;
            if(++o.arrIndex >= msgArr.length){
                o.arrIndex = 0;
                o.writeArr.splice(0, o.writeArr.length);
            }
        }
        setIntroduce(() => [...o.writeArr]);
    };

    useEffect(()=>{
        const interval = setInterval(()=>{
            onChangeMsg(OPTION);
        }, WORD_TYPING_SPEED);
        return () => clearInterval(interval);
    }, []);
    
    return (
        <div id="introduce" className="Introduce" style={{background: `url(${bg_img}) no-repeat`}}>
            <h2 className="introduce_msg">
                {
                    introduce.map((m, i) => (
                        msgArr[i].length === m.length ? <p key={i}>{m}</p>
                        : <p key={i}>{m}<MdKeyboardReturn /></p>                    
                    ))
                }
            </h2>
        </div>
    );
}

export default Introduce;

나름 UI를 확인해봤을땐 별 문제 없이 잘 동작하는것으로 보였습니다.

 

하지만, 관리자 모드를 열어서 확인해보니 Hook관련 에러가 계속해서 발생하는 것을 확인했습니다.😣

 

처리 방법

검색을 하다보니 setInterval과 react의 동작 구조 개념에서 발생하는 현상이라는 번역 글을 확인했습니다.

 

아래는 제가 참고한 블로그 글입니다.

https://velog.io/@jakeseo_me/%EB%B2%88%EC%97%AD-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90%EC%84%9C-setInterval-%EC%82%AC%EC%9A%A9-%EC%8B%9C%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%A0%90

 

번역 / 리액트 훅스 컴포넌트에서 setInterval 사용 시의 문제점

Dan abramov의 https://overreacted.io/making-setinterval-declarative-with-react-hooks/ 번역입니다.All copyrights to Dan Abramovtranslated by Jake seoTHE

velog.io

 

setInterval대신 커스텀으로 작성된 컴포넌트 useInterval을 사용하라는 간단한 제안이 되어있습니다.

 

별도로 컴포넌트를 작성후 import하여 해결하였고, 그 결과는 아래 소스입니다.

useInterval.js

import { useEffect, useRef } from 'react';

const useInterval = (callback, delay) => {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

export default useInterval;

 

Introduce.js

import { useState } from "react";
import useInterval from "../../common/useInterval";
import {MdKeyboardReturn} from  "react-icons/md";
import "./Introduce.scss";
import bg_img from "../../../assets/imgs/monet.jpg";


const OPTION = {
    writeArr: [],
    arrIndex: 0,
    index: 0
};
const WORD_TYPING_SPEED = 80;
const msgArr = [
    " 안녕하세요? 웹 개발자 박신우입니다.",
    " 저를 소개하기 위한 페이지를 만들었습니다.",
    " 저의 정보를 확인해주세요.",
];

const Introduce = () => {
    const [introduce, setIntroduce] = useState([]);

    const onChangeMsg = (o) => {
        const msg = msgArr[o.arrIndex];
        o.writeArr[o.arrIndex] = msg.substring(0, ++o.index);
        if(o.index > msg.length) {
            o.index = 0;
            if(++o.arrIndex >= msgArr.length){
                o.arrIndex = 0;
                o.writeArr.splice(0, o.writeArr.length);
            }
        }
        setIntroduce(() => [...o.writeArr]);
    };

    useInterval(() => {
        onChangeMsg(OPTION);
    }, WORD_TYPING_SPEED);

    return (
        <div id="introduce" className="Introduce" style={{background: `url(${bg_img}) no-repeat`}}>
            <h2 className="introduce_msg">
                {
                    introduce.map((m, i) => (
                        msgArr[i].length === m.length ? <p key={i}>{m}</p>
                        : <p key={i}>{m}<MdKeyboardReturn /></p>                    
                    ))
                }
            </h2>
        </div>
    );
}

export default Introduce;

소스도 한결 가벼워지고 더 이상 오류문구도 발생하지 않는걸 볼 수 있습니다.

useInterval에서 useEffect를 통해 setInterval을 처리하기 때문에 useEffect로 감싸지 않는것을 볼 수 있습니다.

반응형
반응형

리눅스등에서 ll 커맨드를 통해 디렉토리 리스트등을 자주 보곤 했는데, 새로 생성한 계정에서 ll이 동작하지 않았다.

 

아래 커맨드를 입력 후 정상동작하는걸 볼 수 있었다.

$ alias ll="ls -al"

 

반응형