Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign

此文章,会从零开始结合Ant Design UI和PostgreSQL做一个简单的增删改
这里只是一个简单的demo,真实的开发中我们能可能还需要权限,日志,连接池等等。

参考官网:https://nextjs.org/learn/basics/getting-started

开发环境:
window10 x64
node v10.16.3
npm v6.13.4

1.项目初始化

参考‘Next.js与TypeScript从零起步学习笔记-1’,我们先创建一个空项目并添加TS引用:

npm init -y
npm install --save react react-dom next
mkdir pages

npm install --save-dev typescript @types/react @types/node

改一下生成命令,修改'package.json'文件代码:

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}

然后我们在'pages'目录下,建一个'index.tsx',随便输入点代码,测试一下是否能跑去起来:

//pages/index.tsx

const Home = () => 

Hello world!

; export default Home;

运行项目:

npm run dev

应该可以看到效果:

Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第1张图片
图1.项目运行

Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第2张图片
图2.目录结构

这里我并打算用到严格模式(类型约束),因为后面文章引用到的类库,会出现诸多问题,为项目简单起见,我不建议用严格模式。

2.创建RESTful API

2.1 创建表

我们需要在数据库创建一张用户表,用来存放用户的数据,并对其进行简单的增删改查,我用的数据库是PostgreSQL。(PS:你需要有一点数据库相关的知识)

--table next_user 

CREATE TABLE public.next_user (
    id int4 NOT NULL GENERATED ALWAYS AS IDENTITY,
    "name" varchar(40) NOT NULL,
    age int2 NOT NULL,
    created_date_time timestamp NOT NULL,
    CONSTRAINT next_user_pk PRIMARY KEY (id)
);

2.2 创建RESTful API

我们在pages文件夹下创建一个api文件夹,主要用来存放我们api

//以项目根目录为准

cd pages
mkdir api
cd api
mkdir user

我们在'user'文件夹下,创建一个'[id].ts'的文件,这是我们的接口文件,并在文件敲入如下代码:

import { NextApiRequest, NextApiResponse } from 'next';


export default (req: NextApiRequest, res: NextApiResponse) => {
    try{
        switch (req.method.toUpperCase()) {
            case "GET":
                _get(req,res);
                break;
            case "POST":
                _post(req,res);
                break;
            case "PUT":
                _put(req,res);
                break;
            case "DELETE":
                _delete(req,res);
                break;
            default:
                res.status(404).send("");
                break;
        }
    } catch (e){
        //make some logs
        console.debug("error");
        console.debug(e);
        res.status(500).send("");
    }
};

function _get(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("GET");
}

function _post(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("POST");
}

function _put(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("PUT");
}

function _delete(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("DELETE");
}

以上,我们已经创建了我们最简单的RESTful API了,把项目跑起来,我们能看到效果


Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第3张图片
图3.RESTful API效果图

PS:以上是动态路由,即根据路径获取资源Id,如图3URL:http://localhost:3000/api/user/1,后面的'/1'表示的是访问这个id的资源,现实中只有编辑时候才会用到,新增或获取列表时候,并不适用,所以:
我们需要在'pages/api'下,新添一个路由文件'user.ts',主要作用是处理非指定资源(没有id):

//pages/api/user.ts
import { NextApiRequest, NextApiResponse } from 'next';

export default (req: NextApiRequest, res: NextApiResponse) => {
    try{
        switch (req.method.toUpperCase()) {
            case "GET":
                _get(req,res);
                break;
            case "POST":
                _post(req,res);
                break;
            default:
                res.status(404).send("");
                break;
        }
    } catch (e){
        //make some logs
        console.debug("error");
        console.debug(e);
        res.status(500).send("");
    }
};

function _get(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("GET");
}

function _post(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("POST");
}

我们再来修改'pages/api/user/[id].ts',把post去掉,因为post是新增,所以逻辑上在这个文件永远用不上

//pages/api/user/[id].ts
import { NextApiRequest, NextApiResponse } from 'next';

export default (req: NextApiRequest, res: NextApiResponse) => {
    try{
        switch (req.method.toUpperCase()) {
            case "GET":
                _get(req,res);
                break;
            case "PUT":
                _put(req,res);
                break;
            case "DELETE":
                _delete(req,res);
                break;
            default:
                res.status(404).send("");
                break;
        }
    } catch (e){
        //make some logs
        console.debug("error");
        console.debug(e);
        res.status(500).send("");
    }
};

