zoukankan      html  css  js  c++  java
  • 从零开始的野路子React/Node(3)打通前后端

    相信很多人都听说过前后端分离这个概念,一直以来我比较好奇的一件事是,分离了之后我们怎么再让数据在前后端流通呢?最近正好学习了一下。

    这次我新建了一个叫connection的项目,我们可以用create-react-app frontend新建一个前端目录,叫frontend。再用express backend新建一个后端目录,叫backend。初始化搭建就完成了,结构如图:

    1、搭建后端

    这次我们先从后端开始。

    作为我本人非常野路子的理解:后端就像一个仓库,可以根据前端的需求向前端运送需要的货物。后端有一群仓管,称为API,每个仓管负责找不同的东西。每个仓管有自己的名字,也就是path,而管理这些名字的花名册也就是路由系统了。

    我们的后端非常简单,修改一下app.js文件:

    var express = require('express');
    var cors = require('cors');
    
    var app = express();
    
    var corsOptions = {
        credentials:true,
        origin:'http://localhost:3000',
        optionsSuccessStatus:200
    };
    app.use(cors(corsOptions));
    
    app.get('/', function (req, res) {
        res.send('来者可是诸葛孔明?')
    });
    
    app.listen(5000, function() {
        console.log('App listening on port 5000...')
    });

    我们需要2个库,一个是express,负责后端框架,另一个是cors,负责跨域请求。

    我们后端的地址默认也是localhost,监听的端口是5000。

    这里需要注意的是,如果不使用cors之类的库处理跨域请求的话,我们会遇到跨域问题,简而言之,也就是说虽然我们前后端的域名相同,但却无法分享信息。你的前端即使引用了后端的内容也会报错。

    在corsOptions中我们定义了一些cors所需要的配置,比如指定前端的地址是http://localhost:3000,之后我们使用app.use来把这些内容提供给app。

    最后我们加上一套朴实无华的路由系统——一共只有一个地址,它允许你向http://localhost:3000/发送一个GET请求,每次你发送这个请求,你得到的内容将会是“来者可是诸葛孔明?”

    后端搭建就完成了,在启动之前,再做两件事,第一是把backend目录下的bin目录整个删掉,第二是修改package.json,把“start”对应的值改成node app.js,这样就会从app.js启动了。

    然后我们就可以在cmd中一通npm install再npm start来启动后端了。

    这样就是成功了。让我们用Postman来发送一个GET请求看看:

    看来后端运作没有问题了。

    2、搭建前端

    接下来的问题就是,前端要如何从后端获取数据呢?当然是发送请求给后端的API啦。

    找到API对应的后端地址,发送相应的GET/POST请求,然后API就会返回相应的数据。这就像你说:“王二狗(path),给我一张仓库所有货物的清单(GET request)。”,然后名叫王二狗的仓管就给了你一张清单。

    前端拿到这些数据后,只要再渲染一下即可。

    那么如何发送这种请求呢?作为一个复制黏贴工程师,我并没有正经学过fetch之类的方法,而是直接从大佬那里抄了axios来用,真正做到大佬用什么我用什么。

    现在我们来写个调用后端API的组件CallApi.js吧:

    import axios from 'axios';
    
    const api = 'http://localhost:5000';
    
    class CallApi {
        getSomething() {
            return new Promise((resolve) => resolve(axios.get(`${api}`)));
        }
    }
    
    export default new CallApi();

    这个组件干了几件事,首先导入了axios,然后指定了后端的地址(http://localhost:5000),接着定义了一个类,这个类有一个函数getSomething。每次这个组件被调用,就会返回一个CallApi的实例。

    getSomething这个函数所做的事情就是向http://localhost:5000这个地址发送一个GET请求,然后返回一个Promise对象,Promise会给出一个请求是否成功的答复(当然,我仍然没有非常理解Promise这个东西)。如果执行成功,会给出一个成功的答复(resolve),并且包含了返回的数据。我们可以通过后接then来执行回调函数,把数据挖出来用。

    这里我们再新建一个Page组件,用一种非常简单的方式,用then来获取数据(.data),再把后端传输过来的数据用一级标题显示出来:

    import React from 'react';
    import CallApi from './CallApi';
    
    export default function Page() {
    
        const getContent = () => {
            CallApi.getSomething()
            .then(response => {
                console.log(response.data)
                return response.data
            })
        };
        
        var content = getContent();
        console.log(content)
    
        return (
                <>
                    <h1>{ content }</h1>
                </>
        );
    }

    修改一下App.js,加入Page这个组件:

    import React from 'react';
    import './App.css';
    import Page from './components/Page';
    
    function App() {
      return (
        <div className="App">
          <Page/>
        </div>
      );
    }
    
    export default App;

    整个结构像这样:

    我们在后端已经启动的情况下在frontend目录通过npm start来启动前端,结果却发现页面一片空白……我们看一下console记录的东西,会发现一些有趣的内容:

    首先,content的内容是undefined,而getCotent函数中response.data的内容却是后端传来的数据,两者竟然不一致。其次,我们会发现,console先记录了content,再记录了getCotent函数中的response.data,顺序跟我们写的是反的。

    查阅资料得知,这是由于异步的问题,程序会先解决var content = getContent()的部分,因此我们会先得到undefined,等CallApi.getSomething返回的Promise执行成功了之后,我们才会得到相应的数据,也就是getCotent函数中response.data,这也就解释了这个奇怪的现象。

    这可以打个比方(可能不太恰当),老板让你打电话找仓管要个仓库所有货物的清单,你打电话给王二狗,告诉他你要一份清单,王二狗说没问题,他去找找,一会儿跟你传真过来(Promise)。然后你挂了电话,先回复老板说,我已经打电话问了。老板如果要问你清单的内容,你当然不知道啦(undefined)。一会儿,王二狗找到了清单,给你传真过来了,这下你才获得了数据(response.data)。

    那么如何解决这个问题呢?还得靠第1篇中提到过的useState。我们重写一下刚才的Page.js这个组件:

    import React, {useState} from 'react';
    import CallApi from './CallApi';
    
    export default function Page() {
        const [content, setContent] = useState("");
    
        function getContent() {
            CallApi.getSomething()
            .then(response => {
                console.log(response.data)
                setContent(response.data)
            })
        }
        
        getContent();
        console.log(content)
    
        return (
                <>
                    <h1>{ content }</h1>
                </>
        );
    }

    这一次,我们在getContent中并没有return,而是通过setContent来改变content的状态。这样一来,一旦Promise执行成功了之后,setContent就会把相应的内容赋予content,这样我们的问题也就解决了。

    可以看到最初返回的是””,当Promise获得了后端传来的数据之后,页面就更新了。当然,至于为什么更新了很多次,我还并不清楚……

    如此一来,我们就完成了后端向前端传输数据的过程。

    3、从前向后

    之前我们完成了数据从后向前的传递,现在我们来看看数据从前向后的传递。从前向后我们可以通过POST请求来完成。

    我们先改一下后端:

    var express = require('express');
    var cors = require('cors');
    
    const greeting = {"刘备":"玄德公乃仁义之士", "曹操":"快与我活捉曹贼"}
    
    var app = express();
    
    var corsOptions = {
        credentials:true,
        origin:'http://localhost:3000',
        optionsSuccessStatus:200
    };
    app.use(cors(corsOptions));
    
    app.use(express.urlencoded({extended: true})); // 必须要加
    app.use(express.json()); // 必须要加
    
    app.get('/', function (req, res) {
        res.send('来者可是诸葛孔明?')
    });
    
    app.post('/hello', function (req, res) {
        let grt = greeting[req.body.name]
        res.send(grt)
    });
    
    app.listen(5000, function() {
        console.log('App listening on port 5000...')
    });

    在这里,我们做了几件事:

    (1)新建了一个名为greeting的Object,可以通过人名来找到对应的问候语;

    (2)我们用app.use给app加了express.urlencoded和express.json,这两个不加的话无法正确解析前端传来的数据;

    (3)我们新加了一个API处理POST请求,对应的path是 /hello 。这就像是新招了一个仓管李二饼,王二狗专门负责查清单,李二饼专门负责盘库存。

    每次 /hello 收到一个POST请求之后,我们需要的内容(人名)就藏在请求的body部分里,body是个JSON,我们假设body里name就是我们需要获取的人名。获取人名之后,我们再通过greeting这个Object查找对应问候语,然后将数据传回去(res.send)。

    后端完成之后,我们再改改前端,首先是CallApi这个组件,我们需要新加一个函数对应POST请求:

    import axios from 'axios';
    
    const api = 'http://localhost:5000';
    
    class CallApi {
        getSomething() {
            return new Promise((resolve) => resolve(axios.get(`${api}`)));
        }
    
        sendSomething(body) {
            return new Promise((resolve) => resolve(axios.post(`${api}/hello`, body)));
        }
    }
    
    export default new CallApi();

    sendSomething这个函数接收一个body参数(一个JSON),然后会将它发送给后端的”/hello”。

    再改改Page这个组件:

    import React, {useState} from 'react';
    import CallApi from './CallApi';
    
    export default function Page() {
        const [content, setContent] = useState("");
        const [greeting, setGreeting] = useState("");
    
        function getContent() {
            CallApi.getSomething()
            .then(response => {
                console.log(response.data)
                setContent(response.data)
            })
        }
        
        function handleLB () {
            CallApi.sendSomething({"name":"刘备"})
            .then(response => {
                console.log(response.data)
                setGreeting(response.data)
            })
        }
    
        function handleCC () {
            CallApi.sendSomething({"name":"曹操"})
            .then(response => {
                console.log(response.data)
                setGreeting(response.data)
            })
        }
    
            getContent();
            console.log(content)
    
        return (
                <>
                    <h1>{ content }</h1>
                    <div>
                        <button onClick={ handleLB }>刘备来了</button>
                        <button onClick={ handleCC }>曹操来了</button>
                    </div>
                    <div>
                        <p>{ greeting }</p>
                    </div>
                </>
        );
    }

    我们增加了两个按钮,以及对应按下按钮时触发的函数(handleLB, handleCC),这两个函数跟getContent非常相似,区别在于它们调用的是CallApi.sendSomething,并且会发送一个JSON,而这个JSON里有我们需要传递的人名数据name。我们会将返回的数据赋值给greeting,然后在两个新增按钮的下方显示返回的内容。

    再启动一下试试,点击按钮我们就会得到想要的效果了:

    从前向后的数据传递也就完成了。

    代码见:

    https://github.com/SilenceGTX/react_front_and_back

  • 相关阅读:
    IDT 信息设计工具使用
    子查询
    分组函数
    多表查询
    单行函数
    基本的sql-select语句
    final关键字的使用
    非静态代码块(非static初始化块)&静态代码块(static初始化块)
    单例模式的饿汉式实现
    static关键字的应用
  • 原文地址:https://www.cnblogs.com/silence-gtx/p/13332793.html
Copyright © 2011-2022 走看看