简单介绍
要从头开始使用 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' }));
}, []);