function _get(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("GET");
}

function _put(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("PUT");
}

function _delete(req: NextApiRequest, res: NextApiResponse){
    res.status(200).send("DELETE");
}

Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第4张图片
图4.文件结构

这样建立文件,看上去有点繁琐,但目前我所知道,Next的路由就是这样。
能不能像.NET这样,用标签属性来确定呢?我目前没有找到好方法,知道的小伙伴请不吝赐教,私信告诉我

2.3 对数据库进行读写

我们连接数据库,第一步是先安装上驱动,我开始使用的是'ts-postgres',为什么用它?没啥的,就是因为这个东西是TS写的,但后面发现坑有点多,例如我要count多少行,总是返回null,我以为是函数问题,但换了now又正常,弄了很久,后面放弃了,只好换另一个驱动'node-postgres',这个东西问题就是用js写的,ts里用,你会发现一堆都是any。
安装node-postgres:

npm install pg

我们先来创建一个配置文件,这个配置文件用来保存一些系统的配置,例如数据库连接等等,在根目录下创建'config.json':

//config.json
{
    "dbCofing":{
        "host": "localhost",
        "port":5432,
        "user":"postgres",
        "database":"next_learn",
        "password":"123456"
    }
}

然后我们创建一个文件,命名为'repositories',用来存放sql操作等逻辑:

//以项目根目录为准

cd pages
mkdir repositories

在'repositories'文件夹下,我们创建一个文件'user-repository.ts',这里主要编写用户表(见2.1)的读写逻辑。

//repositories/user-repository.ts

import { Client } from 'pg';
import config from "../config.json";

//这个接口应该单独弄出去,弄个文件夹夹叫utility之类放着,因为后续肯定不止这个地方用到。
export interface PageData {
    index?: number;
    pageSize?: number;
    totalCount?: number;
    list?: Array;
}


export interface NextUser {
    id?: number;
    name?: string;
    age?: number;
    createdDateTime?: Date;
}

export class UserRepository {
    //分页获取所有用户
    async getAllUser(index: number, pageSize: number): Promise> {
        const client = new Client(config.dbCofing);
        await client.connect();

        try {
            let pageData: PageData = {}
            pageData.index = index;
            pageData.pageSize = pageSize;

            //以下加await的话,会同步等待结果返回
            const totalCount = await client.query(
                `SELECT count(*) as total_count from next_user`
            );
            pageData.totalCount = parseInt(totalCount.rows[0].total_count);

            const result = await client.query(
                `select id,name,age,created_date_time from next_user order by id desc limit ${pageSize} offset ${(pageSize * (index - 1))}; `
            );

            let nextUsers: NextUser[] = [];
            for (const row of result.rows) {
                let nextUser: NextUser = {
                    id: row.id as number,
                    name: row.name as string,
                    age: row.age as number,
                    createdDateTime: row.created_date_time as Date
                };

                nextUsers.push(nextUser);
            }
            pageData.list = nextUsers;

            return pageData;

        }  catch(e){
            console.log(e);
        }
        finally {
            await client.end();
        }
    }

    //根据id获取用户
    async getUser(id: number): Promise {
        const client = new Client(config.dbCofing);
        await client.connect();

        try {

            const result = await client.query(
                `select id,name,age,created_date_time from next_user where id = $1`, [id]
            );

            let nextUser: NextUser = null;
            if(result.rows.length > 0){
                nextUser = {
                    id: result.rows[0].id as number,
                    name: result.rows[0].name as string,
                    age: result.rows[0].age as number,
                    createdDateTime: result.rows[0].created_date_time as Date
                };
            }
            
            return nextUser;
        } finally {
            await client.end();
        }
    }

    //添加用户
    async addUser(user: NextUser) {
        const client = new Client(config.dbCofing);
        await client.connect();

        try {
            await client.query(
                `INSERT INTO public.next_user ("name", age, created_date_time) VALUES($1, $2, $3);`, [user.name, user.age, new Date()]
            );
        } finally {
            await client.end();
        }
    }

