React

 

 

Redux 연동하기

 

버전이 다르기 때문에 소스가 강좌와 다를 수 있다.

버전

next:  13.0.4

antd:  5.0.1

 

소스 : https://github.dev/braverokmc79/node-bird-sns

 

 

 

 

 

13. 리덕스 설치와 필요성 소개

 

강의 : https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48800?tab=curriculum

 

설치

 

$ npm i next-redux-wrapper

$ npm i react-redux redux redux-promise redux-thunk

 

중앙데이타저장소 Store

  • 컴포넌트에서 공통적으로 쓰이는 데이타가 흩어져있기 때문에 부모 컴포넌트에서 데이타를 받아서 자식 컴포넌트에게 각각 보내줘야한다
  • 컴포넌트끼리 데이타를 전달하는 과정도 매우 복잡하고 오류가 발생하기도 쉽다.

  •  
  • 규모가 있는 서비스라면 중앙데이타저장소 Store를 최소 한 개 만들어 중앙에서 모든 데이타를 관리하고 보내주는 것이 편하다
  • 중앙데이타저장소는 Redux, React의 contextedAPI, Mobax, Apolo 등이 있다

Redux

  • Redux는 원리가 간단하고 모든 수정사항을 기록한다.
  • 모든 히스토리가 기록되어 에러 추적이 쉽고 안정적이다.
  • 하지만 코드량이 많아진다.
  • 중앙은 앱이 커지면 데이타를 쪼개는 작업이 필요한데 Redux는 reducer들을 쪼개는 기능을 제공해주어 편리하다 (reducer란 전달받은 action에 따라 상태를 어떻게 업데이트할지 정의해주는 함수)
  • 사용법이 쉬워 초보자에게 추천되지만 전체적으로 가장 많이 사용되는 라이브러리기도 하다

Mobax

  • Mobax는 코드량이 매우 적어 생산성을 높일 수 있다
  • 하지만 실수를 했을 때 추적이 어렵다
  • 초보를 벗어나면 생산성을 위해 추천되는 라이브러리지만 Redux보다는 사용률이 낮다

Context API

  • 앱 규모가 작을 때 권장된다
  • 큰 규모의 프로젝트는 차라리 리덕스나 모백스를 사용하는 것이 낫다
    • 중앙데이타저장소는 서버에서 데이타를 받아오는데 이 과정은 비동기다
    • 서버가 고장나거나 네트워크 에러가 생기면 데이타가 안 올수 있다
    • 실패에 대비하기위해 요청, 성공, 실패 3단계를 직접 구현해야한다.
    • 3단계 구현 코드를 컴포넌트마다 넣어줘야해 의도치 않은 코드 중복이 발생
    • 위의 코드만 밖으로 따로 빼낼 수 있지만 비동기 요청이 많으면 context API도 리덕스와 모백스와 비슷해진다
    • 따라서 리덕스나 모백스 등이 알아서 처리해주는 것이 편하다

 

useEffect(() => {
    	axios.get('/data')
      		.then(() => {
        		setState(data);
        })
      		.catch(() => {
        		setError(error);
        })
    })

 

Redux && redux-wrapper

설치

  • 넥스트가 설치되어있는 front 디렉토리에서 리덕스 설치
    npm i redux

  • Redux의 손쉬운 사용을 위해 redux-wrapper 설치 (Redux와 사용법이 약간 다르다)
    npm i next-redux-wrapper

  • front/store/configureStore.js 파일 생성

import { createWrapper } from 'next-redux-wrapper';
      import {createStore} from 'redux';

      const configureSotre = () => {
        const store = createStore(reducer);
        return store;
      };

      const wrapper = createWrapper(configureSotre, {
        debug: process.env.NODE_ENV === 'development,'
      });

      export default wrapper;

 

 

  • _app.js 파일 수정
    • wrapper 불러오기
    • wrapper를 사용해서 내보내기
    • Nodebird는 앱 이름

 

