<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Marshall Web Develop</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 13 Mar 2023 04:11:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Marshall Web Develop</title>
            <url>https://velog.velcdn.com/images/ty-yun21/profile/a3b1a71f-3def-482b-b08b-1d72d9abc79c/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Marshall Web Develop. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ty-yun21" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring Security (+JWT + Redis + Cookie)]]></title>
            <link>https://velog.io/@ty-yun21/SpringSecurity1</link>
            <guid>https://velog.io/@ty-yun21/SpringSecurity1</guid>
            <pubDate>Mon, 13 Mar 2023 04:11:16 GMT</pubDate>
            <description><![CDATA[<h3 id="1-결론">1. 결론</h3>
<ol>
<li>Spring Security + JWT(Access/Refresh Token) + Redis + Cookie 이용</li>
<li>Access Token의 경우 local Storage 이용</li>
<li>Refresh Token의 경우 Redis + Cookie 이용</li>
<li>Api Call 시, axios api interceptors를 이용하여 jwt token validation 진행</li>
<li>화면 변경 시, RouteService.js에서 <withAuth> component를 이용해 jwt 체크 후 권한이 없다면 login화면 redirect 처리함</li>
</ol>
<h3 id="2-개념">2. 개념</h3>
<p>2.1 Spring Security
공식 문서 : <a href="https://spring.io/projects/spring-security#overview">https://spring.io/projects/spring-security#overview</a>
<a href="https://docs.spring.io/spring-security/site/docs/5.4.0/reference/html5/">https://docs.spring.io/spring-security/site/docs/5.4.0/reference/html5/</a></p>
<pre><code># Reference
1. 설정
https://gngsn.tistory.com/160
2. 개념
https://gngsn.tistory.com/160
3. Spring Security ver 5.7 (WebSecurityConfigAdapter deprecated)
https://velog.io/@pjh612/Deprecated%EB%90%9C-WebSecurityConfigurerAdapter-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8C%80%EC%B2%98%ED%95%98%EC%A7%80
</code></pre><p>2.2 JWT
공식 문서 : <a href="https://jwt.io/">https://jwt.io/</a></p>
<pre><code># Reference
0. JWT
https://velog.io/@junghyeonsu/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%9D%84-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95

https://velog.io/@yaytomato/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0#-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%A2%85%EB%A5%98%EC%99%80-%EB%B3%B4%EC%95%88-%EC%9D%B4%EC%8A%88

https://velog.io/@khy226/jwt%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0-React-Rails

1. Spring Security JWT 적용 (중요)
https://velog.io/@jkijki12/Spirng-Security-Jwt-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

2. redis
https://wildeveloperetrain.tistory.com/59

3. refresh token 적용
https://velog.io/@ehdrms2034/Spring-Security-JWT-Redis%EB%A5%BC-%ED%86%B5%ED%95%9C-%ED%9A%8C%EC%9B%90%EC%9D%B8%EC%A6%9D%ED%97%88%EA%B0%80-%EA%B5%AC%ED%98%84

4. react에 cookie 적용
https://5xjin.github.io/blog/react_jwt_router/

5. Cookie, Session 기본 지식
https://catsbi.oopy.io/0c27061c-204c-4fbf-acfd-418bdc855fd8</code></pre><h3 id="3-flow">3. Flow</h3>
<ol start="0">
<li>사용자 등록을 함</li>
<li>Login 
: ID/PW 일치한다면 Access/Refresh JWT 발급 (Refresh의 경우 cookie값이 null 또는 expired 됬을 때 JWT 발급)</li>
<li>API Call
: Access Token을 이용해서 Authorization
: Access Token Expired 시 Refresh Token 으로 Access Token 재발급
: Refresh Token Expired 시 재로그인 필요</li>
<li>화면 이동
: Access Token을 이용해서 Authorization
: Access Token Expired시 Refresh Token을 이용해 Access Token 재발급
: Access, Refresh Token 둘다 없거나 Expired 상태라면 /login 메뉴로 Redirect</li>
</ol>
<h3 id="4-상세-개념">4. 상세 개념</h3>
<ol>
<li>Spring Security
: FilterChain을 이용해 인증, 인가를 담당함 (Authentication, Authorization)
<img src="https://velog.velcdn.com/images/ty-yun21/post/249cc686-ea52-42b5-8481-888fbbb1080e/image.png" alt=""></li>
</ol>
<p>: 인증, 인가 처리를 여러개의 필터를 통해 진행하고 필터는 HttpSecurity 클래스에서 생성됨</p>
<pre><code>    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class)
    ...</code></pre><p>: 이렇게 설정 파일 별로 필터 목록을 갖게 된 후, 이 필터들은 WebSecurity 클래스에게 전달</p>
<p><img src="https://velog.velcdn.com/images/ty-yun21/post/a5d7289e-e82a-4b04-b092-cc95a640ef12/image.png" alt=""></p>
<p>: WebSecurity는 각각 설정 클래스로 부터 필터 목록들을 전달받고, 다시 FilterChainProxy를 생성자의 인자로 전달합</p>
<p>: 결국 FilterChainProxy는 각각의 설정 클래스 별(SecurityConfig1, SecurityConfig2)로 필터 목록들을 갖고 있는 형태가 됨</p>
<h3 id="5-상세-구현">5. 상세 구현</h3>
<p>[F : Front Side], [B : Backend Side]</p>
<h4 id="contents">Contents</h4>
<ol>
<li><p>Front<br>[F-1] Login, 회원등록 Page 생성<br>: Login Page Component, Redux-Saga<br>[F-2] Api Axios Post Call Intercept<br>[F-3] 메뉴 이동 시 isAuth를 통해 권한 확인 및 권한 없을 시 login 메뉴 redirect
[F-4] 권한에 따른 메뉴 출력 처리</p>
</li>
<li><p>Back<br>[B-1] Spring Security 설정
[B-2] JWT 관련 설정
[B-3] login, register관련 controller, service
[B-4] Refresh 관련 설정
[B-5] isAuth 관련 설정</p>
</li>
</ol>
<h4 id="상세">상세</h4>
<ol>
<li><p>[F-1] src/App.js<br>: Route 추가  (login, userRegister)</p>
<pre><code class="language-js">     &lt;Routes&gt;
       &lt;Route path=&quot;/login&quot; element={&lt;LoginPage/&gt;} /&gt;
       &lt;Route path=&quot;/userRegister&quot; element={&lt;UserRegister /&gt;} /&gt;
       &lt;Route path=&quot;/*&quot; element={&lt;App/&gt;} /&gt;
       {/* &lt;Route path=&quot;app/*&quot; element={&lt;RouteService /&gt;} /&gt; */}
     &lt;/Routes&gt;</code></pre>
</li>
<li><p>[F-1] LoiginPage, UserRegisterPag 생성</p>
<pre><code>/routes/LoginPage/index.js
/routes/UserRegister/index.js</code></pre><pre><code class="language-js">const LoginPage = () =&gt; {

 //const {error} = useSelector(reducer =&gt; reducer.tAuthReducer);
 const dispatch = useDispatch();
 const navigate = useNavigate();
 const location = useLocation();
 const [state, setState] = useState({
     account: &#39;&#39;,
     password: &#39;&#39;
 });

 const onHandleChange = e =&gt; {
     const {name, value} = e.target;
     setState({...state, [name]: value})
 }

 const onHandleSubmit = e =&gt; {
     e.preventDefault();
     dispatch(tAuthLogin({account: state.account, password: state.password, navigate}))
 }
</code></pre>
</li>
</ol>
<p>return(</p>
<form onSubmit={onHandleSubmit}>
    <ul className='form-btns'>
        <li>
              <button className='btn full primary' type='submit'>
                                        Login
            </button>
        </li>                            
    </ul>
</form>
);
};

<p>export default LoginPage;</p>
<pre><code>
3. [F-1] Action, Saga, Reducer 생성
: 로그인, 회원등록, 권한 Expired 상태 확인, 권한 Set 관련 Action, Saga, Reducer

```js
//TAuthSagas.js

function* tAuthLoginToServer(action) {

    try {
        const response = yield call(getLoginRequest, action);

        if (response.status === 200){

            yield put(setTAuthIsAuthByLogin(response));
            yield localStorage.setItem(&#39;jwt-access&#39;, response.data.token);
            yield localStorage.setItem(&#39;authority&#39;, response.data.roles[0].role);
            setRefreshToken(response.data.refreshToken.value);
            navigate(&#39;/&#39;);
        } else if (response.data.statusCode === 0) {
            yield put(setTAuthIsAuthError(response))
            navigate(&#39;/login&#39;);
        }
    } catch (error) {
        message.error(&#39;Auth - SERVER ERROR&#39;);
    }
}

const getLoginRequest = async (request) =&gt;
    await api( {
        method: &#39;post&#39;,
        url: &#39;/api/auth/login&#39;,
        data: JSON.stringify(request.payload),
        headers: {&#39;Content-Type&#39;: &#39;application/json&#39;},
        //headers: {Authorization: `Bearer ${localStorage.getItem(&#39;jwt-access&#39;)}` &#39;Content-Type&#39;: &#39;application/json&#39;},

    }).then((response) =&gt; {
        return response;
    }).catch((error) =&gt; {
        return error;
    });