    //更新用户
    async updateUser(user: NextUser) {
        const client = new Client(config.dbCofing);
        await client.connect();

        try {
            await client.query(
                `UPDATE public.next_user SET "name"=$1, age=$2 WHERE id=$3;
            `, [user.name, user.age,user.id]
            );
        } finally {
            await client.end();
        }
    }

    //删除用户
    async deleteUser(id: number) {
        const client = new Client(config.dbCofing);
        await client.connect();

        try {
            await client.query(
                `delete from next_user where id = $1`, [id]
            );
        } finally {
            await client.end();
        }
    }
}

然后我们分别来修改一下'pages/api/user/[id].ts'和'pages/api/user.ts'文件,让他们访问数据库
PS:正常来说,为了逻辑复用,api不应该直接访问数据仓储层(repository),中间应该多一个service层什么的,这边只是一个演示demo,以简优先,所以省略很多

//pages/api/user/[id].ts

import { NextApiRequest, NextApiResponse } from 'next';
import {UserRepository,NextUser} from '../../../repositories/user-repository';

export default (req: NextApiRequest, res: NextApiResponse) => {
    try{
        switch (req.method.toUpperCase()) {
            case "GET":
                _get(req,res);
                break;
            case "PUT":
                _put(req,res);
                break;
            case "DELETE":
                _delete(req,res);
                break;
            default:
                res.status(404).send("");
                break;
        }
    } catch (e){
        //make some logs
        console.debug("error");
        console.debug(e);
        res.status(500).send("");
    }
};

async function _get(req: NextApiRequest, res: NextApiResponse){
    let userRepository = new UserRepository();
    let id = parseInt(req.query.id.toString());
    let user = await userRepository.getUser(id);

    res.status(200).json({status:"ok",data:user});
}

async function _put(req: NextApiRequest, res: NextApiResponse){
    let userRepository = new UserRepository();
    let user:NextUser= req.body as NextUser;
    user.id = parseInt(req.query.id.toString());
    await userRepository.updateUser(user);

    res.status(200).send({status:"ok"});
}1

async function _delete(req: NextApiRequest, res: NextApiResponse){
    let userRepository = new UserRepository();
    await userRepository.deleteUser(parseInt(req.query.id.toString()));

    res.status(200).json({status:"ok"})
}

//pages/api/user.ts

import { NextApiRequest, NextApiResponse } from 'next';
import {UserRepository,NextUser} from '../../repositories/user-repository';

export default (req: NextApiRequest, res: NextApiResponse) => {
    try{
        switch (req.method.toUpperCase()) {
            case "GET":
                _get(req,res);
                break;
            case "POST":
                _post(req,res);
                break;
            default:
                res.status(404).send("");
                break;
        }
    } catch (e){
        //make some logs
        console.debug("error");
        console.debug(e);
        res.status(500).send("");
    }
};

async function _get(req: NextApiRequest, res: NextApiResponse){
    let userRepository = new UserRepository();
    let index = parseInt(req.query.index.toString());
    let pageSize =  parseInt(req.query.pageSize.toString());

    let pageData = await userRepository.getAllUser(index,pageSize)

    res.status(200).json({status:"ok",data:pageData});
}

async function _post(req: NextApiRequest, res: NextApiResponse){
    let userRepository = new UserRepository();
    let user:NextUser= req.body as NextUser;
    await userRepository.addUser(user);

    res.status(200).send({status:"ok"});
}

启动一下服务器,测试一下结果:

npm run dev

我们先post加一条数据:


Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第5张图片
图5.测试添加数据

Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第6张图片
图6.测试添加数据

然后看看接口查询返回:


Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第7张图片
图7.测试获取数据

其他诸如删除修改等,我就不一一截图了。

3.引入Ant Design UI

3.1配置Ant Design UI

我UI的功底十分差劲,所以引入第三方UI框架,实际开发上,有很多开源且优秀的框架以供你使用,你并不需要重复造轮子。阿里体系貌似全部推荐用yarn,可能大概是跟他们作者之间有什么关联吧?开始yarn有一个lock的优势,但npm后面也跟着更新了,所以我本人觉得yarn相对npm,优势不大。
引入Ant Design UI,在命令行输入:

npm install antd --save