import wrapper from '../store/configureStore.js`
	.
	.
    	.
    	export default wrapper.withRedux(Nodebird);
	//wrapper로 감싸줘야 프로젝트의 모든 컴포넌트와 페이지에 적용된다

 

  • 예전에는 _app.js의 return 내부를 Provider로 감싸야했는데 지금은 redux가 알아서 감싸주니 비워두어야한다 (사용자가 감싸면 중복되어 에러 발생)
//before
	return (
            <Provider store={store}>
            </Provider>
    	);


	//from redux@6
	return (
            <>
            </>
    	);

 

 

 

 

 

 

 

 

14. 리덕스의 원리와 불변성

 

강의 :https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48801?tab=curriculum

 

 

Redux의 원리

진행 과정

 

 

  1. 앱의 상태를 객체 형식으로 작성한다 state (좌)
  2. 변경하고 싶은 내용을 action으로 만든다 (우)
    • action의 이름을 적는 type
    • 변경사항을 적는 data
  3. action을 dispatch한다
  4. dispatch된 action을 reducer에 따라 처리한다 (아래)
    • reducer는 action 어떻게 처리할지 정의해준다
    • 예를 들어 switch를 값을 변경해주는 것이다
    • case에 action의 type을 적어준다
    • 바꾸고싶은 값에 action.data를 넣어준다
  •  

 

  • action을 생성해서 무엇으로 변경할지 적어준다

    • 변경사항마다 action을 만들어줘야한다

 

{
    type: 'CHANGE_NICKNAME' // action이름
    data: 'boogicho' // 변경될 데이타값
  }
  {
    type: 'CHANGE_AGE'
    data: 30,
  }

 

 

 

 

 

 

 

 

 

15. 리덕스의 원리와 불변성

 

강의 : https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48802?tab=curriculum

 

 

pages/_app.js

~

import wrapper from '../store/configureStore';



~
export default wrapper.withRedux(NodeBird);

 

 

$ npm i redux-devtools-extension

 

 

store/configureStore.js

import { createWrapper } from 'next-redux-wrapper';
import { compose, applyMiddleware, legacy_createStore as createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';

import reducer from '../reducers';

const configureSotre = () => {
    const middlewares = [];
    const enhancer = process.env.NODE_ENV === 'production'
        ? compose(applyMiddleware(...middlewares))
        : composeWithDevTools(applyMiddleware(...middlewares));

    const store = createStore(reducer, enhancer);
    return store;
};


const wrapper = createWrapper(configureSotre, {
    debug: process.env.NODE_ENV === 'development,'
});

export default wrapper;


// import Reducer from './_reducers';
// import { applyMiddleware, legacy_createStore as createStore } from 'redux';
// import promiseMiddleware from 'redux-promise';
// import ReduxThunk from 'redux-thunk';
// const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore);
// const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
// createStoreWithMiddleware(Reducer, devTools)

 

 

reducer/index.js

const initialState = {
    user: {
        isLoggedIn: false,
        user: null,
        signUpdata: {},
        loginData: {}
    },
    post: {
        mainPosts: []
    }
}


export const loginAction = (data) => {
    return {
        type: "LOG_IN",
        data
    }
}

export const logoutAction = () => {
    return {
        type: "LOG_OUT"
    }
}


//(이전상태,액션)  => 다음 상태
const rootReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'LOG_IN':
            return {
                ...state,
                user: {
                    ...state.user,
                    isLoggedIn: true,
                    user: action.data
                }
            }
        case 'LOG_OUT':
            return {
                ...state,
                user: {
                    ...state.user,
                    isLoggedIn: false,
                    user: null
                }
            }
        default:
            return state;
    }
}

export default rootReducer;


 

 

components/AppLayout.js

import { useSelector } from 'react-redux';

