基于Next.js搭建基本可用于项目开发的过程

简单介绍

要从头开始使用 React 构建一个完整的 Web 应用程序,需要考虑许多重要的细节:

  • 必须使用打包程序(例如 webpack)打包代码,并使用 Babel 等编译器进行代码转换。
  • 你需要针对生产环境进行优化,例如代码拆分。
  • 你可能需要对一些页面进行预先渲染以提高页面性能和 SEO。你可能还希望使用服务器端渲染或客户端渲染。
  • 你可能必须编写一些服务器端代码才能将 React 应用程序连接到数据存储。
    一个 框架 就可以解决上述这些问题。但是,这样的框架必须具有正确的抽象级别,否则它将不是很有用。它还需要具有出色的“开发人员体验”,以确保您和您的团队在编写代码时拥有出色的体验。

Next.js 具有同类框架中最佳的“开发人员体验”和许多内置功能。列举其中一些如下:

  • 直观的、 基于页面 的路由系统(并支持 动态路由)
  • 预渲染。支持在页面级的 静态生成 (SSG) 和 服务器端渲染 (SSR)
  • 自动代码拆分,提升页面加载速度
  • 具有经过优化的预取功能的 客户端路由
  • 内置 CSS 和 Sass 的支持,并支持任何 CSS-in-JS
  • 开发环境支持 快速刷新
  • 利用 Serverless Functions 及 API 路由 构建 API 功能
  • 完全可扩展

上面引用与官网介绍

初始化项目

运行初始化项目命令:

yarn create next-app

添加 typescript、sass 支持

touch tsconfig.json

把 .js文件 改为 .tsx 或者 .ts

yarn add sass

项目初始目录如图:
目录图

  • paegs 所有页面都在这里编写,文件路由必须写在这个目录下面
  • public 存放静态资源
  • styles 样式

运行项目:

npm run dev

效果图
接下来我们开始改造项目,以便适应实际项目的应用开发。

思考

一般来说,我们从头开始搭建一个项目,需要考虑三个方面:

  • 布局
    布局不仅思考页面的整体布局如何,还得使用怎样的组件库,还是自己开发组件库等一下,这些东西要一开始就考虑,即使是自己写组件,也比后面才回头优化布局要好得多

  • 获取数据以及数据流
    获取数据主要分两点:同步异步,同步获取数据很好解决,直接使用函数调用即可,异步怎么搞?尤其是在使用redux处理数据流的时候,这时候需要加入类似 redux-saga 的中间件专门处理异步数据

  • 路由
    路由怎么配置以及需要怎样的权限管理

布局

Document、Layout
Document

Document 组件是有Next.js提供的,是对页面的结构一个抽象,方便与我们调整位置以及编写一些公共属性,Document的子组件如下:

  • Html,比如写 lang="zh"
  • Main
  • Head,编写一些 meta 标签的属性,比如:<meta name="theme-color"/> 主题颜色
  • NextScript
Layout

Layout 同样是由 Next.js提供,React 模型允许我们将一个页面解构为一系列组件。许多这些组件经常在页面之间重用。例如,可能在每个页面上都有相同的导航栏和页脚。

实现过程

创建 _document.tsx,在根目录创建 layout 目录,在 layout 目录下创建 index.tsx、sidebar.tsx,代码分别如下:

// _document.tsx
import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html lang="zh">
        <Head>
          <meta name="theme-color"/>
        </Head>
        <body>
        <Main/>
        <NextScript/>
        </body>
      </Html>
    );
  }
}

MyDocument.getInitialProps = async (ctx: DocumentContext) => {
  const originalRenderPage = ctx.renderPage;

  ctx.renderPage = () =>
    originalRenderPage({
      // useful for wrapping the whole react tree
      enhanceApp: (App) => App,
      // useful for wrapping in a per-page basis
      enhanceComponent: (Component) => Component,
    });

  // Run the parent `getInitialProps`, it now includes the custom `renderPage`
  return await Document.getInitialProps(ctx);
};

export default MyDocument;
// index.tsx
import Head from 'next/head'
import styles from './layout.module.scss'

export default function Layout({ children }) {
  return (
    <>
      <Head>
        <title>My App</title>
        <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/>
      </Head>
      <main className={styles.main}>{children}</main>
    </>
  )
}
import Link from 'next/link'
import styles from './sidebar.module.scss'

export default function Sidebar() {
  return (
    <nav className={styles.nav}>
      <Link href="/">
        <a>Home</a>
      </Link>
      <Link href="/about">
        <a>About</a>
      </Link>
      <Link href="/contact">
        <a>Contact</a>
      </Link>
    </nav>
  )
}

最终实现效果:

获取数据

  • getStaticProps(静态生成):在构建时获取数据。
  • getStaticPaths(静态生成):指定动态路由以根据数据预渲染页面。
  • getServerSideProps(服务器端渲染):获取每个请求的数据。

想要了解这三种方式,首先我们得对客户端渲染、服务端渲染有所了解
它支持三种渲染方式包括

  • 客户端渲染 BSR (Broswer Side Render)

  • 静态页面生成 SSG (Static Site Generation)

  • 服务端渲染 SSR (Server Side Render)
    旧瓶装新酒
    上面说的几种渲染方式,其实并非什么新东西,其实可以和这些技术对应起来

  • BSR – 用 JS、Vue、React 创建 HTML

  • SSG – 页面静态化,把 PHP 提前渲染成 HTML

  • SSR – PHP、Python、Ruby、Java 后台的基本功能

详情请点击

实现

在 pages 目录创建 about.tsx、contact.tsx、index.tsx,分别调用 getStaticProps 方法

about.tsx
import Layout from '../layout';
import Sidebar from '../layout/sidebar';
import { GetStaticProps } from "next";