这里有些按需加载的知识需要了解,实际上你应该按需加载。本篇为了简单,所以用全加载。
Any Design UI的使用,请参阅:https://ant.design/index-cn;
除了上述UI的使用文档,我们可能还需要参阅Pro Ant Design:https://pro.ant.design/docs/uset-typescript-cn,这里收集了一些TypeScript的问题。
Next在git上有一个demo,请参阅:https://github.com/zeit/next.js/tree/canary/examples/with-ant-design

Next加载全局CSS时候,需要配置,我这边习惯用less,所以sass不安装了。
安装less:

npm install less less-loader

安装完less之后,我们需要安装Next的全局css引入

npm install @zeit/next-css @zeit/next-less

然后我们在跟目录下,添加一个配置文件'next.config.js'。没错,这就是我们的webpack配置文件,在文件敲入:

const withCSS = require('@zeit/next-css')
const withLess = require('@zeit/next-less')

const isProd = process.env.NODE_ENV === 'production'

// fix: prevents error when .less files are required by node
if (typeof require !== 'undefined') {
  require.extensions['.less'] = file => { }
}

module.exports = withLess(withCSS({
  lessLoaderOptions: {
    javascriptEnabled: true
  }
}))

还有最后一步,就是要找一个地方,配置ant design的全局样式,我们建立一个在跟目录下,建一个文件夹'css',在css下建立一个'antd.less',敲入:

//css/antd.less
@import "~antd/dist/antd.less";

然后我们在首页,加点ant design的东西,看看成功没有。
修改我们'pages/index.tsx',代码如下:

//pages/index.tsx

import {Button} from 'antd'
import '../css/antd.less'

const Home = () => 
    
export default Home;

启动项目,我们可以看到,ant design的东西出来:


Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第8张图片
图8.ant design配置测试

目前整体目录结构如下(next-env.d.ts,tsconfig.json这个是系统运动时候生成的):


Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第9张图片
图9.配置后的目录结构
3.2 做一个简单的增删改页面

现在我们可以用它来做一个带分页的增删改页面了,页面我本地已经做好了,这里直接把它粘贴出来,也没什么好说的。。。。不过是无脑的套用UI框架而已,时间格式可能需要美化,自己写一个方法或者借助第三库如'@angular/common',这里用toString带过,修改我们'pages/index.tsx',代码如下:

//pages/index.tsx

import React from 'react'
import { FormComponentProps } from "antd/lib/form/Form";
import { NextPage } from 'next';
import {
    Form,
    Input,
    Tooltip,
    Icon,
    Cascader,
    Select,
    Row,
    Col,
    Checkbox,
    AutoComplete,
    Button,
    Modal,
    InputNumber,
    Table,
    Divider
} from 'antd';
import '../css/antd.less';
import '../css/style.less';


interface IProps extends FormComponentProps {

}

interface IState {
    modalVisible?: boolean;
}

class Home extends React.Component {
    constructor(props: IProps) {
        super(props);

        this.state = {
            modalVisible: false
        };
    };

    
    //注意这里要写成'handleAdd = () => {}',假如普通写'handleAdd() {}'会引起this为undefined
    handleAdd = () => {
        this.setState({ modalVisible: true });
    };



    closeModal = () => {
        this.setState({ modalVisible: false });
    }

    doEdit = () => {



    };

    render() {
        const { getFieldDecorator } = this.props.form;
        const formItemLayout = {
            labelCol: {
                xs: { span: 18 },
                sm: { span: 6 },
            },
            wrapperCol: {
                xs: { span: 24 },
                sm: { span: 16 },
            },
        };


        const columns = [
            {
              title: '姓名',
              dataIndex: 'name',
              key: 'name',
            },
            {
              title: '年龄',
              dataIndex: 'age',
              key: 'age',
            },
            {
                title: '操作',
                key: 'action',
                render: (text, record) => (
                  
                    编辑
                    
                    删除
                  
                ),
              }
          ];

          const dataSource = [
            {
              key: '1',
              name: '吴彦祖',
              age: 32
            },
            {
              key: '2',
              name: '彭于晏',
              age: 42
            },
          ];


        return (
            
;
{getFieldDecorator('username', { rules: [{ required: true, message: 'Please input your username!' }], })( } placeholder="Username" />, )}
); } } export default Form.create()(Home);

