반응형

1. 라이브러리 설치

 yarn add @sanity/block-content-to-react
 yarn add react-syntax-highlighter

 

2. BlogPostDetail.jsx

import {Col, Row } from 'antd'
import BlockContent from '@sanity/block-content-to-react'
import SyntaxHighlighter from 'react-syntax-highlighter'

const serializers = {
  types: {
    code: ({node}) => {
      const { code } = node;
      return (
        <SyntaxHighlighter 
          language="javascript" 
          style={{"hljs": {color: 'red',}}}
        >{code}</SyntaxHighlighter>
      )
    },
    video: ({ node }) => {
      return <p>video</p>
    },
    link: ({ node }) => {
      return <p>link</p>
    },
    imageGallery: ({ node }) => {
      return <p>imageGallery</p>
    },
  }
}

export default function BlogPostDetail({blocks}) {
  return (
    <>
      <Col span={24}>
        <BlockContent 
          blocks={blocks}
          projectId= {process.env.SANITY_PROJECT_ID}
          dataset="production"  
          serializers={serializers}
        />
      </Col>
    </>
  )
}
반응형
반응형

1. 라이브러리 설치

yarn add antd @ant-design/icons		// ant design 
yarn add dayjs				// 날짜 변환 라이브러리

2. pages/_app.js 파일에 반영 -> 전체 적용

import 'antd/dist/antd.css' //<- 추가
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

3. Roboto 적용을 위한 /pages/_document.js 파일 생성

참고문서 경로: https://nextjs.org/docs/advanced-features/custom-document

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head>
          {/* 구글 웹폰트 Roboto 링크 추가 */}
          <link rel="preconnect" href="https://fonts.gstatic.com" />
          <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

 

4. styles/globals.css 파일에서 적용 폰트 삭제 및 수정