export function* tAuthLoginSagas() {
    yield takeEvery(TAUTH_LOGIN, tAuthLoginToServer);
}

export default function* TAuthSagas() {
    yield all( [
        fork(tAuthLoginSagas),
        fork(tAuthRegisterSagas),
        fork(tAuthISAuthSagas),
    ]);
}</code></pre><pre><code class="language-js">
const TAuthReducer = (state = INIT_STATTE, action) =&gt; {
    switch(action.type) {
        case TAUTH_SET_IS_AUTH:
            return {...state, isAuth: action.payload.body.isAuth, error: &#39;&#39;}
        case TAUTH_SET_IS_AUTH_BY_LOGIN:
            return {...state, isAuth: true, error: &#39;&#39;}
        case TAUTH_SET_IS_AUTH_ERROR:
            removeCookieToken();
            return {...state, error: &quot;Error&quot; }
        case TAUTH_REGISTER_SUCCESS: { 
            return { ...state, loading: false};}
        case TAUTH_REGISTER_ERROR: { 
            return { ...state, loading: false};}
        default:
            return {...state};
    }
}

export default TAuthReducer;
</code></pre>
<ol start="4">
<li>[B-1] Spring Security 설정</li>
</ol>
<p>4.1 SecurityConfig.java</p>
<pre><code>@Configuration
: @Configuration이라고 하면 설정파일을 만들기 위한 애노테이션 or Bean을 등록하기 위한 애노테이션이다.
: https://castleone.tistory.com/2

@EnableWebSecurity
: @EnableWebSecurity 어노테이션을 달면SpringSecurityFilterChain이 자동으로 포함됩니다.

@RequiredArgsConstructor
: final이 붙거나 @NotNull 이 붙은 필드의 생성자를 자동 생성해주는 롬복 어노테이션
: https://velog.io/@developerjun0615/Spring-RequiredArgsConstructor-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85
</code></pre><pre><code class="language-java">@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtProvider jwtProvider;

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -&gt; web.ignoring().antMatchers(&quot;/images/**&quot;, &quot;/js/**&quot;, &quot;/webjars/**&quot;, &quot;/h2-console/**&quot;, &quot;/favicon.ico&quot;, &quot;/login&quot;);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                // ID, Password 문자열을 Base64로 인코딩하여 전달하는 구조
                .httpBasic().disable()
                // 쿠키 기반이 아닌 JWT 기반이므로 사용하지 않음
                .csrf().disable()
                // CORS 설정
                .cors(c -&gt; {
                            CorsConfigurationSource source = request -&gt; {
                                // Cors 허용 패턴
                                CorsConfiguration config = new CorsConfiguration();
                                config.setAllowedOrigins(
                                        List.of(&quot;*&quot;)
                                );
                                config.setAllowedMethods(
                                        List.of(&quot;*&quot;)
                                );
                                return config;
                            };
                            c.configurationSource(source);
                        }
                )
                // Spring Security 세션 정책 : 세션을 생성 및 사용하지 않음
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 조건별로 요청 허용/제한 설정
                .authorizeRequests()
                    // 회원가입과 로그인은 모두 승인
                    .antMatchers(&quot;/api/auth/login&quot;).permitAll()
                    .antMatchers(&quot;/api/auth/refresh&quot;).permitAll()
                    .antMatchers(&quot;/api/auth/register&quot;).permitAll()
                    .antMatchers(&quot;/api/auth/isAuth&quot;).permitAll()
                    .and()
                    // /admin으로 시작하는 요청은 ADMIN 권한이 있는 유저에게만 허용
                    //.antMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
                    // /user 로 시작하는 요청은 USER 권한이 있는 유저에게만 허용
                    //.antMatchers(&quot;/user/**&quot;).hasRole(&quot;USER&quot;)
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                // JWT 인증 필터 적용
                .addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class)
                // 에러 핸들링
                .exceptionHandling()
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                        // 권한 문제가 발생했을 때 이 부분을 호출한다.
//                            401(Unauthorized)
//                            상태: 클라이언트가 인증되지 않았거나, 유효한 인증 정보가 부족하여 요청이 거부됨
//                            예시: 사용자가 로그인되지 않은 경우
                        response.setStatus(403);
                        response.setCharacterEncoding(&quot;utf-8&quot;);
                        response.setContentType(&quot;text/html; charset=UTF-8&quot;);
                        response.getWriter().write(&quot;권한이 없는 사용자입니다.&quot;);
                    }
                })
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                        // 인증문제가 발생했을 때 이 부분을 호출한다.
//                            403(Forbidden)
//                            상태: 서버가 해당 요청을 이해했지만, 권한이 없어 요청이 거부됨
//                            예시: 사용자가 권한이 없는 요청을 하는 경우
                        response.setStatus(401);
                        response.setCharacterEncoding(&quot;utf-8&quot;);
                        response.setContentType(&quot;text/html; charset=UTF-8&quot;);
                        response.getWriter().write(&quot;AcessExpired&quot;);
                    }
                });
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}</code></pre>
<ol start="5">
<li>[B-2] JWT 설정</li>
<li>1 JwtAuthenticationFilter.java (커스텀 필터)</li>
</ol>
<pre><code class="language-java">/**
 * Jwt가 유효성을 검증하는 Filter
 */
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtProvider jwtProvider;

    public JwtAuthenticationFilter(JwtProvider jwtProvider) {
        this.jwtProvider = jwtProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = jwtProvider.resolveToken(request);
        System.out.println(&quot;############ jwt filter api/auth/login : token : &quot;+token);
        if (token != null &amp;&amp; jwtProvider.validateToken(token)) {
            // check access token
            token = token.split(&quot; &quot;)[1].trim();
            Authentication auth = jwtProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }
}</code></pre>
<ol start="6">
<li>[B-3] login, register관련 controller, service, [B-4], [B-5]</li>
<li>1 JwtProvider.java (커스텀 provider)</li>
</ol>
<pre><code class="language-java">// secret key 생성
// jwt token 생성 (Access, Refresh)
// 권한 정보 획득
// resolveToken : request header에서 token값 가져오기
// validateToken : Token 유효성, 만료일자 확인
// createAccessTokenByRefreshToken : refresh token을 통해 Access token 생성</code></pre>
<p>6.2 User, UserDetailsService 작성
: SystemUser로 Entit 생성
: CustomUserDetails.java 생성 (Spring Security가 이용하는 객체)</p>
<p>6.3 Controller, Service, Repository 생성</p>
<pre><code class="language-java">//SystemUserController.java
// login
// register
// refresh (react의 api.interceptors를 통해 access token expired시 refresh 호출함)
// isAuth (시스템에서 경로 이동 시 현재 인증여부를 확인함, Aceess &amp; Refresh 모두 없거나 Expired상태라면 false return)

    @PostMapping(value = &quot;/login&quot;)
    public ResponseEntity&lt;?&gt; signin(HttpServletRequest request, @RequestBody SignRequestDto signRequestDto) throws Exception {

        return new ResponseEntity&lt;&gt;(signService.login(request, signRequestDto), HttpStatus.OK);

    }

    @PostMapping(value = &quot;/refresh&quot;)
    public ResponseEntity&lt;?&gt; generateAccessJwtByRefreshToken(HttpServletRequest request) throws Exception {
        return new ResponseEntity&lt;&gt;(signService.generateAccessJwtByRefreshToken(request), HttpStatus.OK);
    }

       @PostMapping(value = &quot;/register&quot;)
    public ResponseEntity&lt;?&gt; signup(HttpServletRequest request, @RequestBody SignRequestDto signRequestDto) throws Exception {

        try{
            signService.register(signRequestDto);
        } catch(JSONException e) {
            log.error(&quot;Json Exception : &quot;, e);
            res.error(&quot;Something Wrong&quot;);
        } catch (Exception e) {
            log.error(&quot;Exception e : &quot;, e);
            res.error(&quot;Something Wrong&quot;);
        }
        return res.send();
    }

    @PostMapping(value = &quot;/isAuth&quot;)
    public ResponseEntity&lt;?&gt; isAuth(@RequestBody Map&lt;String, String&gt; bodyParam, HttpServletRequest request, Principal principal) throws Exception {

        JsonResponse&lt;Map&lt;Object, Object&gt;&gt; res = new JsonResponse&lt;&gt;(&quot;checkAuth&quot;);
        Map&lt;Object, Object&gt; map = new HashMap&lt;&gt;();

        try {
            if (principal != null) {
                map.put(&quot;isAuth&quot;, true);
            } else {
                if(signService.checkRefreshTokenValid(request)){
                    map.put(&quot;isAuth&quot;, true);
                }else{
                    map.put(&quot;isAuth&quot;, false);
                }

            }
            res.success(map);
        } catch (Exception e) {
            log.error(&quot;Exception e : &quot;, e);
            throw new Exception(e);
        }
</code></pre>
<pre><code class="language-java">// SignService.java

// generateAccessJwtByRefreshToken : Refresh token으로 access token 생성
// checkRefreshTokenValid : refresh token valid여부 확인
// login
: DB에서 ID/PW 확인
: access token 생성
: cookie에 refresh token이 없거나 expired상태라면 refresh token 생성 + refresh token cookie에 담아서 return + redis에 refresh token 저장
// register : User 생성 및 권한 등록</code></pre>
<pre><code>@Service
public class JwtCookieUtil {

    public static Cookie createCookie(String cookieName, String value){
        Cookie token = new Cookie(cookieName,value);
        token.setHttpOnly(true);
        token.setMaxAge((int)JwtProvider.jwtRefreshExpire);
        token.setPath(&quot;/&quot;);
        return token;
    }

    public static Cookie getCookie(HttpServletRequest req, String cookieName){
        final Cookie[] cookies = req.getCookies();
        if(cookies==null) return null;
        for(Cookie cookie : cookies){
            if(cookie.getName().equals(cookieName))
                return cookie;
        }
        return null;
    }
}
</code></pre><pre><code>@Service
public class RedisUtil {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public String getData(String key){
        ValueOperations&lt;String,String&gt; valueOperations = stringRedisTemplate.opsForValue();
        return valueOperations.get(key);
    }

//    public void setData(String key, String value){
//        ValueOperations&lt;String,String&gt; valueOperations = stringRedisTsemplate.opsForValue();
//        valueOperations.set(key,value);
//    }

    public void setDataExpire(String key, String value, long duration){
        ValueOperations&lt;String,String&gt; valueOperations = stringRedisTemplate.opsForValue();
        Duration expireDuration = Duration.ofSeconds(duration);
        valueOperations.set(key,value,expireDuration);
    }

    public void deleteData(String key){
        stringRedisTemplate.delete(key);
    }

}</code></pre><ol start="7">
<li>[F-2] Api Axios Post Call Intercept</li>
</ol>
<pre><code class="language-js">// 참고
// https://velog.io/@wooya/axios-interceptors%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-token%EB%A7%8C%EB%A3%8C%EC%8B%9C-refreshToken-%EC%9E%90%EB%8F%99%EC%9A%94%EC%B2%AD 
import axios from &quot;axios&quot;;

// url 호출 시 기본 값 셋팅
const api = axios.create({
    //timeout: 2000
    //headers: { &quot;Content-type&quot;: &quot;application/json&quot; }, // data type
});

// Add a request interceptor
api.interceptors.request.use(
  function (config) {
    const token = localStorage.getItem(&quot;jwt-access&quot;);

    //요청시 AccessToken 계속 보내주기
    if (!token) {
      config.headers.accessToken = null;
      //config.headers.refreshToken = null;
      return config;
    }

    if (config.headers &amp;&amp; token) {
      //const { accessToken, refreshToken } = JSON.parse(token);
      config.headers.authorization = `Bearer ${localStorage.getItem(&quot;jwt-access&quot;)}`;
      //config.headers.refreshToken = `Bearer ${refreshToken}`;
      return config;
    }
    // Do something before request is sent
    console.log(&quot;api.interceptors.request.use start&quot;, config);
  },
  function (error) {
    // Do something with request error
    console.log(&quot;api.interceptors.request.use error&quot;, error);
    return Promise.reject(error);
  }
);

// Add a response interceptor
api.interceptors.response.use(

  function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    console.log(&quot;api.interceptors.response.use&quot;);
    console.log(&quot;get response&quot;, response);
    return response;
  },
  async (error) =&gt; {
    console.log(&quot;api.interceptors.response.error : &quot;,error);
    const {
      config,
      response: { status },
    } = error;
        const originalRequest = config;
        //const refreshToken = await localStorage.getItem(&quot;refreshToken&quot;);
        // token refresh 요청
        const { data } = await axios.post(
          `/api/auth/refresh`, // token refresh api
          {},
          {}
        );

        console.log(&quot;api.interceptors.response AccessExpired data.accessToeken : &quot;, config);

        await localStorage.setItem(&#39;jwt-access&#39;, data.token);
        originalRequest.headers.authorization = `Bearer ${localStorage.getItem(&#39;jwt-access&#39;)}`;
        // 401로 요청 실패했던 요청 새로운 accessToken으로 재요청
        return axios(originalRequest);
      }
    }
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    console.log(&quot;response error&quot;, error);
    return Promise.reject(error);
  }
);

export default api;</code></pre>
<ol start="8">
<li>[F-3] 메뉴 이동 시 isAuth를 통해 권한 확인 및 권한 없을 시 login 메뉴 redirect</li>
</ol>
<pre><code class="language-js">import React, {useEffect, useState} from &quot;react&quot;;
import {Redirect} from &quot;react-router-dom&quot;;
import { useDispatch, useSelector } from &quot;react-redux&quot;;
import {tAuthIsAuth} from &quot;../../actions&quot;;
import { Navigate } from &quot;react-router-dom&quot;;


const WithAuth = ({ children }) =&gt; {
     const authority = localStorage.getItem(&#39;authority&#39;);
     const dispatch = useDispatch();
     const {isAuth} = useSelector(reducer =&gt; reducer.tAuthReducer);
    useEffect(() =&gt; {
        dispatch(tAuthIsAuth({}));
    }, [dispatch]);

    useEffect(() =&gt; {
        if(isAuth === false) {
            window.localStorage.removeItem(&#39;authority&#39;);
        }
    }, [isAuth]);

    if (authority) {
      // user has authority
      return children;  
    }
    return &lt;Navigate to=&quot;/login&quot; /&gt;;

  };

export default WithAuth;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[10. Midterm Arrangement 중간정리]]></title>
            <link>https://velog.io/@ty-yun21/react10</link>
            <guid>https://velog.io/@ty-yun21/react10</guid>
            <pubDate>Wed, 08 Feb 2023 08:29:27 GMT</pubDate>
            <description><![CDATA[<h1 id="10-midterm-arrangement-중간정리">10. Midterm Arrangement (중간정리)</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react10">https://velog.io/@ty-yun21/react10</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : (2023.02.08) 10. Mitrem Arrangement</p>
<h2 id="목표">목표</h2>
<ol>
<li>Styled Components</li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li><p>Styled Compnents (CSS in JS)<br>: <a href="https://www.daleseo.com/react-styled-components/">https://www.daleseo.com/react-styled-components/</a>  </p>
</li>
<li><p>1 ThemeProvider<br>: <a href="https://wonit.tistory.com/366">https://wonit.tistory.com/366</a></p>
<pre><code class="language-javascript">import { ThemeProvider } from &#39;styled-components&#39;;
return (
  &lt;ThemeProvider theme={theme}&gt;
      &lt;div&gt;&lt;/div&gt;
      &lt;div&gt;&lt;/div&gt;
  &lt;/ThemeProvider&gt;
)</code></pre>
</li>
<li><p>propTypes<br>: <a href="https://velog.io/@eunjin/React-PropTypes-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EB%B0%A9%EB%B2%95">https://velog.io/@eunjin/React-PropTypes-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EB%B0%A9%EB%B2%95</a>  </p>
<pre><code class="language-javascript">PcLeftMenu.propTypes = {
 isActive: PropTypes.bool,
 layout: PropTypes.string,
 color: PropTypes.string,
 sidebarBackgrounds: PropTypes.string,
}</code></pre>
</li>
<li><p>Suspense, LazyComponent
: <a href="https://www.daleseo.com/react-suspense/">https://www.daleseo.com/react-suspense/</a>
: <a href="https://velog.io/@code-bebop/dynamic-import%EC%99%80-React.lazy">https://velog.io/@code-bebop/dynamic-import%EC%99%80-React.lazy</a>  </p>
</li>
</ol>
<h3 id="reference">Reference</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[9. ReduxSaga]]></title>
            <link>https://velog.io/@ty-yun21/react9</link>
            <guid>https://velog.io/@ty-yun21/react9</guid>
            <pubDate>Wed, 08 Feb 2023 08:24:09 GMT</pubDate>
            <description><![CDATA[<h1 id="9-reduxsaga">9. ReduxSaga</h1>
<p>참고용 velog<br><a href="https://velog.io/@ty-yun21/react9">https://velog.io/@ty-yun21/react9</a><br>참고용 github<br><a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : 2023/02/08 redux, redux-saga </p>
<h2 id="목표">목표</h2>
<ol>
<li>redux-saga</li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li>redux-saga</li>
<li>1 redux-saga<br><a href="https://react.vlpt.us/redux-middleware/10-redux-saga.html">https://react.vlpt.us/redux-middleware/10-redux-saga.html</a>  <pre><code>redux-thunk의 경우엔 함수를 디스패치 할 수 있게 해주는 미들웨어였지요?  
redux-saga의 경우엔, 액션을 모니터링하고 있다가, 
특정 액션이 발생하면 이에 따라 특정 작업을 하는 방식으로 사용합니다.  
여기서 특정 작업이란, 특장 자바스크립트를 실행하는 것 일수도 있고,  
다른 액션을 디스패치 하는 것 일수도 있고, 현재 상태를 불러오는 것 일수도 있습니다.  
</code></pre></li>
</ol>
<p>redux-saga는 redux-thunk로 못하는 다양한 작업들을 처리 할 수 있습니다. 예를 들자면..  </p>
<p>비동기 작업을 할 때 기존 요청을 취소 처리 할 수 있습니다<br>특정 액션이 발생했을 때 이에 따라 다른 액션이 디스패치되게끔 하거나, 자바스크립트 코드를 실행 할 수 있습니다.<br>웹소켓을 사용하는 경우 Channel 이라는 기능을 사용하여 더욱 효율적으로 코드를 관리 할 수 있습니다 (참고)<br>API 요청이 실패했을 때 재요청하는 작업을 할 수 있습니다.<br>이 외에도 다양한 까다로운 비동기 작업들을 redux-saga를 사용하여 처리 할 수 있답니다.</p>
<p>redux-saga는 다양한 상황에 쓸 수 있는 만큼, 제공되는 기능도 많고, 사용방법도 진입장벽이 꽤나 큽니다. 
자바스크립트 초심자라면 생소할만한 Generator 문법을 사용하는데요, 
이 문법을 이해하지 못하면 redux-saga를 배우는 것이 매우 어려우니, 
이 문법부터 작동방식을 이해해보도록 하겠습니다.</p>
<pre><code>

1.2 Generator Function &amp; Action Mornitoring  
: redux-saga 에서는 제너레이터 함수를 &quot;saga&quot; 라고 부릅니다.  
```javascript
/**
 * Get List, save, import, send
 */
 export function* getTsysMenusList() {
    yield takeEvery(TSYS_MENU_GET_LIST, getMenusListFromServer);
}

/**
 * Get &amp; Send Menus List From Server
 */
function* getMenusListFromServer() {
    try {
        const response = yield call (getMenusListRequest);
        if(response.data.statusCode ===0) yield put(getTsysMenusListSuccess(response)); // put은 특정 액션을 디스패치 해줍니다.
    } catch (error) {
        message.error(&#39;SERVER ERROR&#39;) 
    }
}</code></pre><p>: redux-saga에서는 이러한 원리로 액션을 모니터링하고, 특정 액션이 발생했을때 우리가 원하는 자바스크립트 코드를 실행시켜준답니다.  </p>
<p>1.3 Summary</p>
<pre><code>1) store 생성 (reducer create 및 saga apply) 및 App.js에 &lt;Provider&gt;로 적용
2) 메뉴 화면에서 dispatch로 Action 요청
3) Action 실행함수 발동 (./actions/***Actions.js)
4) reducer로 가기전에 saga에서 중간 처리
: 보통 RootSaga --&gt; 해당 제너레이터 함수 (사가) --&gt; 여기서 자바스크립트 함수 사용 (보통 API Call) --&gt; Success시 Success 관련 Action 발생  
--&gt; Reducer에서 호출 메뉴에 새로운 상태 반환 (데이터 반환)  </code></pre><ol start="2">
<li>오류
store/index.js에서 아래 내용 추가시  오류 발생  <pre><code class="language-javascript">import sagaMonitor from &#39;@redux-saga/simple-saga-monitor&#39;;
const sagaMiddleware = createSagaMiddleware( {sagaMonitor});</code></pre>
오류 내용  <pre><code>BREAKING CHANGE: webpack &lt; 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
</code></pre></li>
</ol>
<p>If you want to include a polyfill, you need to:
    - add a fallback &#39;resolve.fallback: { &quot;util&quot;: require.resolve(&quot;util/&quot;) }&#39;
    - install &#39;util&#39;</p>
<pre><code>해결책  
https://mr-son.tistory.com/153 를 보고 참고하려 했으나  
결국 해결책은 npm install utill 이였음.  


3. 세팅  
3.1 App.js  
```javascript
import { Provider } from &#39;react-redux&#39;;
import RootSaga from &#39;./sagas&#39;;
import { configureStore } from &#39;./store&#39;;

const store = configureStore(window.__INITIAL_STATE__);   //Create Store
store.runSaga(RootSaga);                                  //Run Root Saga, // 주의: 스토어 생성이 된 다음에 위 코드를 실행해야합니다.

const MainApp = () =&gt; (
  &lt;Provider store={store}&gt;
    &lt;MuiPickersUtilsProvider utils={MomentUtils}&gt;
      &lt;Router&gt;
        &lt;Routes&gt;
          &lt;Route path=&quot;/&quot; element={&lt;App/&gt;} /&gt;
          &lt;Route path=&quot;app/*&quot; element={&lt;App/&gt;} /&gt;
        &lt;/Routes&gt;
      &lt;/Router&gt;
    &lt;/MuiPickersUtilsProvider&gt;
  &lt;/Provider&gt;
);</code></pre><p>3.2 store/index.js  </p>
<pre><code class="language-javascript">/**
 * Redux Store
 */

import { legacy_createStore as createStore, applyMiddleware, compose } from &quot;redux&quot;;
import createSagaMiddleware, { END } from &#39;redux-saga&#39;;
import sagaMonitor from &#39;@redux-saga/simple-saga-monitor&#39;;
import reducers from &#39;../reducers&#39;;
import RootSaga from &#39;../sagas&#39;;

const sagaMiddleware = createSagaMiddleware( {sagaMonitor});        //Create Saga Middleware 


export function configureStore(initialState) {

    const store = createStore(
        reducers,
        //initialState,
        compose(applyMiddleware(sagaMiddleware))
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () =&gt; store.dispatch(END);

    if (module.hot) {
        // Enable Webpack hot module replacement for reducers
        module.hot.accept(&#39;../reducers/index&#39;, () =&gt; {
            const nextRootReducer = require(&#39;../reducers/index&#39;);
            store.replaceReducer(nextRootReducer);
        })
    }


    return store;
}</code></pre>
<p>1.3 화면에서부터 타고 올라고보자<br>1.3.1 routes/system/MenuPage/index.js</p>
<pre><code class="language-javascript">import { useSelector, useDispatch } from &#39;react-redux&#39;;

import {
    getTsysMenuList,
 } from &#39;../../../actions&#39;;

dispatch(getTsysMenuList({}));</code></pre>
<p>1.3.2 [M-1] action/types.js<br>: Action Type을 정의한다  </p>
<pre><code class="language-javascript">// TSYS_MENU
export const TSYS_MENU_ON_GET_LIST = &#39;TSYS_MENU_ON_GET_LIST&#39;;
export const TSYS_MENU_ON_GET_LIST_SUCCESS = &#39;TSYS_MENU_ON_GET_LIST_SUCCESS&#39;; 
export const TSYS_MENU_ON_GET_LIST_FAILURE = &#39;TSYS_MENU_ON_GET_LIST_FAILURE&#39;;</code></pre>
<p>1.3.3 [M-2] action/***Actions.js<br>: Action 파일을 추가한다</p>
<pre><code class="language-javascript">/**
 * Todo App Actions
 */
import {
TSYS_MENU_ON_GET_LIST,    
TSYS_MENU_ON_GET_LIST_SUCCESS,
TSYS_MENU_ON_GET_LIST_FAILURE,
} from &#39;./types&#39;;

export const getTsysMenusList = () =&gt; ({
    type: TSYS_MENU_GET_LIST
});

export const getTsysMenusListSuccess = (response) =&gt; ({
    type: TSYS_MENU_GET_LIST_SUCCESS,
    payload: response.data
});

export const getTsysMenusListFailure = (error) =&gt; ({
    type: TSYS_MENU_GET_LIST_FAILURE,
    payload: error
});
</code></pre>
<p>1.3.3 [M-3] action/index.js<br>: Action 파일 export를 추가한다</p>
<pre><code class="language-javascript">/**
 * Redux Actions
 */

//system
export * from &#39;./TsysMenuActions&#39;;</code></pre>
<p>1.3.4 [M-4] reducers/***Reducers.js
: Reducer 파일을 추가한다 </p>
<pre><code class="language-javascript">import { toast, ToastContainer } from &#39;react-toastify&#39;;
// toast 알람
// https://defineall.tistory.com/1021

// action types
import {
    TSYS_MENU_ON_GET_LIST,
    TSYS_MENU_ON_GET_LIST_SUCCESS,
    TSYS_MENU_ON_GET_LIST_FAILURE,
} from &#39;../actions/types&#39;;

const INIT_STATE = {
    loading: false,
    statusCode: 0,
    errormsg: &quot;&quot;,
    grid: {
        data: [],
        newRowAtTop: false,
        instance: null,
    },
    items: [],
    menuList: [],
};

const TsysMenuReducer = (state = INIT_STATE, action) =&gt; {
    switch (action.type) {
        case TSYS_MENU_ON_GET_LIST: { return { ...state, loading: true, menuList: []};}
        case TSYS_MENU_ON_GET_LIST_SUCCESS: { return { ...state, loading: false, grid: {data: action.payload.body}};}
        case TSYS_MENU_ON_GET_LIST_FAILURE: { return { ...state, loading: false};}
        default: return {...state};
    }
}

export default TsysMenuReducer;</code></pre>
<p>1.3.2 [M-5] reducers/index.js<br>: Reducer import / export 추가한다. </p>
<pre><code class="language-javascript">/**
 * App Reducers
 */
import { combineReducers } from &#39;redux&#39;;

// System
import tsysMenuReducer from &#39;./TsysMenuReducer&#39;;

const reducers = combineReducers({
    tsysMenuReducer: tsysMenuReducer,
});

export default reducers;</code></pre>
<p>1.3.3 [M-6] sagas/***Sagas.js<br>: Saga 파일을 추가한다 </p>
<pre><code class="language-javascript">/**
 * Todo Sagas
 */

import {all, call, fork, put, takeEvery, throttle} from &#39;redux-saga/effects&#39;;

//api
import api from &#39;../api&#39;;

import {
    TSYS_MENU_ON_GET_LIST,
    TSYS_MENU_GET_LIST,
} from &#39;../actions/types&#39;;

import {
    getTsysMenusListSuccess,
    getTsysMenusListFailure,
    getTsysMenuListSuccess,
    getTsysMenuListFailure,
} from &#39;../actions&#39;;

import {message} from &#39;antd&#39;;

/**
 * Get List, save, import, send
 */
 export function* getTsysMenusList() {
    yield takeEvery(TSYS_MENU_GET_LIST, getMenuListFromServer);
}
export function* getTsysMenuList() {
    yield takeEvery(TSYS_MENU_ON_GET_LIST, getListFromServer);
}


/**
 * Get Todos From Server
 */
function* getMenusListFromServer() {
    try {
        const response = yield call (getMenusListRequest);
        if(response.data.statusCode ===0) yield put(getTsysMenusListSuccess(response));
    } catch (error) {
        message.error(&#39;SERVER ERROR&#39;) 
    }
}

const getMenusListRequest = async (request) =&gt; {

    await api({
        method: &#39;post&#39;,
        url: &#39;/api/sys/menu/getSystemMenuList&#39;,
        data: JSON.stringify(request.payload),
        // headers: {Authorization: `Bearer ${localStorage.getItem(&#39;id_token&#39;)}`, &#39;Content-Type&#39;: &#39;application/json&#39;},
    }).then((response) =&gt; {
        return response;
    }).catch((error) =&gt; {
        return error;
    });
};

/**
 * Get &amp; Send Menu List From Server
 */
 function* getListFromServer(action) {
    try {
        const response = yield call(getListRequest, action);
        yield put(getTsysMenuListSuccess(response));
    }catch(error) {
        yield put(getTsysMenuListFailure(error));
    }
}

const getListRequest = async (request) =&gt;
    await api( {
        method: &#39;post&#39;,
        url: &#39;/api/sys/menu/list&#39;,
        data: JSON.stringify(request.payload),
        //headers: {Authorization: `Bearer ${localStorage.getItem(&#39;id_token&#39;)}` &#39;Content-Type&#39;: &#39;application/json&#39;},
    }).then((response) =&gt; {
        return response;
    }).catch((error) =&gt; {
        return error;
    });

/**
 * Root Saga
 */

export default function* rootSaga() {
    yield all( [
        fork(getTsysMenuList),
    ])
}</code></pre>
<p>1.3.3 [M-7] sagas/index.js<br>: Saga import / export 추가한다.</p>
<pre><code class="language-javascript">/**
 * Root Sagas
 */

import {all} from &#39;redux-saga/effects&#39;;

//system
import tsysMenuSagas from &#39;./TsysMenuSaga&#39;;
//import tsysAuthSagas from &#39;./TsysAuthSaga&#39;;

export default function* rootSaga(getState) {
    yield all(
        [
            tsysMenuSagas(),
        ]
    );
}</code></pre>
<h3 id="reference">Reference</h3>
<ol>
<li>벨로퍼트 리액트
<a href="https://react.vlpt.us/">https://react.vlpt.us/</a>
<a href="https://react.vlpt.us/redux-middleware/10-redux-saga.html">https://react.vlpt.us/redux-middleware/10-redux-saga.html</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[8. useEffect, useReducer, useDispatch, useSelector, useMemo, useCallback ]]></title>
            <link>https://velog.io/@ty-yun21/react8</link>
            <guid>https://velog.io/@ty-yun21/react8</guid>
            <pubDate>Wed, 08 Feb 2023 08:23:18 GMT</pubDate>
            <description><![CDATA[<h1 id="8-useeffect-usereducer-usedispatch-useselector-usememo-usecallback">8. useEffect, useReducer, useDispatch, useSelector, useMemo, useCallback</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react8">https://velog.io/@ty-yun21/react8</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : </p>
<h2 id="목표">목표</h2>
<ol>
<li>useEffect</li>
<li>useReducer</li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li>useEffect</li>
<li>1 개요<br>: useEffect를 활용해서 마운트 (처음 나타났을 때), 언마운트, 업데이트 (특정 props가 바귈 때) 될 때<br>특정 작업을 처리하는 방법  <pre><code class="language-javascript">useEffect( () =&gt; {
  console.log(&#39;컴포넌트가 화면에 나타남&#39;);
  return () =&gt; {
      console.log(&#39;컴포넌트가 화면에 사라짐&#39;);
  };
}, []);</code></pre>
: useEffect 사용 시 첫번째 파라미터에는 함수, 두번째 파라미터에는 의존값이 들어있는 배열 (deps)를 넣음<br>deps를 비우면 컴포넌트가 처음 나타날 때만 useEffect에 등록한 함수 호출<br>: 마운트시 주로 하는 작업들  </li>
</ol>
<p>--&gt; props로 받은 값 컴포넌트의 로컬 상태로 설정<br>--&gt; 외부 API요청 (REST API etc...)
--&gt; 라이브러리 사용 (D3, Video.js etc..)<br>--&gt; setInterval을 통한 반복작업 또는 setTimeout을 통한 작업 예약  </p>
<p>1.2 deps에 특정 값 넣기<br>: deps에 특정 값을 넣으면 컴포넌트 마운트, 지정한 값 바뀔 때, 언마운트, 값이 바뀌기 직전 호출  </p>
<pre><code class="language-javascript">useEffect( () =&gt; {
    console.log(&#39;user 값 설정됨&#39;);
    console.log(user);
    return () =&gt; {
        console.log(&#39;user 바뀌기 전...&#39;);
        console.log(user);
    };
}, [user]);</code></pre>
<p>: deps를 생략하면 컴포넌트가 리렌더링 될 때마다 호출됨  </p>
<ol start="2">
<li>useReducer
: useState와 다르게 useReducer를 사용시<br>컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리 가능<br>상태 업데이트 로직을 컴포넌트 바깥에 작성 할 수도 있고, 다른 파일에 작성 후 불러와서 사용도 가능<br>: reducer는 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수<pre><code class="language-javascript">function reducer(state, action) {
 //새로운 상태를 만든튼 로직
 // const nextState = ...
 return nextState;
}
// 리듀서에서 반환하는 상태는 곧 컴포넌트가 지닐 새로운 상태
// action은 업데이트를 위한 정보를 가지고 있음... </code></pre>
</li>
</ol>
<p>: useReducer의 사용법</p>
<pre><code class="language-javascript">    const [state, dispatch] = useReducer(reducer, initialState);
    // state : 우리가 앖으로 컴포넌트에서 사용할 수 있는 상태를 가르킴
    // dispatch : 액션을 발생시키는 함수
    // ex. dispatch ({ type: &#39;INVERMENT&#39;})
    // useReducer 함수에 첫 번째 파라미터는 리듀서 함수, 두 번째는 초기상태    </code></pre>
<h3 id="reference">Reference</h3>
<ol>
<li>벨로퍼트 리액트
<a href="https://react.vlpt.us/">https://react.vlpt.us/</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux]]></title>
            <link>https://velog.io/@ty-yun21/react7</link>
            <guid>https://velog.io/@ty-yun21/react7</guid>
            <pubDate>Wed, 08 Feb 2023 08:22:15 GMT</pubDate>
            <description><![CDATA[<h1 id="7-redux">7. Redux</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react7">https://velog.io/@ty-yun21/react7</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : </p>
<h2 id="목표">목표</h2>
<ol>
<li>Redux</li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li>Redux</li>
<li>1 미들웨어<br>: 리덕스로 상태 관리시 우리가 useReducer를 사용할 때 접했던 개념인 리듀서 함수 사용...<br>: 리덕스의 미들웨어를 사용하면 액션 객체가 리듀서에서 처리되기 전에 우리가 원하는 작업들 수행 가능  </li>
</ol>
<p>--&gt; 특정 조건에 따라 액션 무시 가능<br>--&gt; 액션을 콘솔에 출력하거나 서버로깅 가능<br>--&gt; 액션이 디스패치 됐을 때 이를 수정해서 리듀서에게 전달되도록 가능<br>--&gt; 특정 액션이 발생 시 이를 기반으로 다른 액션 발생<br>--&gt; 특정 액션 발생 시 특정 자바스크립트 함수 실행 가능  </p>
<p>1.2 유용한 함수<br>connect : 리덕스의 상태 또는 액션 생성함수를 컴포넌트의 props로 받아올 수 있음<br>useSelector, useDispatch, useStore 를 통해 손쉽게 상태조회나 액션 디스패치 가능<br>connect함수와 useSelector함수에는 내부적으로 최적화가 잘 이루어져 있어 실제 상태가 바뀔떄만 컴포넌트가 리렌더링...</p>
<p>1.3 리덕스는 언제 사용?<br>: 프로젝트의 규모가 클 때<br>: 비동기작업을 자주 할 때  </p>
<ol start="2">
<li>Redux 키워드
: Action, Action Creator, Reducer, Store, Dispatch, Subscribe,  </li>
<li>1 Action<br>: 상태에 어떤 변화가 필요할 때 액션 발생시<pre><code>//액션 객체는 type 필드를 필수적으로 가지고 있어야하고 그 외의 값들은 개발자 마음대로 넣어줄 수 있습니다.
</code></pre></li>
</ol>
<p>{
  type: &quot;ADD_TODO&quot;,
  data: {
    id: 0,
    text: &quot;리덕스 배우기&quot;
  }
}</p>
<pre><code>
2.2 액션 생성함수 (Action Creator)  
```javascript
// 액션 생성함수는, 액션을 만드는 함수입니다. 단순히 파라미터를 받아와서 액션 객체 형태로 만들어주죠.

export function addTodo(data) {
  return {
    type: &quot;ADD_TODO&quot;,
    data
  };
}

// 화살표 함수로도 만들 수 있습니다.
export const changeInput = text =&gt; ({ 
  type: &quot;CHANGE_INPUT&quot;,
  text
});

2.3 Reducer
```javascript
// 리듀서는 변화를 일으키는 함수입니다. 리듀서는 두가지의 파라미터를 받아옵니다.
// 리듀서는, 현재의 상태와, 전달 받은 액션을 참고하여 새로운 상태를 만들어서 반환합니다. 이 리듀서는 useReducer 를 사용할때 작성하는 리듀서와 똑같은 형태를 가지고 있습니다.
// 만약 카운터를 위한 리듀서를 작성한다면 다음과 같이 작성할 수 있습니다.

const TsysMenuReducer = (state = INIT_STATE, action) =&gt; {
    switch (action.type) {
        case TSYS_MENU_GET_LIST: { return { ...state, loading: true, menuList: []};}
        case TSYS_MENU_GET_LIST_SUCCESS: { return { ...state, loading: false, menuList: action.payload.body};}
        case TSYS_MENU_GET_LIST_FAILURE: { return { ...state, loading: false};}
        case TSYS_MENU_ON_GET_LIST: { return { ...state, loading: true, menuList: []};}
        case TSYS_MENU_ON_GET_LIST_SUCCESS: { return { ...state, loading: false, grid: {data: action.payload.body}};}
        case TSYS_MENU_ON_GET_LIST_FAILURE: { return { ...state, loading: false};}
        default: return {...state};
    }
}

// useReducer 에선 일반적으로 default: 부분에 throw new Error(&#39;Unhandled Action&#39;)과 같이 에러를 발생시키도록 처리하는게 일반적인 반면 
// 리덕스의 리듀서에서는 기존 state를 그대로 반환하도록 작성해야합니다.
// 리덕스를 사용 할 때에는 여러개의 리듀서를 만들고 이를 합쳐서 루트 리듀서 (Root Reducer)를 만들 수 있습니다. (루트 리듀서 안의 작은 리듀서들은 서브 리듀서라고 부릅니다.)</code></pre><p>2.4 Store 
: 리덕스에서는 한 애플리케이션당 하나의 스토어를 만들게 됩니다. 스토어 안에는, 현재의 앱 상태와, 리듀서가 들어가있고, 추가적으로 몇가지 내장 함수들이 있습니다.  </p>
<p>2.5 dispatch<br>: 디스패치는 스토어의 내장함수 중 하나입니다. 디스패치는 액션을 발생 시키는 것 이라고 이해하시면 됩니다.<br>dispatch 라는 함수에는 액션을 파라미터로 전달합니다.. dispatch(action) 이런식으로 말이죠.<br>그렇게 호출을 하면, 스토어는 리듀서 함수를 실행시켜서 해당 액션을 처리하는 로직이 있다면 액션을 참고하여 새로운 상태를 만들어줍니다.  </p>
<pre><code class="language-javascript">import { useDispatch, useSelector } form &#39;react-redus&#39;;

  // useDispatch 는 리덕스 스토어의 dispatch 를 함수에서 사용 할 수 있게 해주는 Hook 입니다.
  const dispatch = useDispatch();
  // 각 액션들을 디스패치하는 함수들을 만드세요
  const onIncrease = () =&gt; dispatch(increase());
  const onDecrease = () =&gt; dispatch(decrease());
  const onSetDiff = diff =&gt; dispatch(setDiff(diff));</code></pre>
<p>2.6 subscribe<br>: 구독 또한 스토어의 내장함수 중 하나입니다. subscribe 함수는, 함수 형태의 값을 파라미터로 받아옵니다.<br>subscribe 함수에 특정 함수를 전달해주면, 액션이 디스패치 되었을 때 마다 전달해준 함수가 호출됩니다.<br>리액트에서 리덕스를 사용하게 될 때 보통 이 함수를 직접 사용하는 일은 별로 없습니다.<br>그 대신에 react-redux 라는 라이브러리에서 제공하는 connect 함수 또는 useSelector Hook 을 사용하여 리덕스 스토어의 상태에 구독합니다.  </p>
<p>2.7 useSelector</p>
<pre><code class="language-javascript">  // useSelector는 리덕스 스토어의 상태를 조회하는 Hook입니다.
  // state의 값은 store.getState() 함수를 호출했을 때 나타나는 결과물과 동일합니다.
  const { number, diff } = useSelector(state =&gt; ({
    number: state.counter.number,
    diff: state.counter.diff
  }));</code></pre>
<h3 id="reference">Reference</h3>
<ol>
<li>벨로퍼트 리액트
<a href="https://react.vlpt.us/">https://react.vlpt.us/</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[6. 배열에 항목 추가/제거/수정]]></title>
            <link>https://velog.io/@ty-yun21/6.-%EB%B0%B0%EC%97%B4%EC%97%90-%ED%95%AD%EB%AA%A9-%EC%B6%94%EA%B0%80%EC%A0%9C%EA%B1%B0%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@ty-yun21/6.-%EB%B0%B0%EC%97%B4%EC%97%90-%ED%95%AD%EB%AA%A9-%EC%B6%94%EA%B0%80%EC%A0%9C%EA%B1%B0%EC%88%98%EC%A0%95</guid>
            <pubDate>Wed, 08 Feb 2023 08:21:42 GMT</pubDate>
            <description><![CDATA[<h1 id="6-배열에-항목-추가제거수정">6. 배열에 항목 추가/제거/수정</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react6">https://velog.io/@ty-yun21/react6</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : </p>
<h2 id="목표">목표</h2>
<ol>
<li>배열 항목 추가</li>
<li>배열 항목 제거</li>
<li>배열 항목 수정</li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li>배열 항목 추가</li>
<li>배열 항목 제거</li>
<li>배열 항목 수정</li>
</ol>
<h3 id="reference">Reference</h3>
<ol>
<li>벨로퍼트 리액트
<a href="https://react.vlpt.us/">https://react.vlpt.us/</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[5. JSX, props, useState, useRef, 배열렌더링]]></title>
            <link>https://velog.io/@ty-yun21/5.-JSX-props-useState-useRef-%EB%B0%B0%EC%97%B4%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@ty-yun21/5.-JSX-props-useState-useRef-%EB%B0%B0%EC%97%B4%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Wed, 08 Feb 2023 08:20:57 GMT</pubDate>
            <description><![CDATA[<h1 id="5-jsx-props-usestate-useref-배열-렌더링">5. JSX, props, useState, useRef, 배열 렌더링</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react5">https://velog.io/@ty-yun21/react5</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : </p>
<h2 id="목표">목표</h2>
<ol>
<li>JSX</li>
<li>props </li>
<li>useState</li>
<li>useRef</li>
<li>배열 렌더링</li>
<li>useRef 심화</li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li>JSX
: HTML 문법 같이 생겼지만 실제로는 JS<br>: Babel을 통해서 자바스크립트 문법을 확장해줌 (ES6 --&gt; ES5)<br>: 꼭 태그는 닫혀 있어야 함
: 두개이상의 태그는 <div></div> 또는 fragment &lt;&gt;&lt;/&gt;로 닫혀 있어야함<br>fragment는 브라우저상 별도의 엘리먼트로 나타나지 않음<br>: JSX안에 JS값 사용하려면 {}로 감싸서 보여줘야 함
: style 적용하려면 background-color 같은 애들을 backgroundColor와 같이 camelCase로 네이밍<br>: CSS class 를 설정하려면 className= 으로 설정해줘야함 (Ex. <div className="main_contents">)  </li>
</ol>
<ol start="2">
<li>props를 통해 컴포넌트에 값 전달<pre><code class="language-javascript">return (
 &lt;Hello name=&#39;react&#39; /&gt;
);
</code></pre>
</li>
</ol>
<p>const Hello = (props) =&gt; {
    return <div>안녕하세요 {props.name}</div>
}</p>
<pre><code>2.2 여러개의 props  
2.3 defaultProps로 기본값 설정
```javascript
Hello.defaultProps = {
    name: &#39;noname&#39;
}</code></pre><p>2.4 props.children<br>: 컴포넌트 태그 사이에 넣은 값 조회시 props.children 사용
(Ex. wrapper아래에 컴포넌트 넣을 시 그냥은 안보인 Wrapper의 div 사이에 {children} 넣어줘야 함)  </p>
<ol start="3">
<li>useState를 통해 컴포넌트에서 바뀌는 값 관리</li>
<li>1 동적인 값 넣기  <pre><code class="language-javascript">const [number, setNumber] = useState(0);</code></pre>
</li>
<li>2 input 상태관리  <pre><code class="language-javascript">const [text, setText] = useState(&#39;&#39;);
</code></pre>
</li>
</ol>
<p>const onChange = (e) =&gt; {
    setText(e.target.value);
};
// e : 이벤트 객체
// e.target : 이벤트가 발생한 DOM을 가르킴 (여기서는 input DOM)
// DOM 의 value : e.target.value</p>
<pre><code>3.2 여러개 input 관리
```javascript
const [inputs, setInputs] = useState( {
    name: &#39;&#39;,
    nickname: &#39;&#39;
} );

const {name, nickname} = inputs; // 비구조화 할당을 통해 값 추출
const onChange = (e) =&gt; {
    const { value, name } = e.target;   //우선 e.target에서 name과 value를 추출
    setInputs({
        ...inputs, //기존의 input 객체를 복사한 뒤 (스프레드 문법)
        [name] : value //name키를 가진 값을 value로 설정
    });
};
</code></pre><ol start="4">
<li>useRef로 특정 DOM 선택하기
: 이럴 때 리액트에서는 ref 라는 것을 사용함
: ref를 사용하기 위해서는 useRef 라는 Hook 함수를 사용...<pre><code class="language-javascript">const nameInput = useRef();
</code></pre>
</li>
</ol>
<p>const {name, nickname} = inuts;</p>
<p>const onReset = () = {
    setInputs({
        name: &#39;&#39;,
        nickname: &#39;&#39;
    });
    nameInput.current.focus();
};</p>
<p>return (
    <div>
        <input
            name="name"
            placeholder="이름"
            ref={nameIput}
        />
    </div>
)</p>
<pre><code>

5. 배열 렌더링
```javascript
const users = [
    {
        id: 1,
        username: &#39;velopert1&#39;
    },
    {
        id: 2,
        username: &#39;velopert2&#39;
    }
];
return (
    &lt;div&gt;
        {users.map(user =&gt; (
            &lt;User user={user} key={user.id} /&gt;
        ))}
    &lt;/div&gt;
)
// 동적인 배열을 렌더릴 할때 자바스크립트 배열의 내장함수 map()을 사용
// users을 값들을 user객체로 받아와서 객체 수 만큼 뿌려줌 (내 생각) 객체의 정확한 의미는?
// 배열을 렌더릴할 때는 key라는 props를 설정해야 함... (key는 각 원소들이 가지고 있는 고유값...)
</code></pre><ol start="6">
<li>useRef 심화 (컴포넌트 안의 변수 만들기)
: useRef를 통해 DOM을 선택하는 용도 외에도 컴포넌트 안에서 조회 및 수정할 수 있는 변수 관리 가능  </li>
</ol>
<p>--&gt; setTimeout, setInterval을 통해 만들어진 id<br>--&gt; 외부 라이브러리를 사용하여 생성된 인스턴스
--&gt; scroll위치  </p>
<pre><code class="language-javascript">const users = [
    {
        id: 1,
        username: &#39;velopert1&#39;
    },
    {
        id: 2,
        username: &#39;velopert2&#39;
    }
];
const nextId = useRef(3);
const onCrate = () = {
    nextId.current += 1;
}

return (
    &lt;div&gt;
        {users.map(user =&gt; (
            &lt;User user={user} key={user.id} /&gt;
        ))}
    &lt;/div&gt;
)</code></pre>
<h3 id="reference">Reference</h3>
<ol>
<li>벨로퍼트 리액트
<a href="https://react.vlpt.us/">https://react.vlpt.us/</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[4. Wijmo]]></title>
            <link>https://velog.io/@ty-yun21/react4</link>
            <guid>https://velog.io/@ty-yun21/react4</guid>
            <pubDate>Wed, 08 Feb 2023 08:18:41 GMT</pubDate>
            <description><![CDATA[<h1 id="4-wijmo">4. Wijmo</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react4">https://velog.io/@ty-yun21/react4</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : 4. Wijmo</p>
<h2 id="목표">목표</h2>
<ol>
<li>Wijmo 적용</li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li>Wijmo 적용</li>
<li>1 라이브러리 추가<br>npm i @grapecity/wijmo.react.all
<a href="https://demo.grapecity.co.kr/wijmo/docs/GettingStarted/QuickStart/QuickStart-React">https://demo.grapecity.co.kr/wijmo/docs/GettingStarted/QuickStart/QuickStart-React</a>  </li>
</ol>
<pre><code class="language-javascript">import * as wjFlexGrid from &#39;@grapecity/wijmo.react.grid&#39;;
import * as wjGrid from &#39;@grapecity/wijmo.grid&#39;;
import * as wjcCore from &#39;@grapecity/wijmo&#39;;
import * as wjGridFilter from &#39;@grapecity/wijmo.react.grid.filter&#39;;
import * as wjGroupPanel from &#39;@grapecity/wijmo.react.grid.grouppanel&#39;;
import { Selector, BooleanChecker } from &#39;@grapecity/wijmo.grid.selector&#39;;
import &#39;@grapecity/wijmo.touch&#39;; // add touch support on mobile devices
import { InputDate, InputTime, ComboBox, AutoComplete, InputColor, InputNumber } from &#39;@grapecity/wijmo.input&#39;;</code></pre>
<h3 id="reference">Reference</h3>
<ol>
<li>Wijmo 홈페이지<br><a href="https://demo.grapecity.co.kr/wijmo/docs/GettingStarted/QuickStart/QuickStart-React">https://demo.grapecity.co.kr/wijmo/docs/GettingStarted/QuickStart/QuickStart-React</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[3. useState, local Storage, useEffect]]></title>
            <link>https://velog.io/@ty-yun21/react3</link>
            <guid>https://velog.io/@ty-yun21/react3</guid>
            <pubDate>Wed, 01 Feb 2023 02:13:05 GMT</pubDate>
            <description><![CDATA[<h1 id="3-usestate-local-storage-useeffect">3. useState, local Storage, useEffect</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react3">https://velog.io/@ty-yun21/react3</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : 2. UI &amp; Router *</p>
<h2 id="목표">목표</h2>
<ol>
<li>useState 활용                                           </li>
<li>local Storage</li>
<li>useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기</li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li>useState 활용
컴포넌트에서 보여줘야 하는 내용이 사용자 인터랙션에 따라 바뀌어야 할 때 어떻게 구현할 수 있는지<br>컴포넌트에서 동적인 값을 상태(state)라고 부릅니다. 리액트에 useState 라는 함수가 있는데요, 이것을 사용하면 컴포넌트에서 상태를 관리 할 수 있습니다.  </li>
</ol>
<p>1.1 ./container/App.js</p>
<pre><code>    const [ isActive, setActive] = useState(false);

    const onClickMenu = () =&gt; {
        setActive(!isActive);
    }</code></pre><ol start="2">
<li>local Storage
<a href="https://www.daleseo.com/js-web-storage/">https://www.daleseo.com/js-web-storage/</a>  </li>
<li>1 웹 스토리지
로컬 스토리지 vs. 세션 스토리지<br>웹 스토리지(web storage)에는 로컬 스토리지(localStorage)와 세션 스토리지(sessionStorage)가 있습니다. 이 두 개의 매커니즘의 차이점은 데이터가 어떤 범위 내에서 얼마나 오래 보존되느냐에 있습니다. 세션 스토리지는 웹페이지의 세션이 끝날 때 저장된 데이터가 지워지는 반면에, 로컬 스토리지는 웹페이지의 세션이 끝나더라도 데이터가 지워지지 않습니다.</li>
</ol>
<p>다시 말해, 브라우저에서 같은 웹사이트를 여러 탭이나 창에 띄우면, 여러 개의 세션 스토리지에 데이터가 서로 격리되어 저장되며, 각 탭이나 창이 닫힐 때 저장해 둔 데이터도 함께 소멸합니다. 반면에, 로컬 스토리지의 경우 여러 탭이나 창 간에 데이터가 서로 공유되며 탭이나 창을 닫아도 데이터는 브라우저에 그대로 남아 있습니다.</p>
<p>2.1 App.js  </p>
<pre><code>    const [stateConfig, setStateConfig] = useState(
        JSON.parse(window.localStorage.getItem(&#39;portal-config&#39;)) === null
        ? defaultConfig
        : JSON.parse(window.localStorage.getItem(&#39;portal-config&#39;))
    );

window.localStorage.setItem(&#39;portal-config&#39;, JSON.stringify(stateConfig));

</code></pre><ol start="3">
<li>useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기
<a href="https://react.vlpt.us/basic/16-useEffect.html">https://react.vlpt.us/basic/16-useEffect.html</a>  </li>
</ol>
<h3 id="reference">Reference</h3>
<ol>
<li><a href="https://react.vlpt.us/basic/07-useState.html">https://react.vlpt.us/basic/07-useState.html</a>  </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[2. React Router]]></title>
            <link>https://velog.io/@ty-yun21/react2</link>
            <guid>https://velog.io/@ty-yun21/react2</guid>
            <pubDate>Tue, 31 Jan 2023 07:52:33 GMT</pubDate>
            <description><![CDATA[<h1 id="ui--react-router">UI &amp; React Router</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react2">https://velog.io/@ty-yun21/react2</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a><br>commit message : 2. UI &amp; Router *</p>
<h2 id="목표">목표</h2>
<ol>
<li>material-ui 라이브러리 적용</li>
<li>react router 사용</li>
<li>styled-components
<a href="https://styled-components.com/">https://styled-components.com/</a></li>
</ol>
<h3 id="정리">정리</h3>
<ol>
<li><p>material-ui : src/App.js 참고
<a href="https://material-ui-pickers.dev/getting-started/installation">https://material-ui-pickers.dev/getting-started/installation</a>  </p>
</li>
<li><p>react router : src/App.js<br>react-router-dom v5 v6 차이점 알아두기<br><a href="https://blog.woolta.com/categories/1/posts/211">https://blog.woolta.com/categories/1/posts/211</a><br><a href="https://sennieworld.tistory.com/30">https://sennieworld.tistory.com/30</a><br>(Ex. switch --&gt; routes, component --&gt; element, withRouter 사라짐, match.url 못씀)  </p>
</li>
</ol>
<p>2.1 components/molecules/LeftMenu/MenuItems</p>
<pre><code>    const setSelectedMenuItem = (e) =&gt; {   
        if(!e.item.props.path.includes(&#39;http&#39;)){            
            navigate(e.item.props.path);
        }else{
            window.open(e.item.props.path, &#39;_blank&#39;, &#39;noopener,noreferrer&#39;);
        }
    }</code></pre><p>2.2 src/App.js<br>각 메뉴의 접근은 app/<em>/</em> (ex. app/sys/menu) 식으로 함</p>
<pre><code>    &lt;Router&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;App/&gt;} /&gt;
        &lt;Route path=&quot;app/*&quot; element={&lt;RouteService /&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/Router&gt;</code></pre><p>2.3 ./utils/RouteService
_routerService에 대메뉴 mapdmf 해둠</p>
<pre><code>import routerService from &#39;../services/_routerService&#39;;
                &lt;Routes&gt;
                    {routerService &amp;&amp; routerService.map((route, key) =&gt; 
                        &lt;Route key={key} path=&quot;sys/*&quot; element={&lt;route.element /&gt;} /&gt;
                    )}
                &lt;/Routes&gt;</code></pre><p>2.4 ./routes/system</p>
<pre><code>import loadable from &#39;../../utils/loadable&#39;;

const MenuPage = loadable(() =&gt; import(&#39;./MenuPage&#39;));
const Pages = ({match}) =&gt; {    
    return (
        &lt;div className=&#39;content-wrapper&#39;&gt;
            &lt;Routes&gt;                
                &lt;Route path=&quot;menu&quot; element={&lt;MenuPage /&gt;} exact /&gt;
            &lt;/Routes&gt;
        &lt;/div&gt;
    );
};</code></pre><ol start="3">
<li>styled-components
index.js와 동일경로에 Styeld.js 생성 후 import 함<pre><code>import Styled from &#39;./Styled&#39;;</code></pre></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 개발환경 세팅]]></title>
            <link>https://velog.io/@ty-yun21/react1</link>
            <guid>https://velog.io/@ty-yun21/react1</guid>
            <pubDate>Tue, 17 Jan 2023 23:45:49 GMT</pubDate>
            <description><![CDATA[<h1 id="getting-started-with-create-react-app">Getting Started with Create React App</h1>
<p>참고용 velog
<a href="https://velog.io/@ty-yun21/react1">https://velog.io/@ty-yun21/react1</a>
참고용 github
<a href="https://github.com/ty-yoon21/react-study1">https://github.com/ty-yoon21/react-study1</a></p>
<h2 id="목표">목표</h2>
<ol>
<li>cra를 통해 react app 생성</li>
<li>package.json을 통해 의존 라이브러리 설정</li>
<li>webpack, babel을 이용해 개발환경 설정</li>
</ol>
<h2 id="사용법">사용법</h2>
<ol>
<li>make directory</li>
<li>git clone <a href="https://github.com/ty-yoon21/react-study1.git">https://github.com/ty-yoon21/react-study1.git</a></li>
<li>open with vscode</li>
<li><code>npm install</code></li>
<li><code>npm start</code><br>Runs the app in the development mode.<br>Open <a href="http://localhost:3000">http://localhost:3000</a> to view it in your browser.</li>
</ol>
<h2 id="settings">Settings</h2>
<h3 id="정리">정리</h3>
<ol>
<li>npx create-react-app 을 통해서 프로젝트 생성
: 현재 react 18로 pjt가 생성되기 때문에 나는 17로 변경함</li>
<li>npm, yarn 사용법에 대해 알아두자</li>
<li>package.json 에 대해 알아두자 </li>
<li>webpack, babel을 이용해서 dev환경 실행<br>webpack (webpack.config.js), babel (babelrc) 사용하는 이유와 사용법에 대해 알아두자  </li>
</ol>
<h3 id="reference">Reference</h3>
<ol>
<li><p>package.json 관련 설명<br><a href="https://velog.io/@couchcoding/React-2-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-package.json">https://velog.io/@couchcoding/React-2-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-package.json</a>  </p>
</li>
<li><p>package-lock.json<br><a href="https://hyunjun19.github.io/2018/03/23/package-lock-why-need/">https://hyunjun19.github.io/2018/03/23/package-lock-why-need/</a><br>: 의존성 트리에 대한 정보를 가지고 있으며 해당 파일이 작성도니 시점의 의존성 트리가 다시 생성될 수 있도록 보장<br>: npm create-react-app 을 통해 생성한 파일들과 지금의 것을 비교해보면 좋다  </p>
</li>
<li><p>create-react-app 18 —&gt; 17<br>나의 경우 material-ui등 react 18에서 사용하지 못하는 lib를 사용하였기 때문에 17로 version 변경함<br><a href="https://trend21c.tistory.com/2250">https://trend21c.tistory.com/2250</a><br>1) package.json --&gt; React, react-dom 17.0.0 으로 변경 이후<br><code>npm i</code>
2) src/App.js 파일 변경
```
import ReactDOM from &#39;react-dom/client&#39;;</p>
</li>
</ol>
<p>--&gt;
import ReactDOM from &#39;react-dom&#39;;</p>
<pre><code>
4. npm devDependencies  
package.json에 devDependencies 항목에 lib 추가하기 위해서는 아래 명령어 필요</code></pre><p>npm install <package-name> --save-dev</p>
<pre><code>
5. yarn 설치  
나 같은 경우 yarn add하는 경우 package.json에 추가가 되지 않아서 npm install 을 통해서 lib 추가 하였음...</code></pre><p>Nom install -g yarn
Yarn install
Yarn build</p>
<p>```</p>
<ol start="6">
<li>react + webpack + babel로 개발 환경 구축하기 (javascript)<br><a href="https://velog.io/@jinsunee/2.-react-webpack-babel%EB%A1%9C-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0">https://velog.io/@jinsunee/2.-react-webpack-babel%EB%A1%9C-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</a><br>webpack.config.js 설정 하는 방법 알아야 함<br>webpack과 babel을 왜 쓰는지 알아야 함<br><a href="https://velog.io/@yon3115/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%95%84%EC%88%98-Webpack%EC%9D%B4%EB%9E%80">https://velog.io/@yon3115/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%95%84%EC%88%98-Webpack%EC%9D%B4%EB%9E%80</a></li>
</ol>
]]></description>
        </item>
    </channel>
</rss>