启动可以看到效果:


Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第10张图片
图10.效果图

Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第11张图片
图11.效果图

很简单的就能搭建一个页面,感谢这些开源企业的无私奉献,让我们的工作变得简单。

4.与API对接

现在到了开发的最后一步,我们需要利用之前的RESTful API(详见2)对数据库读写,并渲染页面。这里我使用第三库'axios'请求http接口,安装axios:

npm install axios

然后在页面'pages/index.tsx'的顶部引用进来:

//pages/index.tsx

import axios from 'axios';
4.1 获取所有数据

我们需要把现在的数据库的用户都显示在界面,在'pages/index.tsx'中,我们可以写一个方法这样请求接口:

//pages/index.tsx

//引用用户类型接口,主要为了API返回数据作类型转换
import { NextUser, PageData } from "../repositories/user-repository";
//引用Ant Design的table props,用于table的数据约束
import { ColumnProps } from 'antd/es/table';

//新建一个用户接口,这个接口用于table的数据显示
interface TableUser{
    key?:number,
    id?: number;
    name?: string;
    age?: number;
}

//修改state接口,加上用户属性特性
interface IState {
    modalVisible?: boolean;
    index?: number;
    pageSize?: number;
    tableData?: Array;
    pagination?: any;
}

//这个是ant design的列
const columns: ColumnProps[] = [
    {
        title: '姓名',
        dataIndex: 'name',
        key: 'name',
    },
    {
        title: '年龄',
        dataIndex: 'age',
        key: 'age',
    },
    {
        title: '操作',
        key: 'action',
        render: (text, record) => (
            
                编辑
                
                删除
            
        ),
    }
];

//用我们新数据类型,从新封装table
class NextUserTable extends Table {}

//这个方法方法是获取数据
 getData = async () => {
        let { data } = await axios.get(`/api/user`, {
            params: {
                index: this.state.index,
                pageSize: this.state.pageSize
            }
        });


        if (data.status !== 'ok') return; //or error message

        let pageData: PageData = data.data;

        const pagination = { ...this.state.pagination };
        pagination.total = pageData.totalCount;
        pagination.pageSize = this.state.pageSize;

        const users: Array = pageData.list;
        const tableData: Array = [];

        for (let user of users) {
            tableData.push({
                key: user.id,
                id: user.id,
                name: user.name,
                age: user.age
            });
        }

        this.setState({
            index: this.state.index,
            tableData: tableData,
            pagination
        });
    };

//分页获取数据
    handleTableChange = (pagination, filters, sorter) => {
        this.setState({
            index: pagination.current
        });

        this.getData();
      };

然后我们的可以把ant design的table控件写成这样:

//pages/index.tsx

 

因为我目前数据库只有4条数据,我想弄个分页看看,所以设置pageSize:为了2,即1页2条,这样我们就能看到有2页面。
完整页面代码:

//pages/index.tsx

import React from 'react'
import { FormComponentProps } from "antd/lib/form/Form";
import { ColumnProps } from 'antd/es/table';
//引用用户类型接口,主要为了API返回数据作类型转换
import { NextUser, PageData } from "../repositories/user-repository";
import axios from 'axios';
import {
    Form,
    Input,
    Tooltip,
    Icon,
    Cascader,
    Select,
    Row,
    Col,
    Checkbox,
    AutoComplete,
    Button,
    Modal,
    InputNumber,
    Table,
    Divider
} from 'antd';
import '../css/antd.less';
import '../css/style.less';


interface IProps extends FormComponentProps {

}

interface IState {
    modalVisible?: boolean;
    index?: number;
    pageSize?: number;
    tableData?: Array;
    pagination?: any;
}

//新建一个用户接口,这个接口为table的item格式约束
interface TableUser {
    key?: number,
    id?: number;
    name?: string;
    age?: number;
}

//这个是ant design的列
const columns: ColumnProps[] = [
    {
        title: '姓名',
        dataIndex: 'name',
        key: 'name',
    },
    {
        title: '年龄',
        dataIndex: 'age',
        key: 'age',
    },
    {
        title: '操作',
        key: 'action',
        render: (text, record) => (
            
                编辑
                
                删除
            
        ),
    }
];