~
const AppLayout = ({ children }) => {

    const isLoggedIn = useSelector((state) => state.user.isLoggedIn);


~

~


  <Col xs={24} md={6} className="mt-20">
                    {isLoggedIn ? <UserProfile /> : <LoginForm />}
                </Col>

~


 

componets/UserProfile.js

import React, { useCallback } from 'react';
import { Card, Avatar, Button } from 'antd';
import { useDispatch } from 'react-redux';
import { logoutAction } from '../reducers';

const UserProfile = () => {

    const dispatch = useDispatch();


    const onLogOut = useCallback(() => {
        dispatch(logoutAction(false));
    }, []);

~

 

 

 

 

 

 

 

 

 

16. 미들웨어와 리덕스 데브툴즈

강의 : 

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48803?tab=curriculum

 

 

$ npm i redux-devtools-extension

 

import { createWrapper } from 'next-redux-wrapper';
import { compose, applyMiddleware, legacy_createStore as createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';

import reducer from '../reducers';

const configureSotre = () => {
    const middlewares = [];
    const enhancer = process.env.NODE_ENV === 'production'
        ? compose(applyMiddleware(...middlewares))
        : composeWithDevTools(applyMiddleware(...middlewares));

    const store = createStore(reducer, enhancer);
    return store;
};


const wrapper = createWrapper(configureSotre, {
    debug: process.env.NODE_ENV === 'development,'
});

export default wrapper;


// import Reducer from './_reducers';
// import { applyMiddleware, legacy_createStore as createStore } from 'redux';
// import promiseMiddleware from 'redux-promise';
// import ReduxThunk from 'redux-thunk';
// const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore);
// const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
// createStoreWithMiddleware(Reducer, devTools)

 

 

 

 

 

 

 

 

 

17. 리듀서 쪼개기

 

강의 : https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48804?tab=curriculum

 

reducers/index.js

import { HYDRATE } from 'next-redux-wrapper';
import { combineReducers } from 'redux';

import user from './user';
import post from './post';


//(이전상태,액션)  => 다음 상태
const rootReducer = combineReducers({
    index: (state = {}, action) => {
        switch (action.type) {
            case HYDRATE:
                console.log(' HYDRATE ', action);
                return { ...state, ...action.payload };
            default:
                return state;
        }
    },
    user,
    post
});

export default rootReducer;


 

 

reducers/user.js

export const initialState = {
    isLoggedIn: false,
    user: null,
    signUpdata: {},
    loginData: {}
}

export const loginAction = (data) => {
    return {
        type: "LOG_IN",
        data
    }
}

export const logoutAction = () => {
    return {
        type: "LOG_OUT"
    }
}

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case 'LOG_IN':
            return {
                ...state,
                isLoggedIn: true,
                user: action.data
            }
        case 'LOG_OUT':
            return {
                ...state,
                isLoggedIn: false,
                user: null
            }
        default:
            return state;
    }
}


export default reducer;


 

 

reducers/post.js

const initialState = {
    mainPosts: [],
}

const reducer = (state = initialState, action) => {
    switch (action.type) {
        default:
            return state;
    }
}


export default reducer;


 

 

 

[Next.js] Hydrate란?

Hydrate란, Server Side단에서 렌더링 된 정적 페이지와 번들링된 js파일(Webpack)을 클라이언트에게 보낸 뒤, 클라이언트 단에서 HTML 코드와 React인 js 코드를 서로 매칭시키는 과정을 말합니다.

 

 

 

일단, Client Side Rendering인 기본 React에서는 js파일만을 이용하여 public/index.html의 기본 뼈대만 있는 내용을 제외하고 src/index.js의 자바스크립트 코드에서 모든 화면을 렌더링 한 뒤, HTML DOM 요소 중 root라는 엘리먼트를 찾아 하위로 주입을 하여 웹 화면이 구성합니다. 

 

Next.js에서는 클라이언트에게 웹 페이지를 보내기 전에 Server Side 단에서 미리 웹 페이지를 Pre-Rendering 합니다.

Pre-Rendering하면 HTML document가 생성되고, 이 파일을 클라이언트에게 전송합니다.

 

이때, 클라이언트가 받은 웹페이지는 단순한 웹 화면만 보여주는 HTML일 뿐이고 자바스크립트 요소는 하나도 없는 상태입니다. 이는 웹 화면을 보여주고 있지만, 특정 JS 모듈 뿐만 아니라 단순 클릭과 같은 이벤트 리스너들도 각 웹페이지의 DOM 요소에 적용되어 있지 않은 상태를 말합니다.

 

  • React.js : Html과 JS파일을 한꺼번에 보내고 클라이언트가 js 코드를 통해 웹 화면을 렌더링
  • Next.js : Pre-Rendering된 웹 페이지를 클라이언트에게먼저 보내고, React가 번들링된 자바스크립트 코드들을 클라이언트에게 전송함.

 

참조 : https://narup.tistory.com/230

 

 

 

 

 

 

 

 

 

 

18. 더미데이터와 포스트폼 만들기

 

강의 :

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48805?tab=curriculum

 

pages/index.js

import React from 'react';
import AppLayout from './../components/AppLayout';
import { useSelector } from 'react-redux';
import PostCard from './../components/PostCard';
import PostForm from './../components/PostForm';

const Index = () => {
    const { isLoggedIn } = useSelector((state) => state.user);
    const { mainPosts } = useSelector((state) => state.post);

    return (
        <AppLayout>
            {isLoggedIn && <PostForm />}
            {mainPosts.map((post) => <PostCard key={post.id} post={post} />)}
        </AppLayout>
    );
};

export default Index;

 

 

components/PostCard.js

import React from 'react';

const PostCard = () => {
    return (
        <div>
            PostCard
        </div>
    );
};

export default PostCard;

 

 

components/PostForm.js

import React, { useCallback, useRef, useState } from 'react';
import { Form, Input, Button } from 'antd';
import { useSelector, useDispatch } from 'react-redux';
import { addPost } from '../reducers/post';


const PostForm = () => {
    const { imagePaths } = useSelector((state) => state.post);
    const dispatch = useDispatch();
    const imageInput = useRef();

    const [text, setText] = useState('');
    const onChangeText = useCallback((e) => {
        setText(e.target.value);
    }, []);

    const onSubmit = useCallback(() => {
        dispatch(addPost);
        setText("");
    }, []);


    const onClickImageUpload = useCallback(() => {
        imageInput.current.click();

    }, [imageInput.current]);

    return (
        <Form style={{ margin: '10px 0 20px' }} encType="multipart/form-data" onFinish={onSubmit}>
            <Input.TextArea
                value={text}
                onChange={onChangeText}
                maxLength={140}
                placeholder="어떤 신기한 일이 있었나요?"
            />

            <div>
                <input type="file" multiple hidden ref={imageInput} style={{ display: "none" }} />

                <Button onClick={onClickImageUpload}>이미지 업로드</Button>
                <Button type="primary" htmlType='submit' style={{ float: 'right' }} >짹짹</Button>
            </div>
            <div>
                {
                    imagePaths.map((v) => (
                        <div key={v} style={{ display: "inline-block" }}>
                            <img src={v} style={{ width: '200px' }} alt={v} />
                            <div>
                                <Button>제거</Button>
                            </div>
                        </div>
                    ))
                }
            </div>
        </Form>
    );
};

export default PostForm;

 

 

 

 

 

 

 

 

 

19. 게시글 구현하기

 

강의 :https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48806?tab=curriculum

 

components/PostCard.js

 

import React, { useState, useCallback } from 'react';
import { Card, ButtonGroup, Button, Avatar, Image, Popover, Space } from 'antd';
import { RetweetOutlined, HeartOutlined, MessageOutlined, EllipsisOutlined, HeartTwoTone } from '@ant-design/icons';
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux';
import PostImages from './PostImages';


const PostCard = ({ post }) => {
    const id = useSelector((state) => state.user.me?.id);
    const [liked, setLiked] = useState(false);
    const [commentFormOpened, setCommentFormOpened] = useState(false);
    const onToggleLike = useCallback(() => {
        setLiked((prev) => !prev);
    }, []);

    const onToggleComment = useCallback(() => {
        setCommentFormOpened((prev) => !prev);
    }, [])

    const content = (
        <div>
            {id && post.User.id === id ? (
                <>
                    <Button>수정</Button>
                    <Button type='danger'>삭제</Button>
                </>
            ) : (
                <Button>신고</Button>
            )
            }
        </div >
    );
    return (
        <div style={{ marginBottom: 30 }}>
            <Card


                cover={post.Images[0] && <PostImages images={post.Images} />}

                actions={[
                    <RetweetOutlined key="retweet" />,
                    liked ? <HeartTwoTone key="heart" twoToneColor="#ebef96" onClick={onToggleLike} /> :
                        <HeartOutlined key="heart" onClick={onToggleLike} />,

                    <MessageOutlined key="comment" onClick={onToggleComment} />,

                    <Popover content={content} title="더보기" key="popover" style={{ textAlign: "center" }}>
                        <EllipsisOutlined />
                    </Popover>
                ]}
            >

                <Card.Meta
                    avatar={<Avatar>{post.User.nickname[0]}</Avatar>}
                    title={post.User.nickname}
                    description={post.content}
                />

                <Image />

                <Button></Button>

            </Card >
            {commentFormOpened && (
                <div>
                    댓글 부분
                </div>
            )}
            {/* <CommentForm />
            <Comments /> */}

        </div >
    );
};

PostCard.propTypes = {
    post: PropTypes.shape({
        id: PropTypes.number,
        User: PropTypes.object,
        content: PropTypes.string,
        createdAt: PropTypes.object,
        Comment: PropTypes.arrayOf(PropTypes.object),
        Images: PropTypes.arrayOf(PropTypes.object)
    }).isRequired
}


export default PostCard;

 

 

components/PostImages.js

import React from 'react';

const PostImages = () => {
    return (
        <div>
            PostImages
        </div>
    );
};

export default PostImages;

 

 

 

 

 

 

 

 

 

 

20. 댓글 구현하기

강의 :https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48807?tab=curriculum

 

components/PostCard.js

import React, { useState, useCallback } from 'react';
import { Card, Button, Avatar, Image, Popover, List } from 'antd';
import { Comment } from '@ant-design/compatible';

import { RetweetOutlined, HeartOutlined, MessageOutlined, EllipsisOutlined, HeartTwoTone } from '@ant-design/icons';
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux';
import PostImages from './PostImages';
import CommentForm from './CommentForm';
//import Comment from './Comment';


const PostCard = ({ post }) => {
    const id = useSelector((state) => state.user.me?.id);
    const [liked, setLiked] = useState(false);
    const [commentFormOpened, setCommentFormOpened] = useState(false);
    const onToggleLike = useCallback(() => {
        setLiked((prev) => !prev);
    }, []);

    const onToggleComment = useCallback(() => {
        setCommentFormOpened((prev) => !prev);
    }, [])

    const content = (
        <div>
            {id && post.User.id === id ? (
                <>
                    <Button>수정</Button>
                    <Button type='danger'>삭제</Button>
                </>
            ) : (
                <Button>신고</Button>
            )
            }
        </div >
    );
    return (
        <div style={{ marginBottom: 30 }}>
            <Card
                cover={post.Images[0] && <PostImages images={post.Images} />}
                actions={[
                    <RetweetOutlined key="retweet" />,
                    liked ? <HeartTwoTone key="heart" twoToneColor="#ebef96" onClick={onToggleLike} /> :
                        <HeartOutlined key="heart" onClick={onToggleLike} />,
                    <MessageOutlined key="comment" onClick={onToggleComment} />,
                    <Popover content={content} title="더보기" key="popover" style={{ textAlign: "center" }}>
                        <EllipsisOutlined />
                    </Popover>
                ]}
            >

                <Card.Meta
                    avatar={<Avatar>{post.User.nickname[0]}</Avatar>}
                    title={post.User.nickname}
                    description={post.content}
                />

                <Image />



            </Card >
            {commentFormOpened && (
                <div>

                    <CommentForm post={post} />
                    <List
                        header={`${post.Comments.length} 개의 댓글`}
                        itemLayout="horizontal"
                        dataSource={post.Comments}
                        renderItem={(item) => (
                            <li>
                                <Comment
                                    author={item.User.nickname}
                                    avatar={<Avatar>{item.User.nickname[0]}</Avatar>}
                                    content={item.content}
                                />
                            </li>
                        )}
                    />
                </div>
            )}

            {/* <CommentForm />
            <Comments /> */}

        </div >
    );
};

PostCard.propTypes = {
    post: PropTypes.shape({
        id: PropTypes.number,
        User: PropTypes.object,
        content: PropTypes.string,
        createdAt: PropTypes.object,
        Comment: PropTypes.arrayOf(PropTypes.object),
        Images: PropTypes.arrayOf(PropTypes.object)
    }).isRequired
}


export default PostCard;

 

 

 

 

 

 

 

 

 

21. 이미지 구현하기

강의 : https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48808?tab=curriculum

components/PostImages.js

import React, { useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { PlusOutlined } from '@ant-design/icons';



const PostImages = ({ images }) => {
    const [showImageZoom, setShowImagesZoom] = useState(false);

    const onZoom = useCallback(() => {
        setShowImagesZoom(true);
    }, []);


    if (images.length === 1) {
        return (
            <>
                <img role="presentation" src={images[0].src} alt={images[0].src}
                    onClick={onZoom} width="50%" />
            </>
        )
    } else if (images.length === 2) {
        return (
            <>
                <div>
                    <img role="presentation" src={images[0].src} alt={images[0].src} onClick={onZoom} style={{ width: "50%" }} />
                    <img role="presentation" src={images[1].src} alt={images[1].src} onClick={onZoom} style={{ width: "50%" }} />
                </div>
            </>
        )

    } else if (images.length > 2) {

        return (
            <>
                <div>
                    <img role="presentation" src={images[0].src} alt={images[0].src}
                        onClick={onZoom} style={{ width: "50%" }} />

                    <div
                        role="presentation"
                        style={{ display: 'inline-block', width: '50%', textAlign: 'center', verticalAlign: 'middle' }}
                        onClick={onZoom}
                    >
                        <PlusOutlined />
                        <br />
                        {images.length - 1}
                        개의 사진 더보기
                    </div>
                </div>
            </>
        );
    }

};



PostImages.propTypes = {
    image: PropTypes.object.isRequired
}


export default PostImages;

 

 

 

 

 

 

 

 

 

 

22. 이미지 캐루셀 구현하기(react-slick)

 

강의 :

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48809?tab=curriculum

 

슬릭 슬라이드  : 
 

$ npm i react-slick



$ npm install slick-carousel

 

 

Carousel 만들기 (React-Slick)

 

 

components/ImagesZoom/index.js

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Slick from 'react-slick';

import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";

import styled, { createGlobalStyle } from 'styled-components';




const Overlay = styled.div`
    position:fixed;
    z-index:5000;
    top:0;
    left:0;
    right:0;
    bottom:0;
`;

const Header = styled.header`
    height:44px;
    background:white;
    position:relative;
    padding:0;
    text-align:center;

    & h1{
        margin:0;
        font-size:17px;
        color:#333;
        line-height:44px;
    }
    & button{
        position:absolute;
        right:0;
        top:0;
        padding:15px;
        line-height:10px;
        cursor:pointer;
        background: #faad14;
        color: #fff;
        border: 1px solid #fa8c16;
    }
`;

const SlickWrapper = styled.div`
    height:calc(100% -44px);
    background:rgba(0, 0, 0, 0.9);
    border:none !important;
    height: 100%;
`;

const ImageWrapper = styled.div`
    padding:32px;
    text-align:center;
    cursor:pointer;
    &img{
        margin:0 auto;
        max-height:750px;
        display: inline-block !important;
    }
`;


const Indicator = styled.div`    
    text-align :cetner;

     & > div{
        width:75px;
        height:30px;
        line-height:30px;
        border-radius:15px;
        background:#313131;
        display:inline-block;
        text-align:center;
        top:60px;
        background:#fff;
        position: relative;
     }

`;

const Global = createGlobalStyle`
    .slick-slide{
        display:inline-block;
    }
    .ant-card-cover{
        transform:none !important;
    }

    .slick-dots li button:before{
        color:#fff;
        opacity:1;
    }
    .slick-dots li.slick-active button:before {
         opacity: 1;
         color:#faad14;
    }

    .slick-prev, .slick-nex{
        width: 50px;
        height: 50px;
    }
    .slick-prev {
        left: 15%;
    }
    .slick-next{
        right:15%;
    }
`;

const ImagesZoom = ({ images, onClose }) => {
    const settings = {
        dots: true,
        infinite: true,
        speed: 1000,
        //autoplay: true,
        autoplayspeed: 1000,
        slidesToShow: 1,
        slidesToScroll: 1,
        arrows: false,
        pauseOnHover: true,

    };
    const [currentSlide, setCurrentSlide] = useState(0);

    return (
        <Overlay>
            <Global />
            <Header>
                <h1>상세 이미지 {images.length}</h1>
                <button onClick={onClose} >x</button>
            </Header>

            <SlickWrapper>
                <div>
                    <Slick
                        // settings={settings}
                        initialSlide={0}
                        afterChange={(slide) => setCurrentSlide(slide)}
                        infinite={true}
                        dots={true}
                        arrows={true}
                        slidesToShow={1}
                        slidesToScroll={1}
                    >
                        {images && images.map((v) => {
                            return (
                                <ImageWrapper key={v.src}>
                                    <img src={v.src} alt={v.src} />
                                </ImageWrapper>
                            )
                        })}
                    </Slick>

                    <Indicator>
                        <div>
                            {currentSlide + 1}
                            {' / '}
                            {images.length}
                        </div>
                    </Indicator>
                </div>
            </SlickWrapper>
        </Overlay>
    );
};


ImagesZoom.propTypes = {
    images: PropTypes.arrayOf(PropTypes.object).isRequired,
    onClose: PropTypes.func.isRequired,
}

export default ImagesZoom;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

23. 글로벌 스타일과 컴포넌트 폴더 구조

강의 :

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48810?tab=curriculum

 

 

components/ImagesZoom/styles.js

import styled, { createGlobalStyle } from 'styled-components';



export const Overlay = styled.div`
    position:fixed;
    z-index:5000;
    top:0;
    left:0;
    right:0;
    bottom:0;
`;

export const Header = styled.header`
    height:44px;
    background:white;
    position:relative;
    padding:0;
    text-align:center;

    & h1{
        margin:0;
        font-size:17px;
        color:#333;
        line-height:44px;
    }
    & button{
        position:absolute;
        right:0;
        top:0;
        padding:15px;
        line-height:10px;
        cursor:pointer;
        background: #faad14;
        color: #fff;
        border: 1px solid #fa8c16;
    }
`;

export const SlickWrapper = styled.div`
    height:calc(100% -44px);
    background:rgba(0, 0, 0, 0.9);
    border:none !important;
    height: 100%;
`;

export const ImageWrapper = styled.div`
    padding:32px;
    text-align:center;
    cursor:pointer;
    &img{
        margin:0 auto;
        max-height:750px;
        display: inline-block !important;
    }
`;


export const Indicator = styled.div`    
    text-align :cetner;

     & > div{
        width:75px;
        height:30px;
        line-height:30px;
        border-radius:15px;
        background:#313131;
        display:inline-block;
        text-align:center;
        top:60px;
        background:#fff;
        position: relative;
     }

`;

export const Global = createGlobalStyle`
    .slick-slide{
        display:inline-block;
    }
    .ant-card-cover{
        transform:none !important;
    }

    .slick-dots li button:before{
        color:#fff;
        opacity:1;
    }
    .slick-dots li.slick-active button:before {
         opacity: 1;
         color:#faad14;
    }

    .slick-prev, .slick-nex{
        width: 50px;
        height: 50px;
    }
    .slick-prev {
        left: 15%;
    }
    .slick-next{
        right:15%;
    }
`;

 

 

 

components/ImagesZoom/index.js

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Slick from 'react-slick';
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { Overlay, Global, Header, SlickWrapper, ImageWrapper, Indicator } from "./styles";




const ImagesZoom = ({ images, onClose }) => {
    const settings = {
        dots: true,
        infinite: true,
        speed: 1000,
        //autoplay: true,
        autoplayspeed: 1000,
        slidesToShow: 1,
        slidesToScroll: 1,
        arrows: false,
        pauseOnHover: true,

    };
    const [currentSlide, setCurrentSlide] = useState(0);

    return (
        <Overlay>
            <Global />
            <Header>
                <h1>상세 이미지 {images.length}</h1>
                <button onClick={onClose} >x</button>
            </Header>

            <SlickWrapper>
                <div>
                    <Slick
                        // settings={settings}
                        initialSlide={0}
                        //afterChange={(slide) => setCurrentSlide(slide)}
                        beforeChange={(slide) => setCurrentSlide(slide)}
                        infinite={true}
                        dots={true}
                        arrows={true}
                        slidesToShow={1}
                        slidesToScroll={1}
                    >
                        {images && images.map((v) => {
                            return (
                                <ImageWrapper key={v.src}>
                                    <img src={v.src} alt={v.src} />
                                </ImageWrapper>
                            )
                        })}
                    </Slick>

                    <Indicator>
                        <div>
                            {currentSlide + 1}
                            {' / '}
                            {images.length}
                        </div>
                    </Indicator>
                </div>
            </SlickWrapper>
        </Overlay>
    );
};


ImagesZoom.propTypes = {
    images: PropTypes.arrayOf(PropTypes.object).isRequired,
    onClose: PropTypes.func.isRequired,
}

export default ImagesZoom;

 

 

 

 

 

 

 

 

24. 게시글 해시태그 링크로 만들기

강의

:

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/50030?tab=curriculum

 

 

정규식 사이트  

https://regexr.com/

 

 

components/PostCard.js

 

import PostCardContent from './PostCardContent';


~


   <Card.Meta
                    avatar={<Avatar>{post.User.nickname[0]}</Avatar>}
                    title={post.User.nickname}
                    description={<PostCardContent postData={post.content} />}

                />

~

 

 

 

 

 

 

components/PostCardContent.js

import React from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link';

const PostCardContent = ({ postData }) => {
    //첫번째 게시글 #해시태그 #익스프레스
    return (
        <div>
            {postData.split(/(#[^\s#]+)/g).map((v, index) => {

                if (v.match(/(#[^\s#]+)/)) {
                    return <Link key={index} href={`/hashtag/${v.slice(1)}`}>{v}</Link>
                }
                return v;
            })}
        </div>
    );
};

PostCardContent.propTypes = {
    postData: PropTypes.string.isRequired
}


export default PostCardContent;

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

남자란 이기심의 목적이외의 것에는 마음을 쓰지 않는다. - S. 모옴

댓글 ( 4)

댓글 남기기

작성