소개
안녕하세요? 프로그래밍 기술을 향상시키는 데 도움이 되는 새로운 프로젝트로 돌아온 Vítor입니다. 마지막으로 튜토리얼을 게시한 지 꽤 시간이 지났습니다. 지난 몇 달 동안 저는 휴식을 취하고 다른 활동에 집중하는 시간을 가졌습니다. 이 기간 동안 저는 이 튜토리얼의 초점이 된 블로그라는 작은 웹 프로젝트를 개발했습니다.
이 가이드에서는 마크다운을 렌더링할 수 있는 블로그 페이지의 프런트엔드를 만들어 보겠습니다. 애플리케이션에는 공개 및 비공개 경로, 사용자 인증, 마크다운 텍스트 작성, 사진 추가, 기사 표시 등의 기능이 포함됩니다.
원하는 대로 애플리케이션을 자유롭게 맞춤설정하세요. 권장합니다.
여기에서 이 애플리케이션의 저장소에 액세스할 수 있습니다.
곤드락08
/
블로그 플랫폼
Next.js/typescript로 만든 블로그 플랫폼입니다.
Plataforma 파라 블로그
- 텍스트로 튜토리얼
성분
- next-auth - Next.js에 대한 인증 도서
- github.com/markdown-it/markdown-it - markdown biblioteca.
- github.com/sindresorhus/github-markdown-css- Para dar estilo ao nosso editor markdown.
- github.com/remarkjs/react-markdown - 반응 구성 요소인 마크다운 렌더링을 위한 도서관.
- github.com/remarkjs/remark-react/tree/4722bdf - Markdown em React를 변환하는 플러그인.
- codemirror.net - 웹용 편집기 구성 요소
- 반응 아이콘 - 반응에 따른 아이콘 라이브러리입니다.
코모 우사르
npm i npm run start
서버
EM 서버에 대한 você pode encontrar o servidor dessa aplicação em server
이 튜토리얼에는 이 가이드에서 사용될 Node.js 서버 작성도 포함되어 있습니다.
즐기시기 바랍니다.
즐거운 코딩하세요!
도서관
다음은 이 프로젝트에 사용된 라이브러리에 대한 요약입니다.
- next-auth - Next.js용 인증 라이브러리
- github.com/markdown-it/markdown-it - 마크다운 라이브러리.
- github.com/sindresorhus/github-markdown-css - Markdown 편집기 스타일을 지정합니다.
- github.com/remarkjs/react-markdown - React 구성 요소에서 Markdown을 렌더링하기 위한 라이브러리입니다.
- github.com/remarkjs/remark-react/tree/4722bdf - Markdown을 React로 변환하는 플러그인.
- codemirror.net - 웹 구성 요소 편집기.
- React-icons - React용 아이콘 라이브러리
반응 프로젝트 만들기
이 튜토리얼을 작성할 당시 버전 13.4인 Next.js 프레임워크의 최신 버전을 사용하겠습니다.
다음 명령을 실행하여 프로젝트를 생성하세요.
npm i npm run start
설치하는 동안 템플릿 설정을 선택하세요. 이 튜토리얼에서는 TypeScript를 프로그래밍 언어로 사용하고 Tailwind CSS 프레임워크를 사용하여 애플리케이션 스타일을 지정하겠습니다.
구성
이제 사용할 라이브러리를 모두 설치해 보겠습니다.
가격 인하
npx create-next-app myblog
반응 비고
npm i markdown-it @types/markdown-it markdown-it-style github-markdown-css react-markdown
코드미러
remark remark-gfm remark-react
아이콘
npm @codemirror/commands @codemirror/highlight @codemirror/lang-javascript @codemirror/lang-markdown @codemirror/language @codemirror/language-data @codemirror/state @codemirror/theme-one-dark @codemirror/view
그런 다음 사용하지 않을 모든 것을 제거하여 설치의 초기 구조를 정리하세요.
건축학
이것이 우리 애플리케이션의 최종 구조입니다.
npm i react-icons @types/react-icons
첫 번째 단계
next.config 구성
프로젝트 루트의 next.config.js 파일에서 기사의 이미지에 액세스할 도메인 주소를 구성해 보겠습니다. 이 튜토리얼에서는 또는 로컬 서버를 사용하는 경우 localhost를 사용합니다.
애플리케이션에 이미지가 올바르게 로드되도록 하려면 이 구성을 포함해야 합니다.
src- |- app/ | |-(pages)/ | | |- (private)/ | | | |- (home) | | | |- editArticle/[id] | | | | | | | |- newArticle | | | - (public)/ | | | - article/[id] | | | - login | | | api/ | |- auth/[...nextAuth]/route.ts | |- global.css | |- layout.tsx | | - components/ | - context/ | - interfaces/ | - lib/ | - services/ middleware.ts
미들웨어 구성
애플리케이션 src/의 루트 폴더에 middleware.ts를 생성하여 비공개 경로에 대한 액세스를 확인하세요.
const nextConfig = { images: { domains: ["localhost"], }, };
미들웨어와 미들웨어로 수행할 수 있는 모든 작업에 대해 자세히 알아보려면 설명서를 확인하세요.
인증 경로 구성
/app 폴더 내 api/auth/[...nextauth]에 Route.ts라는 파일을 생성합니다. 여기에는 CredentialsProvider를 사용하여 인증 API에 연결하는 경로 구성이 포함됩니다.
CredentialsProvider를 사용하면 사용자 이름과 비밀번호, 도메인, 2단계 인증, 하드웨어 장치 등과 같은 임의의 자격 증명으로 로그인을 처리할 수 있습니다.
먼저 프로젝트 루트에 .env.local 파일을 생성하고 비밀으로 사용될 토큰을 추가합니다.
npm i npm run start
다음으로 NEXTAUTH_SECRET이 src/app/auth/[...nextauth]/routes.ts 파일의 비밀에 추가되는 인증 시스템을 작성해 보겠습니다.
npx create-next-app myblog
인증 제공자
비공개 경로의 페이지 전체에서 사용자 데이터를 공유할 인증 공급자인 컨텍스트를 만들어 보겠습니다. 나중에 이를 레이아웃.tsx 파일 중 하나를 래핑하는 데 사용할 것입니다.
src/context/auth-provider.tsx에 다음 내용이 포함된 파일을 만듭니다.
npm i markdown-it @types/markdown-it markdown-it-style github-markdown-css react-markdown
글로벌 스타일
전체적으로 우리 애플리케이션에서는 Tailwind CSS를 사용하여 스타일을 만듭니다. 그러나 일부 장소에서는 페이지와 구성 요소 간에 사용자 정의 CSS 클래스를 공유합니다.
remark remark-gfm remark-react
레이아웃
이제 비공개 및 공개 레이아웃을 작성해 보겠습니다.
앱/layout.tsx
npm @codemirror/commands @codemirror/highlight @codemirror/lang-javascript @codemirror/lang-markdown @codemirror/language @codemirror/language-data @codemirror/state @codemirror/theme-one-dark @codemirror/view
페이지/layout.tsx
npm i react-icons @types/react-icons
API 호출
우리 애플리케이션은 API를 여러 번 호출하며, 외부 API를 사용하도록 이 애플리케이션을 조정할 수 있습니다. 이 예에서는 로컬 애플리케이션을 사용하고 있습니다. 백엔드 튜토리얼과 서버 생성을 아직 못 보신 분들은 꼭 확인해보세요.
src/services/에 다음 함수를 작성해 보겠습니다.
- authService.ts: 서버에서 사용자를 인증하는 기능입니다.
src- |- app/ | |-(pages)/ | | |- (private)/ | | | |- (home) | | | |- editArticle/[id] | | | | | | | |- newArticle | | | - (public)/ | | | - article/[id] | | | - login | | | api/ | |- auth/[...nextAuth]/route.ts | |- global.css | |- layout.tsx | | - components/ | - context/ | - interfaces/ | - lib/ | - services/ middleware.ts
2.refreshAccessToken.tsx:
const nextConfig = { images: { domains: ["localhost"], }, };
- getArticles.tsx: 데이터베이스에 저장된 모든 기사를 가져오는 역할을 담당하는 함수:
export { default } from "next-auth/middleware"; export const config = { matcher: ["/", "/newArticle/", "/article/", "/article/:path*"], };
- postArticle.tsx: 기사 데이터를 서버에 제출하는 기능을 담당합니다.
.env.local NEXTAUTH_SECRET = SubsTituaPorToken
- editArticle.tsx: 데이터베이스 내의 특정 기사를 수정하는 기능입니다.
import NextAuth from "next-auth/next"; import type { AuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { authenticate } from "@/services/authService"; import refreshAccessToken from "@/services/refreshAccessToken"; export const authOptions: AuthOptions = { providers: [ CredentialsProvider({ name: "credentials", credentials: { email: { name: "email", label: "email", type: "email", placeholder: "Email", }, password: { name: "password", label: "password", type: "password", placeholder: "Password", }, }, async authorize(credentials, req) { if (typeof credentials !== "undefined") { const res = await authenticate({ email: credentials.email, password: credentials.password, }); if (typeof res !== "undefined") { return { ...res }; } else { return null; } } else { return null; } }, }), ], session: { strategy: "jwt" }, secret: process.env.NEXTAUTH_SECRET, callbacks: { async jwt({ token, user, account }: any) { if (user && account) { return { token: user?.token, accessTokenExpires: Date.now() + parseInt(user?.expiresIn, 10), refreshToken: user?.tokenRefresh, }; } if (Date.now() <ol> <li> deleteArticle.tsx: 데이터베이스에서 특정 기사를 제거하는 기능입니다. </li> </ol> <pre class="brush:php;toolbar:false">'use client'; import React from 'react'; import { SessionProvider } from "next-auth/react"; export default function Provider({ children, session }: { children: React.ReactNode, session: any }): React.ReactNode { return ( <sessionprovider session="{session}"> {children} </sessionprovider> ) };
구성요소
다음으로 애플리케이션 전반에 걸쳐 사용되는 각 구성 요소를 작성해 보겠습니다.
구성 요소/Navbar.tsx
두 개의 탐색 링크가 있는 간단한 구성 요소입니다.
/*global.css*/ .container { max-width: 1100px; width: 100%; margin: 0px auto; } .image-container { position: relative; width: 100%; height: 5em; padding-top: 56.25%; /* Aspect ratio 16:9 (dividindo a altura pela largura) */ } .image-container img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; } @keyframes spinner { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .loading-spinner { width: 50px; height: 50px; border: 10px solid #f3f3f3; border-top: 10px solid #293d71; border-radius: 50%; animation: spinner 1.5s linear infinite; }
구성 요소/Loading.tsx
API 호출이 완료되기를 기다리는 동안 사용되는 간단한 로딩 구성 요소입니다.
import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import Provider from "@/context/auth-provider"; import { getServerSession } from "next-auth"; import { authOptions } from "./api/auth/[...nextauth]/route"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Markdown Text Editor", description: "Created by ", }; export default async function RootLayout({ children, }: { children: React.ReactNode; }) { const session = await getServerSession(authOptions); return ( <provider session="{session}"> {children} </provider> ); }
구성 요소/페이지 매김.tsx
비공개 경로에서 모든 기사를 표시하는 페이지에 사용되는 페이지 매기기 구성 요소입니다. 이 구성 요소를 작성하는 방법에 대한 자세한 내용은 여기에서 확인할 수 있습니다
npm i npm run start
구성 요소/기사Card.tsx
작성된 글을 표시하는 카드 컴포넌트입니다.
이 구성 요소에는 기사 표시 페이지와 이전에 작성된 기사를 편집할 수 있는 페이지로 연결되는 링크도 포함되어 있습니다.
npx create-next-app myblog
구성 요소/ArticleList.tsx
API 호출 및 응답 표시를 담당하는 구성 요소입니다.
여기서는 우리가 작성한 함수를 통해 두 개의 API 호출을 사용합니다.
- getArticles.ts - 구성 요소에 표시될 모든 기사를 반환합니다.
- RemoveArticle - 목록과 서버에서 특정 기사를 제거합니다.
이전에 작성한 Pagination.tsx 구성 요소를 사용하여 페이지 전체에 기사 수를 분할하겠습니다.
npm i markdown-it @types/markdown-it markdown-it-style github-markdown-css react-markdown
페이지
다음으로 각 페이지를 해당 경로별로 나누어 살펴보겠습니다.
공개 페이지
로그인
저희 애플리케이션 홈페이지입니다. 간단한 페이지이므로 원하는 대로 수정할 수 있습니다. 본 페이지에서는 next-auth 네비게이션 라이브러리에서 제공하는 로그인 기능을 활용하겠습니다.
src/app/pages/public/login/page.tsx 파일에 있습니다.
remark remark-gfm remark-react
기사 페이지
기사 읽기 페이지를 만들기 위해 동적인 페이지를 개발하겠습니다.
방문한 모든 블로그 플랫폼에는 URL을 통해 액세스할 수 있는 기사 읽기 전용 페이지가 있을 것입니다. 그 이유는 동적 페이지 경로 때문입니다. 다행히 Next.js는 새로운 AppRouter 메소드를 통해 이를 쉽게 수행하여 우리의 삶을 훨씬 더 단순하게 만들어줍니다.
먼저 [id] 폴더를 추가하여 구조에 경로를 생성해야 합니다. 결과적으로 페이지/(공개)/articles/[id]/pages.tsx.
구조가 됩니다.- ID는 우리 네비게이션 경로의 슬러그에 해당합니다.
- params는 탐색 슬러그를 포함하는 애플리케이션의 트리를 통해 전달되는 속성입니다.
npm @codemirror/commands @codemirror/highlight @codemirror/lang-javascript @codemirror/lang-markdown @codemirror/language @codemirror/language-data @codemirror/state @codemirror/theme-one-dark @codemirror/view
두 번째: MarkdownIt 라이브러리를 사용하여 페이지에서 Markdown 형식으로 텍스트를 표시할 수 있습니다.
npm i react-icons @types/react-icons
그리고 마지막으로
페이지가 준비되면 예를 들어 브라우저에서 localhost:3000/articles/1에 액세스하면 제공된 ID로 기사를 볼 수 있습니다.
우리의 경우 ArticleCards.tsx 구성 요소 중 하나를 클릭하면 탐색을 통해 ID가 전달되며, 이는 비공개 경로의 기본 페이지에 렌더링됩니다.
src- |- app/ | |-(pages)/ | | |- (private)/ | | | |- (home) | | | |- editArticle/[id] | | | | | | | |- newArticle | | | - (public)/ | | | - article/[id] | | | - login | | | api/ | |- auth/[...nextAuth]/route.ts | |- global.css | |- layout.tsx | | - components/ | - context/ | - interfaces/ | - lib/ | - services/ middleware.ts
비공개 페이지
다음은 애플리케이션에서 사용자가 인증된 후에만 액세스할 수 있는 비공개 페이지입니다.
집
app/pages/ 폴더 내에서 () 안에 파일이 선언되어 있다는 것은 해당 경로가 /에 해당한다는 의미입니다.
저희 경우 (홈) 폴더는 저희 프라이빗 루트의 홈페이지를 의미합니다. 사용자가 시스템에 인증할 때 보게 되는 첫 번째 페이지입니다. 이 페이지에는 당사 데이터베이스의 기사 목록이 표시됩니다.
데이터는 ArticlesList.tsx 구성 요소에 의해 처리됩니다. 아직 이 코드를 작성하지 않았다면 구성 요소 섹션을 다시 참조하세요.
앱 내/(페이지)/(비공개)/(홈)/page.tsx.
npm i npm run start
새 기사
이 페이지는 기사를 등록할 수 있는 신청서의 가장 중요한 페이지 중 하나입니다.
이 페이지를 통해 사용자는 다음을 수행할 수 있습니다.
- 마크다운 형식으로 기사를 작성하세요.
- 기사에 이미지를 할당하세요.
- Markdown 텍스트를 서버에 제출하기 전에 미리 봅니다.
이 페이지는 여러 후크를 사용합니다.
- useCallback - 기능을 메모하는 데 사용됩니다.
- useState - 구성 요소에 상태 변수를 추가할 수 있습니다.
- useSession - 사용자가 인증되었는지 확인하고 인증 토큰을 얻을 수 있습니다.
이를 위해 두 가지 구성 요소를 사용합니다.
- TextEditor.tsx: 이전에 작성한 텍스트 편집기입니다.
- Preview.tsx: 마크다운 형식으로 파일을 표시하기 위한 구성 요소입니다.
이 페이지를 구성하는 동안 다음 API를 사용합니다.
- POST: postArticle 기능을 사용하여 기사를 서버로 보냅니다.
또한 next-auth 라이브러리에서 제공하는 useSession 후크를 사용하여 서버에 기사를 등록하는 데 사용될 사용자 인증 토큰을 얻습니다.
여기에는 세 가지 개별 API 호출이 포함됩니다.
앱/페이지/(비공개)/newArticle/page.tsx.
"클라이언트 사용"; import React, { ChangeEvent, useCallback, useState } from "react"; "next-auth/react"에서 import { useSession }; "다음/탐색"에서 가져오기 { 리디렉션 }; "@/services/postArticle"에서 postArtical을 가져옵니다. "react-icons/ai"에서 { AiOutlineFolderOpen }을 가져옵니다. "react-icons/ri"에서 { RiImageEditLine }을 가져옵니다. "다음/이미지"에서 이미지를 가져옵니다. "@/comComponents/textEditor"에서 TextEditor를 가져옵니다. "@/comComponents/PreviewText"에서 미리보기를 가져옵니다. "react-icons/ai"에서 { AiOutlineSend }를 가져옵니다. "react-icons/bs"에서 { BsBodyText }를 가져옵니다. 기본 함수 내보내기 NewArticle(params:any) { const { 데이터: 세션 }: any = useSession({ 필수: 사실, 인증되지 않은() { 리다이렉트("/로그인"); }, }); const [imageUrl, setImageUrl] = useState<object>({}); const [previewImage, setPreviewImage] = useState<string>(""); const [previewText, setPreviewText] = useState(false); const [title, setTitle] = useState<string>(""); const [doc, setDoc] = useState<string>("# Escreva o seu texto...n"); const handlerDocChange = useCallback((newDoc: any) => { setDoc(newDoc); }, []); if (!session?.user)는 null을 반환합니다. const handlerArticleSubmit = async (e:any) => { e.preventDefault(); const 토큰: 문자열 = session.user.token; 노력하다 { const res = postArtical({ ID: session.user.userId.toString(), 토큰: 토큰, imageUrl: imageUrl, 제목: "제목," 문서: 문서, }); console.log('re--->', res); 리디렉션('/성공'); } 잡기(오류) { console.error('기사 제출 오류:', error); // 필요한 경우 오류를 처리합니다. 오류 발생; } }; const handlerImageChange = (e: React.ChangeEvent<htmlinputelement>) => { if (e.target.files && e.target.files.length > 0) { const 파일 = e.target.files[0]; const url = URL.createObjectURL(파일); setPreviewImage(url); setImageUrl(파일); } }; const handlerTextPreview = (e: 임의) => { e.preventDefault(); setPreviewText(!previewText); }; 반품 ( <section classname="w-full h-full min-h-screen 상대 py-8"> {미리보기텍스트 &&( <div classname="absolute right-16 top-5 p-5 border-2 border-slate-500 bg-slate-100 rounded-xl w-full max-w-[33em] z-30"> setPreviewText(!previewText)} /> </div> )} <form classname="relative mx-auto max-w-[700px] h-full min-h-[90%] w-full p-2 border-2 border-slate-200 rounded-md bg-slate-50 drop-shadow-xl 플렉스 flex-col gap-2 "> {" "} <div classname="flex justify-between items-center"> <bsbodytext></bsbodytext> 시사 버튼>{" "} 엔비아르 텍스토 <aioutlinesend classname="w-5 h-5 group-hover:text-red-500"></aioutlinesend> 버튼> </div> <div classname="header-wrapper flex flex-col gap-2 "> <div classname="image-box"> {previewImage.length === 0 && ( <div classname="select-image"> <aioutlinefolderopen classname="w-7 h-7"></aioutlinefolderopen> 드래그 앤 드롭 이미지 라벨> <h4> 기사 편집 </h4> <p><em>New Article</em>(newArticle)과 유사하지만 약간의 차이점이 있는 페이지입니다.</p> <p>먼저 ID를 탐색 매개변수로 받는 동적 경로를 정의합니다. 이는 기사 읽기 페이지에서 수행된 작업과 매우 유사합니다. <br> app/(페이지)/(private)/editArticle/[id]/page.tsx<br> </p> <pre class="brush:php;toolbar:false">"클라이언트 사용"; "반응"에서 React, { useState, useEffect, useCallback, useRef, ChangeEvent }를 가져옵니다. "next-auth/react"에서 import { useSession }; "다음/탐색"에서 가져오기 { 리디렉션 }; '다음/이미지'에서 이미지를 가져옵니다. "@/interfaces/article.interface"에서 { IArticle }을 가져옵니다. "react-icons/ai"에서 { AiOutlineEdit }를 가져옵니다. "react-icons/bs"에서 { BsBodyText }를 가져옵니다. "react-icons/ai"에서 { AiOutlineFolderOpen }을 가져옵니다. "react-icons/ri"에서 { RiImageEditLine }을 가져옵니다. "@/comComponents/PreviewText"에서 미리보기를 가져옵니다. "@/comComponents/textEditor"에서 TextEditor를 가져옵니다. '@/comComponents/Loading'에서 로드 가져오기; "@/services/editArticle"에서 editArtical을 가져옵니다. 기본 함수 내보내기 EditArticle({ params }: { params: any }) { const { 데이터: 세션 }: any = useSession({ 필수: 사실, 인증되지 않은() { 리다이렉트("/로그인"); }, }); const id: 숫자 = params.id; const [기사, setArticle] = useState<iarticle>(널); const [imageUrl, setImageUrl] = useState<object>({}); const [previewImage, setPreviewImage] = useState<string>(""); const [previewText, setPreviewText] = useState(false) const [title, setTitle] = useState<string>(""); const [doc, setDoc] = useState<string>(''); const handlerDocChange = useCallback((newDoc: any) => { setDoc(newDoc); }, []); const inputRef= useRef<htmlinputelement>(null); const fetchArticle = async (id: 숫자) => { 노력하다 { const 응답 = 가져오기를 기다립니다( `http://localhost:8080/articles/getById/${id}`, ); const jsonData = 응답을 기다립니다.json(); setArticle(jsonData); } 잡기 (오류) { console.log("뭔가 잘못되었습니다:", err); } }; useEffect(() => { if (기사 !== null || 기사 !== 정의되지 않음) { fetchArticle(id); } }, [ID]); useEffect(()=>{ if(기사 != null && 기사.콘텐츠){ setDoc(article.content) } if(기사 !=null && 기사.이미지){ setPreviewImage(`http://localhost:8080/` 기사.이미지) } },[기사]) const handlerArticleSubmit = async (e:any) => { e.preventDefault(); const 토큰: 문자열 = session.user.token; 노력하다{ const res = editArtical({ 아이디: 아이디, 토큰: 토큰, imageUrl:imageUrl, 제목: 제목, 문서: 문서, }); console.log('re--->',res) 해상도를 반환; } 잡기(오류){ console.log("오류:", 오류) } }; const handlerImageClick = ()=>{ console.log('hiii') if(inputRef.current){ inputRef.current.click(); } }const handlerImageChange = (e: React.ChangeEvent<htmlinputelement>) => { if (e.target.files && e.target.files.length > 0) { const 파일 = e.target.files[0]; const url = URL.createObjectURL(파일); setPreviewImage(url); setImageUrl(파일); } }; const handlerTextPreview = (e: 임의) => { e.preventDefault(); setPreviewText(!previewText); console.log('미리보기에서 안녕하세요!') }; if(!article) return <loading></loading> if(기사?.내용) 반품 ( <section classname="w-full h-full min-h-screen 상대 py-8"> {미리보기텍스트 &&( <div classname="absolute right-16 top-5 p-5 border-2 border-slate-500 bg-slate-100 rounded-xl w-full max-w-[33em] z-30"> setPreviewText(!previewText)} /> </div> )} <div classname="relative mx-auto max-w-[700px] h-full min-h-[90%] w-full p-2 border-2 border-slate-200 rounded-md bg-white drop- 섀도우-MD 플렉스 플렉스-콜 갭-2"> <form classname="relative mx-auto max-w-[700px] h-full min-h-[90%] w-full p-2 border-2 border-slate-200 rounded-md bg-slate-50 drop-shadow-md flex flex-col gap-2 "> {" "} <div classname="flex justify-between items-center"> <bsbodytext></bsbodytext> 시사 버튼>{" "} 아티고 편집 <aioutlineedit classname="w-5 h-5 group-hover:text-red-500"></aioutlineedit> 버튼> </div> <div classname="header-wrapper flex flex-col gap-2 "> <div classname="image-box"> {previewImage.length === 0 && ( <div classname="select-image"> <aioutlinefolderopen classname="w-7 h-7"></aioutlinefolderopen> 드래그 앤 드롭 이미지 라벨> <h2> 결론 </h2> <p>먼저 시간을 내어 이 튜토리얼을 읽어주셔서 감사드리며, 튜토리얼을 완료하신 것도 축하드립니다. 도움이 되셨기를 바라며, 단계별 지침을 따라하기 쉬웠기를 바랍니다.</p> <p>두 번째로, 방금 구축한 기능에 대해 몇 가지 사항을 강조하고 싶습니다. 이는 블로그 시스템의 기초이며, 모든 글을 표시하는 공개 페이지, 사용자 등록 페이지, 사용자 정의 404 오류 페이지 등 추가할 내용이 여전히 많습니다. 튜토리얼을 진행하는 동안 이러한 페이지에 대해 궁금해서 놓친 경우 이는 의도된 것임을 알아두세요. 이 튜토리얼에서는 이러한 새 페이지를 직접 만들고, 다른 페이지를 추가하고, 새로운 기능을 구현하는 데 충분한 경험을 제공했습니다.</p> <p>정말 감사합니다. 다음 시간까지. ㅇ/</p> </div> </div> </div> </form> </div></section></htmlinputelement></htmlinputelement></string></string></string></object></iarticle>
위 내용은 Next.js로 동적 블로그 대시보드 구축의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

JavaScript 코어 데이터 유형은 브라우저 및 Node.js에서 일관되지만 추가 유형과 다르게 처리됩니다. 1) 글로벌 객체는 브라우저의 창이고 node.js의 글로벌입니다. 2) 이진 데이터를 처리하는 데 사용되는 Node.js의 고유 버퍼 객체. 3) 성능 및 시간 처리에는 차이가 있으며 환경에 따라 코드를 조정해야합니다.

javaScriptUSTWOTYPESOFSOFCOMMENTS : 단일 라인 (//) 및 multi-line (//)

Python과 JavaScript의 주요 차이점은 유형 시스템 및 응용 프로그램 시나리오입니다. 1. Python은 과학 컴퓨팅 및 데이터 분석에 적합한 동적 유형을 사용합니다. 2. JavaScript는 약한 유형을 채택하며 프론트 엔드 및 풀 스택 개발에 널리 사용됩니다. 두 사람은 비동기 프로그래밍 및 성능 최적화에서 고유 한 장점을 가지고 있으며 선택할 때 프로젝트 요구 사항에 따라 결정해야합니다.

Python 또는 JavaScript를 선택할지 여부는 프로젝트 유형에 따라 다릅니다. 1) 데이터 과학 및 자동화 작업을 위해 Python을 선택하십시오. 2) 프론트 엔드 및 풀 스택 개발을 위해 JavaScript를 선택하십시오. Python은 데이터 처리 및 자동화 분야에서 강력한 라이브러리에 선호되는 반면 JavaScript는 웹 상호 작용 및 전체 스택 개발의 장점에 없어서는 안될 필수입니다.

파이썬과 자바 스크립트는 각각 고유 한 장점이 있으며 선택은 프로젝트 요구와 개인 선호도에 따라 다릅니다. 1. Python은 간결한 구문으로 데이터 과학 및 백엔드 개발에 적합하지만 실행 속도가 느립니다. 2. JavaScript는 프론트 엔드 개발의 모든 곳에 있으며 강력한 비동기 프로그래밍 기능을 가지고 있습니다. node.js는 풀 스택 개발에 적합하지만 구문은 복잡하고 오류가 발생할 수 있습니다.

javaScriptisNotBuiltoncorc; it'SangretedLanguageThatrunsonOngineStenWrittenInc .1) javaScriptWasDesignEdasAlightweight, 해석 hanguageforwebbrowsers.2) Endinesevolvedfromsimpleplemporectreterstoccilpilers, 전기적으로 개선된다.

JavaScript는 프론트 엔드 및 백엔드 개발에 사용할 수 있습니다. 프론트 엔드는 DOM 작업을 통해 사용자 경험을 향상시키고 백엔드는 Node.js를 통해 서버 작업을 처리합니다. 1. 프론트 엔드 예 : 웹 페이지 텍스트의 내용을 변경하십시오. 2. 백엔드 예제 : node.js 서버를 만듭니다.

Python 또는 JavaScript는 경력 개발, 학습 곡선 및 생태계를 기반으로해야합니다. 1) 경력 개발 : Python은 데이터 과학 및 백엔드 개발에 적합한 반면 JavaScript는 프론트 엔드 및 풀 스택 개발에 적합합니다. 2) 학습 곡선 : Python 구문은 간결하며 초보자에게 적합합니다. JavaScript Syntax는 유연합니다. 3) 생태계 : Python에는 풍부한 과학 컴퓨팅 라이브러리가 있으며 JavaScript는 강력한 프론트 엔드 프레임 워크를 가지고 있습니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

맨티스BT
Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

드림위버 CS6
시각적 웹 개발 도구

ZendStudio 13.5.1 맥
강력한 PHP 통합 개발 환경

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.