const formItemLayout = {
    labelCol: {
        xs: { span: 18 },
        sm: { span: 6 },
    },
    wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 },
    },
};

//封装table
class NextUserTable extends Table { }

class Home extends React.Component {
    constructor(props: IProps) {
        super(props);

        this.state = {
            modalVisible: false,
            index: 1,
            pageSize: 2, //一页2条,是为了测试看到分页,我数据库不想做太多数据
            tableData: []
        };
    };

    componentDidMount?(): void {
        this.getData();
    }

    getData = async () => {
        let { data } = await axios.get(`/api/user`, {
            params: {
                index: this.state.index,
                pageSize: this.state.pageSize
            }
        });


        if (data.status !== 'ok') return; //or error message

        let pageData: PageData = data.data;

        const pagination = { ...this.state.pagination };
        pagination.total = pageData.totalCount;
        pagination.pageSize = this.state.pageSize;

        const users: Array = pageData.list;
        const tableData: Array = [];

        for (let user of users) {
            tableData.push({
                key: user.id,
                id: user.id,
                name: user.name,
                age: user.age
            });
        }

        this.setState({
            index: this.state.index,
            tableData: tableData,
            pagination
        });
    };


    //分页获取数据
    handleTableChange = (pagination, filters, sorter) => {
        this.setState({
            index: pagination.current
        });

        this.getData();
    };


    //注意这里要写成'handleAdd = () => {}',假如普通写'handleAdd() {}'会引起this为undefined
    handleAdd = () => {
        this.setState({ modalVisible: true });
    };



    closeModal = () => {
        this.setState({ modalVisible: false });
    }

    doEdit = () => {



    };

    render() {
        const { getFieldDecorator } = this.props.form;

        return (
            
{getFieldDecorator('username', { rules: [{ required: true, message: 'Please input your username!' }], })( } placeholder="Username" />, )}
); } } export default Form.create()(Home);

启动我们浏览器,可以看到我们数据库的数据显示在页面上了:

Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第12张图片
图12.获取所有数据显示

Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign_第13张图片
图13.获取所有数据显示(数据库)

至此,创建API,引入Ant Design,做数据请求以及TypeScript的数据约束,我们涉及到要用知识点,我们都已经基本用上了,这里用上,余下部门(修改,删除,新增),我这边只会直接把代码贴出来,原理与上述原理并无差异

4.2 余下部分:单个用户查询,新增,修改及删除用户

这里并无新的知识点,仅仅只是调用API,然后渲染页面(把页面弄得好看点),仅此,我这里把所有代码都塞一个文件里,其实这样并不方便维护,在实际开发时候,应该注意分离逻辑,增加复用,另外,上面获取全部数据时候分页有BUG,下面的代码也一并修复了,'pages/index.tsx'完整代码:

//pages/index.tsx

import React from 'react'
import { FormComponentProps } from "antd/lib/form/Form";
import { ColumnProps } from 'antd/es/table';
//引用用户类型接口,主要为了API返回数据作类型转换
import { NextUser, PageData } from "../repositories/user-repository";
import axios from 'axios';
import {
    Form,
    Input,
    Tooltip,
    Icon,
    Cascader,
    Select,
    Row,
    Col,
    Checkbox,
    AutoComplete,
    Button,
    Modal,
    InputNumber,
    Table,
    Divider,
    message
} from 'antd';
import '../css/antd.less';
import '../css/style.less';


interface IProps extends FormComponentProps {

}

interface IState {
    modalVisible?: boolean;
    pageSize?: number;
    tableData?: Array;
    pagination?: any;
    formData?: {
        id?: number;
        name?: string;
        age?: number;
    };
}

//新建一个用户接口,这个接口为table的item格式约束
interface TableUser {
    key?: number,
    id?: number;
    name?: string;
    age?: number;
}

const formItemLayout = {
    labelCol: {
        xs: { span: 18 },
        sm: { span: 6 },
    },
    wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 },
    },
};

//封装table
class NextUserTable extends Table { }

class Home extends React.Component {
    constructor(props: IProps) {
        super(props);

        this.state = {
            modalVisible: false,
            pageSize: 2, //一页2条,是为了测试看到分页,我数据库不想做太多数据
            tableData: [],
            formData: {}
        };
    };