html,
body {
  padding: 0;
  margin: 0;
  /* 아래부분 수정 */
  font-family: Roboto, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

5. styles/Home.module.css 파일에서 .container 클래스 수정

.container {
  width: 1024px;
  margin-left: auto;
  margin-right: auto;
}

6. pages/index.js

첫번째로  Header 컴포넌트를 가져오고, 추후 만들어지는 컴포넌트를 계속 추가해주면 됩니다.

import styles from '../styles/Home.module.css';
import SanityService from '../services/SanityService';
import { Col, Row } from 'antd';
import Link from 'next/link'
import { CodeOutlined } from '@ant-design/icons'
import Header from '../components/Header';

export default function Home({home, posts}) {  
  const mainPost = posts.find(p => p.slug === home.mainPostUrl);
  const otherPosts = posts.filter(p => p.slug !== home.mainPostUrl);
  console.log(mainPost);
  console.log(otherPosts);
  return (
    <div className={styles.container}>
      <Header />
    </div>
  )
}

export async function getStaticProps() {  
  const sanityService = new SanityService();
  const home = await sanityService.getHome();
  const posts = await sanityService.getPosts();

  return {
    props: {
      home,
      posts,
    }
  }
}

7. 컴포넌트 작성 (/components/Header.jsx)

/* eslint-disable react/no-unescaped-entities */
import { Row, Col } from 'antd';
import { CodeOutlined } from '@ant-design/icons'
import Link from 'next/link'


 const Header = () => {
  return (
      <Row
        align="middle"
        style={{
          height: 64,
        }}
      >
        <Col span={24}>
          <Link href="/">
            <a>
              <div
                style={{
                  fontSize: 20,
                  fontWeight: 'bold',
                }}>
                <CodeOutlined />Willy's Blog
              </div>
            </a>
          </Link>
        </Col>
      </Row>
  );
};

export default Header;

8. 결과

반응형
반응형

1. 프로젝트 생성 및 Library 설치

yarn create next-app .
yarn add @sanity/client	//Sanity 연결하여 Data를 가져오기 위한 라이브러리

2. 시작

yarn dev

yarn start는 빌드 이후 가능합니다.

 

3. 라우팅 설정

(기초)

폴더/페이지구조					url
pages/index.js 				-> 	/
pages/blog/index.js			-> 	/blog
pages/blog/first-post.js		->	/blog/first-post
pages/dashboard/setting/username.js	->	/dashboard/settings/username

pages/blog/[slug].js			->	/blog/:slug	(/blog/hello-world)
pages/[username]/settings.js		->	/:username/settings (/foo/settings)
pages/post/[...all].js			->	/post/* (/post/2020/id/title)

static routing이 우선됩니다.

 

(pages/post/index.js)

import React from 'react';

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

export default Blog;

(pages/post/[slug].js)

import React from 'react';
import { useRouter } from 'next/router';

const Blog = () => {
  const router = useRouter();

  const { slug } = router.query;

  return (
    <div>
      <h1>blog/{slug}</h1>
    </div>
  );
};

export default Blog;

 

3. Sanity 연결 및  Data 가져오기

getStaticProps (Static Generation) - Fetch data at build time

getStaticPaths (Static Generation) - Specify dynamic routes to pre-render pages based on data.

getServerSideProps (Server-side Rendering) - Fetch data on each request

 

(/services/SanityService.js) sanity 연결 및 데이터를 요청하는 메서드(클래스)를 정의합니다.

import sanityClient from '@sanity/client';

export default class SanityService {
  _client = sanityClient({
    dataset: 'production',
    projectId: 'ozi5ivc6',  // sanity.io에 접속하여 프로젝트의 ID를 확인한다.
    useCdn: process.env.NODE_ENV === 'production',  // production 모드일 경우 cdn 사용
  });

  // main post를 가져오는 요청
  async getHome() {    
    return await this._client.fetch(`
      *[_type =='home'][0]{'mainPostUrl': mainPost -> slug.current}
    `)
  }

  // main 이외의 포스트를 가져오는 요청
  async getPosts() {
    return await this._client.fetch(`
      *[_type=='post']{
        title, 
        subtitle, 
        createdAt, 
        'content': content[]{
          ..., 
          ...select(_type == 'imageGallery' => {'images': images[]{..., 'url': asset -> url }} )
        },
        'slug': slug.current,
        'thumbnail': {
          'alt': thumbnail.alt,
          'imageUrl': thumbnail.asset -> url
        },
        'author': author -> {
          name,
          role,
          'image': image.asset -> url
        },
        'tag': tag -> {
          title,
          'slug': slug.current
        }
      }
    `)
  }
}

 

(/pages/index.js) 루트 경로에서 메인 포스트와 기타 포스트들을 가져옵니다.

import styles from '../styles/Home.module.css';
import SanityService from '../services/SanityService';

export default function Home({home, posts}) {
  console.log(home);
  console.log(posts);
  return (
    <div className={styles.container}>
      <h1>Blog Home</h1>
    </div>
  )
}

export async function getStaticProps() {  
  const sanityService = new SanityService();
  const home = await sanityService.getHome();
  const posts = await sanityService.getPosts();

  return {
    props: {
      home,
      posts,
    }
  }
}

(/pages/post/[slug].js) 특정 포스트(slug)의 데이터를 가져옵니다.

import React from 'react';
import SanityService from '../../services/SanityService';

const PostAll = ({slug, post}) => {
  return (
    <div>
      <h1>blog/{slug}</h1>
    </div>
  );
};

export default PostAll;

export async function getStaticPaths() {
   
  const posts = await new SanityService().getPosts();

  const paths = posts.map(post => ({
    params: {
      slug: post.slug
    }
  }));

  return {
    paths,
    fallback: false,
  };
}

export async function getStaticProps({params}) {
  const {slug} = params;

  const posts = await new SanityService().getPosts();
  const post = posts.find(p => p.slug === slug);

  return {
    props: {
      slug,
      post,
    }
  }
}

 

4. 결과

/localhost:3000/post/my-blog-test

 

/localhost:3000/

 

반응형
반응형

 

무작정 따라한 소스를 기록으로 남김니다.

 

1. App.js

RecoilRoot 를 사용하여 TodoList 요소를 감싸준다.

import React from 'react';
import TodoList from './components/TodoList'
import { RecoilRoot } from 'recoil';



function App() {
  return (
    <div>
      <RecoilRoot>
        <TodoList />
      </RecoilRoot>
    </div>
  );
}

export default App;

 

2. ./src/recoil/state.js 작성

import { atom } from 'recoil';

export const todoListState = atom({
  key: 'todoListState',
  default: [],
});

3. ./src/components/TodoList.jsx 작성

import React from 'react';
import { useRecoilValue } from 'recoil';

import TodoItemCreator from './TodoItemCreator';
import TodoItem from './TodoItem';
import { todoListState } from '../recoil/state';

const TodoList = () => {
  const todoList = useRecoilValue(todoListState);
  return (
    <div>
      <TodoItemCreator />

      {todoList.map((todoItem) => (
        <TodoItem key={todoItem.id} item={todoItem} />
      ))}
    </div>
  );
};

export default TodoList;

4. ./src/components/TodoItemCreator.jsx 작성

import React, { useState } from 'react';
import { todoListState } from '../recoil/state';
import { useSetRecoilState } from 'recoil';

const TodoItemCreator = () => {
  const [inputValue, setInputValue] = useState('');
  const setTodoList = useSetRecoilState(todoListState);

  const addItem = () => {
    setTodoList((oldTodoList) => {
      const id = oldTodoList.length
      ? oldTodoList[oldTodoList.length - 1].id + 1
      : 0;
      return [
        ...oldTodoList,
        {
          id,
          text: inputValue,
          isComplete: false,
        },
      ];
    });
    setInputValue('');
  };

  const onChange = ({ target: { value } }) => {
    setInputValue(value);
  };

  return (
    <div>
      <input type='text' value={inputValue} onChange={onChange} />
      <button onClick={addItem}>Add</button>
    </div>
  );
};

export default TodoItemCreator;

5. ./src/components/TodoItem.jsx 작성

import React from 'react';
import { useRecoilState } from 'recoil';
import { todoListState } from '../recoil/state';

const TodoItem = ({item}) => {
  const [todoList, setTodoList] = useRecoilState(todoListState);

  const editItemText = ({ target: { value }}) => {
    const newList = todoList.map((listItem) =>
    listItem.id === item.id ? {...listItem, text: value } : listItem
    );
    setTodoList(newList);
  };

  const ToggleItemCompletion = () => {
    const newList = todoList.map((listItem) => 
    listItem.id ===item.id
    ? {...listItem, isComplete: !item.isComplete }
    : listItem
    );
    setTodoList(newList);
  };

  const deleteItem = () => {
    const newList = todoList.filter((listItem) => listItem.id !== item.id);
    setTodoList(newList);
  };
  return (
    <div>
      <input type="text" value={item.text} onChange={editItemText} />
      <input 
        type="checkbox" 
        checked={item.isComplete} 
        onChange={ToggleItemCompletion} 
      />
      <button onClick={deleteItem}>X</button>
    </div>
  );
};

export default TodoItem;

 

6. 결과

yart start

반응형
반응형

"리액트를 다루는 기술" 의 22장...mongodb 연동하기 과정에서 Koa 백엔드에 ESM을 적용하는 부분이 있는데, 현재 Node 14 버전에서 주의해야할 점과 함께 몇가지 정리해보고하 합니다. 

 

1. 라이브러리 설치

해당 과정에서는 ESM이 외에도 몇가지 라이브러리가 필요합니다. koa, koa-bodyparser, koa-router, mongoose, dotenv 등 말이죠. ESLint옵션 설정하는 부분은 제외하도록 하겠습니다. (VSCode에 설치한 ESLint 기능에, 추가 설정했을 경우 달라지는 부분을 잘 몰라서 이부분은 제외하고 사용했습니다.) 아래의 package.json을 보면 필요한 라이브러리들을 확인할 수 있을 것입니다. 

{
  "name": "05_react_backend",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "dotenv": "^8.2.0",
    "esm": "^3.2.25",
    "koa": "^2.13.1",
    "koa-bodyparser": "^4.3.0",
    "koa-router": "^10.0.0",
    "mongoose": "^5.12.3"
  },
  "devDependencies": {
    "nodemon": "^2.0.7"
  },
  "scripts": {
    "start": "node src",
    "start:dev": "nodemon --watch src/ src/index.js"
  },
  "type": "module"
}

2. 수정사항

ESM 문법은 2019년10월 Node V12부터 정식 지원되어, 간단한 설정으로 적용이 가능하다고 합니다. 바로, package.json파일에 "type": "module" 부분을 추가하는 것이죠. 교재와 달라진 부분이 index.js에서 ESM관련 설정을 해주고, main.js에 주요 구현을 만들어서 index.js에서 불러오게끔 했는데, 이젠 그럴필요 없이 package.js에서 저부분만 추가해주면 됩니다. 

 

3. 오류  

 이것만 하면 금방 적용될 줄 알았는데, 오류가 납니다. 무슨...ESM 오류라고하는데... 알고보니 start할때 책에 써있는

"-r esm" 부분이 필요가 없습니다. 지워줍니다. (위 package.json 참조)

 

 그런데 또 오류가 납니다. 여전히 ES Module에서 지원하지 않는 구문이라고 합니다. 알아보니, Node V14부터는 ESM을 적용할 때 파일 확장자까지 정확하게 써줘야 한다고 합니다. 하위 파일들도 마찬가지로요.

 

 

 

 

 

마지막으로 .env 파일과 관련되어 수정할 부분이 있습니다. 책에서는 

require('dotenv').config();

와 같이 사용하는데 이걸 

import dotenv from 'dotenv';
dotenv.config();

와 같이 수정해줘야 합니다.

 

 

그럼 모두 건승하세요~~

 

~~끝~~

반응형
반응형

 

1. https://newsapi.org/register에 가입합니다.

 

News API – Search News and Blog Articles on the Web

Get JSON search results for global news articles in real-time with our free News API.

newsapi.org

가입 완료시 나오는 API Key를 저장해둡시다. 나중에  API 요청할 때 써먹어야 합니다.

 

2. 한국 Link에 들어가봅니다.

https://newsapi.org/s/south-korea-news-api 

 

News API – Search News and Blog Articles on the Web

Get JSON search results for global news articles in real-time with our free News API.

newsapi.org

3. 한국 전체 뉴스를 가져오는 API 주소를 사용하겠습니다.(복사)

 

4. App.js 파일 작성

import React, {useState } from 'react';
import axios from 'axios';

const App = () => {
  const [data, setData] = useState(null);  
  const onClick = async () => {
    try {
      const response = await axios.get(
        'https://newsapi.org/v2/top-headlines?country=kr&apiKey=9b9cc0aa70394bc4950b08fb1faca2f7',
      );
      setData(response.data);
    } catch(e) {
      console.log(e);
    }    
  };
  return (
    <div>
      <div>
        <button onClick = {onClick}>불러오기</button>
      </div>
      {data && <textarea row={7} value={JSON.stringify(data, null, 2)} readOnly={true} />}
    </div>
    
  );
};

export default App;

<결과>

반응형
반응형

비주얼 테스트 도구인 스토리북에 대해 알아보겠습니다. 

 

1. 설치

npx -p @storybook/cli sb init

.storybook폴더가 자동 생성되고, 그 안에 story파일을 로딩해주는 main.js 와 preview.js 파일이 있습니다. main.js에는 ...story.js파일을 자동 추가/로딩해주는 부분과 addon 설정부분이 있습니다. (preview.js는 아직 잘 모르겠습니다.ㅠㅠ)

package.json에도 storybook 스크립트가 추가되었습니다. (모두 이전 버전에서 수동 생성해주던 부분임...)

 

2. Sample 컴포넌트 작성 <./src/components/Input.jsx 파일 생성>

기존 컴포넌트들이 있으면 활용해도 되지만, 저는 새로 만든 프로젝트에서 테스트할 용도로 간단한? 컴포넌트 하나를 만들겠습니다.

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

class Input extends PureComponent {
    constructor(props) {
        super(props);
        this.setRef = this.setRef.bind(this);
        this.handleChange = this.handleChange.bind(this);
    }
    handleChange(e) {
        const { name, onChange } = this.props;
        if(onChange) {
            onChange(name, e.target.value);
        }
    }
    componentDidMount(){
        if(this.props.autoFocus){
            this.ref.focus();
        }
    }
    componentDidUpdate(){
        if(this.props.autoFocus) {
            this.ref.focus();
        }
    }
    setRef(ref){
        this.ref = ref;
    }
    render() {
        const { errorMessage, label, name, value, type, onFocus } =this.props;
        return (
            <label>
                {label}
                <input
                  id={`input_${name}`}
                  ref={this.setRef}
                  onChange={this.handleChange}
                  onFocus={onFocus}
                  value={value}
                  type={type}
                />
                {errorMessage && <span className="error">{errorMessage}</span>}                
            </label>
        );
    }
}

Input.propTypes = {
    type: PropTypes.oneOf(['text','number','price']),
    name: PropTypes.string.isRequired,
    value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    errorMessage: PropTypes.string,
    label: PropTypes.string,
    onChange: PropTypes.func,
    onFocus: PropTypes.func,
    autoFocus: PropTypes.bool,
};
Input.defaultProps={
    onChange: () => {},
    onFocus: () => {},
    autoFocus: false,
    type: 'text',
};

export default Input;

 

3. 스토리 파일 생성(./src/stories/Input.stories.js)

위에서 만든 컴포넌트를 스토리북으로 넘겨줄 story파일을 만들겠습니다.

import React from 'react';
import { storiesOf } from '@storybook/react';
import Input from '../components/Input';

storiesOf('Input', module).add('기본설정', () => <Input />);

src/components/Input.jsx 와 src/stories/Input.stories.js 파일 구조. 선호하는 방식에 따라 변경 가능하다.

4. 실행 및 확인

yarn storybook

 

샘플 컴포넌트들이 하단에 나오고, 새로 생성한 Input 컴포넌트가 가장 위에 표시되는 모습

 


 

스토리북이 버전이 올라가면서 설정할 것도 별로 없어지고 편리해졌네요. story파일 자동으로 추가되도록 스크립트도 이미 생성이 되어있고, storybook 명령도 package.json파일에 자동 등록되고, 무엇보다도 개별 story를 등록해줄 필요도 없어진 것 같아 훨씬 편하게 사용할 수 있게 되었네요. 그냥 설치하고 xxx.stories.js 파일만 생성해주면 자동 등록되네요. 예전 책 "리액트 프로그래밍 정석" 으로 따라하다 잘 안되서 최신 사용법을 찾아본 소감이었습니다.

 

~끝~

반응형
반응형

MongoDB 설치파일 다운로드 위치를 몰라서 기록해놓습니다. 아울러 Compass에서 처음 접속하는 방법도 함께 공유합니다.

 

1. Download Site (www.mongodb.com/try/download/community)접속

 

2. 설치 (설치 중간에 함께 추천하는 Compass 도 설치합니다.)

3. 확인

정상 설치되었을 경우 커맨드 창에 mongo 라고 입력해보면 버전정보 등이 나타납니다.

4. Compass 실행

'mongodb://localhost:27017'을 입력하고 Connect를 누르면..

현재의 데이터베이스들이 나타납니다.

 

~끝~

반응형

'Programming > React' 카테고리의 다른 글

Newsapi api 키 발급받기  (0) 2021.03.23
Storybook 사용하기  (0) 2021.03.18
React - Apache에 배포하기  (0) 2021.02.27
React강좌3 - Redux0. redux 구조  (0) 2020.08.09
React강좌3 - Redux5. react-redux를 이용한 store연결  (0) 2020.08.09

+ Recent posts