export default function About(props) {
  return (
    <section>
      <h2>{props.title}</h2>
      <p>{props.content} </p>
    </section>
  );
}

About.getLayout = function getLayout(page) {
  return (
    <Layout>
      <Sidebar/>
      {page}
    </Layout>
  );
};

export const getStaticProps: GetStaticProps = async () => {
  const res = await fetch('http://localhost:3000/json/about.json');
  const rsp = await res.json();
  return {
    props: {
      ...rsp.data,
    },
  };
};
contact.tsx
import Layout from '../layout'
import Sidebar from '../layout/sidebar'
import { GetStaticProps } from "next";

export default function Contact(props) {
  return (
    <section>
      <h2>{props.title}</h2>
      <p>{props.content} </p>
    </section>
  )
}

Contact.getLayout = function getLayout(page) {
  return (
    <Layout>
      <Sidebar />
      {page}
    </Layout>
  )
}

export const getStaticProps: GetStaticProps = async () => {
  const res = await fetch('http://localhost:3000/json/contact.json');
  const rsp = await res.json();
  return {
    props: {
      ...rsp.data,
    },
  };
};
index.tsx
import Layout from '../layout';
import Sidebar from '../layout/sidebar';
import { GetStaticProps } from "next";


export default function Index(props) {
  console.log(props);
  return (
    <section>
      <h2>{props.title}</h2>
      <p>{props.content} </p>
    </section>
  );
}

Index.getLayout = function getLayout(page) {
  return (
    <Layout>
      <Sidebar/>
      {page}
    </Layout>
  );
};

export const getStaticProps: GetStaticProps = async () => {
  const res = await fetch('http://localhost:3000/json/index.json');
  const rsp = await res.json();
  return {
    props: {
      ...rsp.data,
    },
  };
};
最终效果图

在这里插入图片描述

Redux、React-Redux、Redux-soga、next-redux-wrapper

yarn add redux react-redux redux-soga next-redux-wrapper

创建 store 文件,分别创建 index.ts、rootReducers.ts、rootSagas.ts、user目录

index.ts
import { applyMiddleware, createStore, Middleware, StoreEnhancer, Store } from 'redux';
import { createWrapper, Context } from 'next-redux-wrapper';
import createSagaMiddleware, { Task } from 'redux-saga';

import rootReducer from './rootReducers';
import rootSaga from './rootSagas';

export interface SagaStore extends Store {
    sagaTask?: Task;
}

const bindMiddleware = (middleware: Middleware[]): StoreEnhancer => {
    if (process.env.NODE_ENV !== 'production') {
        const { composeWithDevTools } = require('redux-devtools-extension');
        return composeWithDevTools(applyMiddleware(...middleware));
    }
    return applyMiddleware(...middleware);
};

export const makeStore = (context: Context) => {
    const sagaMiddleware = createSagaMiddleware();

    const store = createStore(rootReducer, bindMiddleware([sagaMiddleware]));

    (store as SagaStore).sagaTask = sagaMiddleware.run(rootSaga);

    return store;
};

export const wrapper = createWrapper(makeStore, { debug: true });

rootReducers.ts
import { combineReducers } from 'redux'
import user from './user/reducer'
import { HYDRATE } from 'next-redux-wrapper';

const reducers = combineReducers({
    user,
});

const rootReducers = (state: any, action: any) => {
    if (action.type === HYDRATE) {
        return { ...state, ...action.payload };
    }
    return reducers(state, action);
}

export default rootReducers
rootSaga.ts
import { all } from "redux-saga/effects";
import user from "./user/soga";

function* rootSaga() {
  yield all([...user]);
}

export default rootSaga;

user目录
action.ts
export enum examplesTypes {
  EXAMPLES_01 = "EXAMPLES_01",
  EXAMPLES_02 = "EXAMPLES_02",
  EXAMPLES_ASYNC = "EXAMPLES_ASYNC",
}

export function examples_01(result: any) {
  return {
    type: examplesTypes.EXAMPLES_01,
    payload: result,
  };
}

export function examples_02(result: any) {
  return {
    type: examplesTypes.EXAMPLES_02,
    payload: result,
  };
}

export function examplesAsync(result: any) {
  return {
    type: examplesTypes.EXAMPLES_ASYNC,
    payload: result,
  };
}
reducers.ts
import { examplesTypes } from './action'
import { HYDRATE } from 'next-redux-wrapper'
import { combineReducers } from "redux";
const initialState = {
  examples: "examples"
}

function examplesData(state: any = initialState, action: any) {
  const { type, payload } = action
  switch (type) {
    case HYDRATE: {
      return { ...state, ...action.payload }
    }
    case examplesTypes.EXAMPLES_01:
      return {
        ...state,
        ...payload
      }

    case examplesTypes.EXAMPLES_02:
      return {
        ...state,
        ...payload
      }
    default:
      return state
  }
}


const examples = combineReducers({
  examplesData,
});
export default examples;
soga.ts
import { all, call, fork, takeLatest } from 'redux-saga/effects'
import { examplesTypes } from './action'
import axios, { AxiosResponse } from 'axios';

function* login() {
  try {

  } catch (err) {

  }
}


interface IExamplesAsync {

}

function* examplesAsync() {
  try {
    const { status, data }: AxiosResponse<IExamplesAsync> = yield call(
      axios.get,
      'https://jsonplaceholder.typicode.com/users',
    );

  } catch (err) {

  }
}

function* userSaga() {
  yield all([
    takeLatest(examplesTypes.EXAMPLES_ASYNC, examplesAsync),
  ])
}

let user = [fork(userSaga)];

export default user;

最后调用测试一下
  useEffect(() => {
    dispatch(examplesAsync({ message: 'examplesAsync' }));
  }, []);

在这里插入图片描述

源码

源码链接

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值