    //这个是ant design的列
    columns: ColumnProps[] = [
        {
            title: '姓名',
            dataIndex: 'name',
            key: 'name',
        },
        {
            title: '年龄',
            dataIndex: 'age',
            key: 'age',
        },
        {
            title: '操作',
            key: 'action',
            render: (text, record) => (
                
                    this.handleEdit(record.id)}>编辑
                    
                    this.doDelete(record.id)}>删除
                
            ),
        }
    ];

    componentDidMount?(): void {
        this.getData(1);
    }

    getData = async (index) => {
        let { data } = await axios.get(`/api/user`, {
            params: {
                index: index,
                pageSize: this.state.pageSize
            }
        });


        if (data.status !== 'ok') return; //or error message

        let pageData: PageData = data.data;

        const pagination = { ...this.state.pagination };
        pagination.total = pageData.totalCount;
        pagination.pageSize = this.state.pageSize;

        const users: Array = pageData.list;
        const tableData: Array = [];

        for (let user of users) {
            tableData.push({
                key: user.id,
                id: user.id,
                name: user.name,
                age: user.age
            });
        }

        this.setState({
            tableData: tableData,
            pagination
        });
    };


    //分页获取数据
    handleTableChange = async (pagination, filters, sorter) => {
        await this.getData(pagination.current);
    };

    //注意这里要写成'handleAdd = () => {}',假如普通写'handleAdd() {}'会引起this为undefined
    handleAdd = () => {
        this.setState({
            modalVisible: true,
            formData: {}
        });
    };

    handleEdit = async id => {
        let { data } = await axios.get(`/api/user/${id}`);
        if(data.status !== "ok") return; //show some error

        let user:NextUser = data.data;
        this.setState({
            modalVisible: true,
            formData: {
                id:id,
                name:user.name,
                age:user.age
            }
        });
    };

    closeModal = () => {
        this.setState({ modalVisible: false });
    }

    doDelete = async id =>{
        let { data } = await axios.delete(`/api/user/${id}`);

        if (data.status === "ok") {
            message.success('删除成功');
            this.getData(1);
        } else {
            message.error('删除失败');
        }
    };

    doEdit = async e => {
        e.preventDefault();
        this.props.form.validateFields(async (err, values) => {
            if (err) return;

            //Received values of form: {name: "1231212312", age: 123}

            if (this.state.formData.id) {
                let { data } = await axios.put(`/api/user/${this.state.formData.id}`, values);

                if (data.status === "ok") {
                    message.success('修改成功');
                    this.setState({ 
                        modalVisible: false
                     });
                    this.getData(1);
                } else {
                    message.error('修改失败');
                }
            } else {
                let { data } = await axios.post(`/api/user`, values);

                if (data.status === "ok") {
                    message.success('添加成功');
                    this.setState({ modalVisible: false });
                } else {
                    message.error('添加失败');
                }
            }
        });
    };

    render() {
        const { getFieldDecorator } = this.props.form;

        return (
            
Submit ]} >
{getFieldDecorator('name', { initialValue: this.state.formData.name, rules: [{ required: true, message: 'Please input your name!' }], })( } placeholder="name" />, )} {getFieldDecorator('age', { initialValue: this.state.formData.age, rules: [{ required: true, message: 'Please input your age!' }], })( )}
); } } export default Form.create()(Home);

5.总结

总体来说,Next.js可以满足某些项目的全栈开发需求,当然,我这个demo用在生产环境上还远远不足,例如缺少权限验证(api接口不是谁都能调用,要授权),日志,连接池等等。

然后,Next是可以用服务端渲染,这个就有点像.NET的MVC和Java的JSP,但我估计现在很少人会这样用了吧?

另外,本人React方面新手一枚,有误导的地方,望留言指出。

我个人感觉这个东西在一些小项目上面尝试,其实还是不错(大项目不用是因为我目前这方面的知识欠缺,不敢乱来),anyway,每一样东西总有个起步吧?
最后祝大家新年快乐!
demo的git地址:https://github.com/JaqenHo/next_js_learn.git

你可能感兴趣的:(Next.js与TypeScript从零起步学习笔记-5(终).项目实战NextJs+PostgreSQL+AntDesign)