<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于React和GraphQL的黛夢(mèng)設(shè)計(jì)與實(shí)現(xiàn)

          共 37602字,需瀏覽 76分鐘

           ·

          2021-09-22 08:13

          寫(xiě)在前面

          這是筆者在中秋無(wú)聊寫(xiě)著玩的,假期閑暇之余憋出來(lái)的帖子。麻雀雖小,但五臟俱全,涉及到的方方面面還是蠻全的。所以就設(shè)計(jì)了一個(gè)黛夢(mèng)(demo)------ 打通了GraphQL的接口與前端交互的流程,并且將數(shù)據(jù)存入MYSQL,分享下React和GraphQL的使用,大致內(nèi)容如下:

          • GraphQL的增刪改查接口設(shè)計(jì)與實(shí)現(xiàn)
          • CRUD包mysql的使用
          • React 和 React Hooks的使用

          因?yàn)樯婕暗絉eact、GraphQL,還有MySQL的一張用戶(hù)表User,所以我本來(lái)是想起一個(gè)“搞人實(shí)驗(yàn)”的名字,后來(lái)斟酌了一下,啊著,太粗暴了。還是文藝點(diǎn),詩(shī)意點(diǎn),就叫它”黛夢(mèng)“吧,哈哈哈哈哈哈。

          這邊文章著重介紹GraphQL的使用,關(guān)于它的一些概念煩請(qǐng)看我去年寫(xiě)的這篇文章,GraphQL的基礎(chǔ)實(shí)踐------ https://segmentfault.com/a/1190000021895204

          技術(shù)實(shí)現(xiàn)

          技術(shù)選型

          最近在用taro寫(xiě)h5和小程序,混個(gè)臉熟,所以前端這邊我選用React,因?yàn)轺靿?mèng)也不是很大,所以沒(méi)必要做前后端分離,用html刀耕火種意思下得了。后端這塊是Node結(jié)合express和GraphQL做的接口,數(shù)據(jù)庫(kù)用的是MySQL。

          GraphQL的接口設(shè)計(jì)

          我們先拋開(kāi)GraphQL,就單純的接口而言。比如說(shuō)抽象出一個(gè)User類(lèi),那么我們對(duì)其進(jìn)行的操作不外乎增刪改查對(duì)吧。然后我們?cè)賻螱raphQL,結(jié)合已知的業(yè)務(wù)邏輯去熟悉新技術(shù)那么我們可以這么一步一步來(lái),一口氣是吃不成胖子的。

          • 先定義用戶(hù)實(shí)體和相應(yīng)的接口,不做細(xì)節(jié)實(shí)現(xiàn),訪問(wèn)相應(yīng)的接口能返回相應(yīng)的預(yù)期
          • 定義一個(gè)全局變量(或者寫(xiě)進(jìn)一個(gè)文件)去模仿數(shù)據(jù)庫(kù)操作,返回相應(yīng)的結(jié)果
          • 結(jié)合數(shù)據(jù)庫(kù)去實(shí)現(xiàn)細(xì)節(jié),訪問(wèn)相應(yīng)的接口能返回相應(yīng)的預(yù)期

          全局變量Mock數(shù)據(jù)庫(kù)的實(shí)現(xiàn)

          • 第一步:導(dǎo)包

            const express = require('express');
            const { buildSchema } = require('graphql');
            const { graphqlHTTP } = require('express-graphql');

            上面分別導(dǎo)入了相應(yīng)的包,express用來(lái)創(chuàng)建相應(yīng)的HTTP服務(wù)器,buildSchema用來(lái)創(chuàng)建相應(yīng)的類(lèi)型、Query和Mutation的定義。graphqlHTTP用來(lái)將相應(yīng)的實(shí)現(xiàn)以中間件的形式注入到express中。

          • 第二步:定義全局變量

            const DB = {
              userlist: [],
            };

            這里定義一個(gè)全局變量去模仿數(shù)據(jù)庫(kù)操作

          • 第三步:定義相應(yīng)的Schema

            const schema = buildSchema(`
              input UserInput {
                name: String
                age: Int
              }
              type User {
                id: ID,
                name: String,
                age: Int
              }
              type Query {
                getUsers: [User]
              }
              type Mutation {
                createUser(user: UserInput): User
                updateUser(id: ID!, user: UserInput): User
              }
            `
            );

            這里定義了用戶(hù)輸入的類(lèi)型以及用戶(hù)的類(lèi)型,然后Query中的getUsers模擬的是返回用戶(hù)列表的接口,返回User實(shí)體的列表集。Mutation是對(duì)其進(jìn)行修改、刪除、新增等操作。這里createUser接收一個(gè)UserInput的輸入,然后返回一個(gè)User類(lèi)型的數(shù)據(jù),updateUser接受一個(gè)ID類(lèi)型的id,然后一個(gè)UserInput類(lèi)型的user

          • 第四步:對(duì)樓上Schema的Query和Mutation的實(shí)現(xiàn)

            const root = {
              getUsers() {
                return DB.userlist || [];
              },
              createUser({ user }) {
                DB.userlist.push({ idMath.random().toString(16).substr(2), ...user });
                return DB.userlist.slice(-1)[0];
              },
              updateUser({ id, user }) {
                let res = null;
                DB.userlist.forEach((item, index) => {
                  if (item.id === id) {
                    DB.userlist[index] = Object.assign({}, item, { id, ...user });
                    res = DB.userlist[index];
                  }
                });
                return res;
              },
            };
          • 第五步:創(chuàng)建服務(wù)器并暴露想要的端口

            const app = express();

            app.use(
              '/api/graphql',
              graphqlHTTP({
                schema: schema,
                rootValue: root,
                graphiqltrue,
              })
            );

            app.listen(3000, () => {
              console.log('server is running in http://localhost:3000/api/graphql');
            });

            文件地址:https://gitee.com/taoge2021/study-nodejs/blob/master/07-graphql/express/01-graphql/server-3.js

            打開(kāi) http://localhost:3000/api/graphql,可以在playground粘貼下樓下的測(cè)試用例試一下

            query {
            getUsers {
            id
            name
            age
            }
            }

            mutation {
            createUser(user: {name: "ataola", age: 18}) {
            id
            name
            age
            }
            }


            mutation {
            updateUser(id: "5b6dd66772afc", user: { name: "daming", age: 24 }) {
            id,
            name,
            age
            }
            }

            文件地址:https://gitee.com/taoge2021/study-nodejs/blob/master/07-graphql/express/01-graphql/server-3.query

          結(jié)合MySQL的實(shí)現(xiàn)

          這里就不像樓上一樣展開(kāi)了,直接貼代碼吧

          const express = require('express');
          const { buildSchema } = require('graphql');
          const { graphqlHTTP } = require('express-graphql');
          const { cmd } = require('./db');

          const schema = buildSchema(`
            input UserInput {
              "姓名"
              name: String
              "年齡"
              age: Int
            }
            type User {
              "ID"
              id: ID,
              "姓名"
              name: String,
              "年齡"
              age: Int
            }
            type Query {
              "獲取所有用戶(hù)"
              getUsers: [User]
              "獲取單個(gè)用戶(hù)信息"
              getUser(id: ID!): User
            }
            type Mutation {
              "創(chuàng)建用戶(hù)"
              createUser(user: UserInput): Int
              "更新用戶(hù)"
              updateUser(id: ID!, user: UserInput): Int
              "刪除用戶(hù)"
              deleteUser(id: ID!): Boolean
            }
          `
          );

          const root = {
            async getUsers() {
              const { results } = await cmd('SELECT id, name, age FROM user');
              return results;
            },
            async getUser({ id }) {
              const { results } = await cmd(
                'SELECT id, name, age FROM user WHERE id = ?',
                [id]
              );
              return results[0];
            },
            async createUser({ user }) {
              const id = Math.random().toString(16).substr(2);
              const data = { id, ...user };
              const {
                results: { affectedRows },
              } = await cmd('INSERT INTO user SET ?', data);
              return affectedRows;
            },
            async updateUser({ id, user }) {
              const {
                results: { affectedRows },
              } = await cmd('UPDATE user SET ? WHERE id = ?', [user, id]);
              return affectedRows;
            },
            async deleteUser({ id }) {
              const {
                results: { affectedRows },
              } = await cmd('DELETE FROM user WHERE id = ?', [id]);
              return affectedRows;
            },
          };

          const app = express();

          app.use(
            '/api/graphql',
            graphqlHTTP({
              schema: schema,
              rootValue: root,
              graphiqltrue,
            })
          );

          app.use(express.json());
          app.use(express.urlencoded({ extendedfalse }));

          app.use(express.static('public'));

          app.listen(3000, () => {
            console.log('server is running in http://localhost:3000/api/graphql');
          });

          這里跟全局變量不同的是,我這邊對(duì)所有字段和方法增加了相應(yīng)的注釋?zhuān)℅raphQL就是好, 接口即文檔),然后封裝了mysql數(shù)據(jù)庫(kù)的操作方法,引入后去實(shí)現(xiàn)相關(guān)的接口。

          MYSQL增刪改查的封裝

          這里簡(jiǎn)單點(diǎn),我們期望是傳入一條SQL和相應(yīng)的參數(shù),返回相應(yīng)的執(zhí)行結(jié)果。

          const mysql = require('mysql');

          const pool = mysql.createPool({
            host'122.51.52.169',
            port3306,
            user'ataola',
            password'123456',
            database'test',
            connectionLimit10,
          });

          function cmd(options, values{
            return new Promise((resolve, reject) => {
              pool.getConnection(function (err, connection{
                if (err) {
                  reject(err);
                } else {
                  connection.query(options, values, (err, results, fields) => {
                    if (err) {
                      reject(err);
                    } else {
                      resolve({ err, results, fields });
                    }
                    connection.release();
                  });
                }
              });
            });
          }

          module.exports = {
            cmd,
          };

          這里導(dǎo)入了Mysql這個(gè)npm包,在它的基礎(chǔ)上創(chuàng)建了一個(gè)連接池,然后暴露一個(gè)cmd方法,它返回一個(gè)Promise對(duì)象,是我們上面?zhèn)魅雜ql和參數(shù)的結(jié)果。

          文件地址如下:https://gitee.com/taoge2021/study-nodejs/blob/master/07-graphql/express/01-graphql/db.js

          有的時(shí)候我們寫(xiě)代碼,不可能一次就寫(xiě)成我們想要的結(jié)果,比如可能寫(xiě)錯(cuò)了一個(gè)單詞啊,或者參數(shù)什么,所以這里需要對(duì)增刪改查的sql做測(cè)試,具體的如下:

          const { cmd } = require('./db');

          // insert
          // (async () => {
          //   const res = await cmd('INSERT INTO user SET ?', {
          //     id: 'beb77a48b7f9f',
          //     name: '張三',
          //     age: 100,
          //   });
          //   console.log(res);
          // })();

          // {
          //   error: null,
          //   results: OkPacket {
          //     fieldCount: 0,
          //     affectedRows: 1,
          //     insertId: 0,
          //     serverStatus: 2,
          //     warningCount: 0,
          //     message: '',
          //     protocol41: true,
          //     changedRows: 0
          //   },
          //   fields: undefined
          // }

          // delete
          // (async () => {
          //   const res = await cmd('DELETE FROM user WHERE id = ?', ['beb77a48b7f9f']);
          //   console.log(res);
          // })();

          // {
          //   error: null,
          //   results: OkPacket {
          //     fieldCount: 0,
          //     affectedRows: 1,
          //     insertId: 0,
          //     serverStatus: 2,
          //     warningCount: 0,
          //     message: '',
          //     protocol41: true,
          //     changedRows: 0
          //   },
          //   fields: undefined
          // }

          // update
          // (async () => {
          //   const res = await cmd('UPDATE user SET ? where id = ?', [
          //     { name: '大明', age: 25 },
          //     'beb77a48b7f9f',
          //   ]);
          //   console.log(res);
          // })();

          // {
          //   error: null,
          //   results: OkPacket {
          //     fieldCount: 0,
          //     affectedRows: 1,
          //     insertId: 0,
          //     serverStatus: 2,
          //     warningCount: 0,
          //     message: '(Rows matched: 1  Changed: 1  Warnings: 0',
          //     protocol41: true,
          //     changedRows: 1
          //   },
          //   fields: undefined
          // }

          // select
          // (async () => {
          //   const res = await cmd('SELECT id, name, age FROM user');
          //   console.log(res);
          // })();

          // {
          //   error: null,
          //   results: [ RowDataPacket { id: 'beb77a48b7f9f', name: '大明', age: 25 } ],
          //   fields: [
          //     FieldPacket {
          //       catalog: 'def',
          //       db: 'test',
          //       table: 'user',
          //       orgTable: 'user',
          //       name: 'id',
          //       orgName: 'id',
          //       charsetNr: 33,
          //       length: 765,
          //       type: 253,
          //       flags: 20483,
          //       decimals: 0,
          //       default: undefined,
          //       zeroFill: false,
          //       protocol41: true
          //     },
          //     FieldPacket {
          //       catalog: 'def',
          //       db: 'test',
          //       table: 'user',
          //       orgTable: 'user',
          //       name: 'name',
          //       orgName: 'name',
          //       charsetNr: 33,
          //       length: 765,
          //       type: 253,
          //       flags: 0,
          //       decimals: 0,
          //       default: undefined,
          //       zeroFill: false,
          //       protocol41: true
          //     },
          //     FieldPacket {
          //       catalog: 'def',
          //       db: 'test',
          //       table: 'user',
          //       orgTable: 'user',
          //       name: 'age',
          //       orgName: 'age',
          //       charsetNr: 63,
          //       length: 11,
          //       type: 3,
          //       flags: 0,
          //       decimals: 0,
          //       default: undefined,
          //       zeroFill: false,
          //       protocol41: true
          //     }
          //   ]
          // }

          // select
          (async () => {
            const res = await cmd('SELECT id, name, age FROM user WHERE id = ?', [
              'beb77a48b7f9f',
            ]);
            console.log(res);
          })();

          // {
          //   error: null,
          //   results: [ RowDataPacket { id: 'beb77a48b7f9f', name: '大明', age: 25 } ],
          //   fields: [
          //     FieldPacket {
          //       catalog: 'def',
          //       db: 'test',
          //       table: 'user',
          //       orgTable: 'user',
          //       name: 'id',
          //       orgName: 'id',
          //       charsetNr: 33,
          //       length: 765,
          //       type: 253,
          //       flags: 20483,
          //       decimals: 0,
          //       default: undefined,
          //       zeroFill: false,
          //       protocol41: true
          //     },
          //     FieldPacket {
          //       catalog: 'def',
          //       db: 'test',
          //       table: 'user',
          //       orgTable: 'user',
          //       name: 'name',
          //       orgName: 'name',
          //       charsetNr: 33,
          //       length: 765,
          //       type: 253,
          //       flags: 0,
          //       decimals: 0,
          //       default: undefined,
          //       zeroFill: false,
          //       protocol41: true
          //     },
          //     FieldPacket {
          //       catalog: 'def',
          //       db: 'test',
          //       table: 'user',
          //       orgTable: 'user',
          //       name: 'age',
          //       orgName: 'age',
          //       charsetNr: 63,
          //       length: 11,
          //       type: 3,
          //       flags: 0,
          //       decimals: 0,
          //       default: undefined,
          //       zeroFill: false,
          //       protocol41: true
          //     }
          //   ]
          // }

          在測(cè)試完成后,我們就可以放心地引入到express和graphql的項(xiàng)目中去了。額,這里的服務(wù)器我就不避諱打星號(hào)了,快到期了,有需要的同學(xué)可以連上去測(cè)試下,這里用的也是測(cè)試服務(wù)器和賬號(hào)哈哈哈,沒(méi)關(guān)系的。

          相關(guān)的query文件在這:https://gitee.com/taoge2021/study-nodejs/blob/master/07-graphql/express/01-graphql/server-4.query

          貼張圖

          React的前端設(shè)計(jì)

          關(guān)于React項(xiàng)目的搭建,可以看下我之前寫(xiě)的這篇文章:https://www.cnblogs.com/cnroadbridge/p/13358136.html

          在React中,我們可以通過(guò)Class和Function的方式創(chuàng)建組件,前者通過(guò)Class創(chuàng)建的組件,具有相應(yīng)的生命周期函數(shù),而且有相應(yīng)的state, 而后者通過(guò)Function創(chuàng)建的更多的是做展示用。自從有了React Hooks之后,在Function創(chuàng)建的組件中也可以用state了,組件間的復(fù)用更加優(yōu)雅,代碼更加簡(jiǎn)潔清爽了,它真的很靈活。Vue3中的組合式API,其實(shí)思想上有點(diǎn)React Hooks的味道。

          構(gòu)思頁(yè)面

          根據(jù)后端這邊提供的接口,這里我們會(huì)有張頁(yè)面,里面有通過(guò)列表接口返回的數(shù)據(jù),它可以編輯和刪除數(shù)據(jù),然后我們有一個(gè)表單可以更新和新增數(shù)據(jù),簡(jiǎn)單的理一下,大致就這些吧。

          • 增刪改查接口的query

              function getUser(id{
                const query = `query getUser($id: ID!) { 
                  getUser(id: $id) {
                    id,
                    name,
                    age
                  }
                }`
            ;

                const variables = { id };

                return new Promise((resolve, reject) => {
                  fetch('/api/graphql', {
                    method'POST',
                    headers: {
                      'Content-Type''application/json',
                      Accept'application/json',
                    },
                    bodyJSON.stringify({
                      query,
                      variables,
                    }),
                  })
                    .then((res) => res.json())
                    .then((data) => {
                      resolve(data);
                    });
                })
              }

              function getUsers({
                const query = `query getUsers { 
                  getUsers {
                    id,
                    name,
                    age
                  }
                }`
            ;

                return new Promise((resolve, reject) => {
                  fetch('/api/graphql', {
                    method'POST',
                    headers: {
                      'Content-Type''application/json',
                      Accept'application/json',
                    },
                    bodyJSON.stringify({
                      query,
                    }),
                  })
                    .then((res) => res.json())
                    .then((data) => {
                      resolve(data)
                    });
                });
              }

              function addUser(name, age{
                const query = `mutation createUser($user: UserInput) { 
                  createUser(user: $user)
                }`
            ;

                const variables = {
                  user: {
                    name, age
                  }
                };
                return new Promise((resolve, reject) => {
                  fetch('/api/graphql', {
                    method'POST',
                    headers: {
                      'Content-Type''application/json',
                      Accept'application/json',
                    },
                    bodyJSON.stringify({
                      query,
                      variables
                    }),
                  })
                    .then((res) => res.json())
                    .then((data) => {
                      resolve(data)
                    });
                });
              }

              function updateUser(id, name, age{
                const query = `mutation updateUser($id: ID!, $user: UserInput) { 
                  updateUser(id: $id, user: $user)
                }`
            ;

                const variables = {
                  id,
                  user: {
                    name, age
                  }
                };
                return new Promise((resolve, reject) => {
                  fetch('/api/graphql', {
                    method'POST',
                    headers: {
                      'Content-Type''application/json',
                      Accept'application/json',
                    },
                    bodyJSON.stringify({
                      query,
                      variables
                    }),
                  })
                    .then((res) => res.json())
                    .then((data) => {
                      resolve(data)
                    });
                });
              }

              function deleteUser(id{
                const query = `mutation deleteUser($id: ID!) { 
                  deleteUser(id: $id)
                }`
            ;

                const variables = {
                  id
                };
                return new Promise((resolve, reject) => {
                  fetch('/api/graphql', {
                    method'POST',
                    headers: {
                      'Content-Type''application/json',
                      Accept'application/json',
                    },
                    bodyJSON.stringify({
                      query,
                      variables
                    }),
                  })
                    .then((res) => res.json())
                    .then((data) => {
                      resolve(data)
                    });
                })
              }

            上面通過(guò)自帶的fetch請(qǐng)求,分別實(shí)現(xiàn)了對(duì)給出的graphql接口的相關(guān)請(qǐng)求

          • UserPage頁(yè)面組件

              // 頁(yè)面
              const UserPage = () => {
                const [userList, setUserList] = React.useState([]);
                const [userForm, setUserForm] = React.useState({ id''name''age''type'add' });
                const [isReload, setReload] = React.useState(false)
                const [id, setId] = React.useState('');
                React.useEffect(() => {
                  refreshUserList();
                }, []);

                React.useEffect(() => {
                  if (isReload) {
                    refreshUserList();
                  }
                  setReload(false);
                }, [isReload]);

                React.useEffect(() => {
                  if (id) {
                    getUser(id).then(res => {
                      const { data: { getUser: user } } = res;
                      setUserForm({ type'edit', ...user });
                    })
                  }
                }, [id]);

                function refreshUserList({
                  getUsers().then(res => {
                    const { data: { getUsers = [] } } = res;
                    setUserList(getUsers);
                  })
                }

                return (<div>
                  <UserList userList={userList} setReload={setReload} setId={setId} />
                  <UserOperator setUserForm={setUserForm} userForm={userForm} setReload={setReload} />
                </div>
            );
              };

            這里用了兩個(gè)React Hooks的鉤子, useState使得函數(shù)組件可以像Class組件一樣可以使用state, useEffect它接受兩個(gè)參數(shù),第一個(gè)是函數(shù),第二個(gè)是一個(gè)數(shù)組,數(shù)組中的元素的變化會(huì)觸發(fā)這個(gè)鉤子的函數(shù)的執(zhí)行。

          • UserList列表組件

              const UserList = (props) => {
                const { userList, setReload, setId } = props;
                const userItems = userList.map((user, index) => {
                  return <UserItem key={user.id} user={user} setReload={setReload} setId={setId} />
                });
                return (<ul>{userItems}</ul>);
              };
          • UserItem單條數(shù)據(jù)項(xiàng)組件

              // 數(shù)據(jù)項(xiàng)
              const UserItem = (props) => {
                const { user, setReload, setId } = props;

                function handleDelete(id{
                  deleteUser(id).then(res => {
                    const { data: { deleteUser: flag } } = res;
                    if (flag) {
                      setReload(true);
                    }
                  })
                }

                function handleEdit(id{
                  setId(id);
                }

                return (<li>
                  {user.name}: {user.age}歲
                  <span className="blue pointer" onClick={() => handleEdit(user.id)}>編輯</span>
                  <span className="red pointer" onClick={() => handleDelete(user.id)}>刪除</span>
                </li>
            );
              };
          • UserOperator 操作組件

            // 新增
            const UserOperator = (props) => {
              const [id, setId] = React.useState('');
              const [name, setName] = React.useState('');
              const [age, setAge] = React.useState('');
              const { setUserForm, userForm, setReload } = props;

              function handleChange(e, cb{
                cb(e.target.value)
              }

              function handleSubmit({
                const { type } = userForm;
                if (type === 'edit') {
                  updateUser(id, name, Number(age)).then(res => {
                    const { data: { updateUser: flag } } = res;
                    if (flag) {
                      setReload(true);
                      setId('');
                      setName('');
                      setAge('');
                    } else {
                      alert('更新失敗');
                    }
                  })
                } else if (type === 'add') {
                  if (name && age) {
                    addUser(name, Number(age)).then(res => {
                      const { data: { createUser: flag } } = res;
                      if (flag) {
                        setReload(true);
                        setId('');
                        setName('');
                        setAge('');
                      } else {
                        alert('添加失敗');
                      }
                    });
                  }
                }
                setUserForm({ ...userForm, type'add' })
              }

              React.useEffect(() => {
                const { id, name, age } = userForm
                setId(id);
                setName(name);
                setAge(age);
              }, [userForm]);

              return (<div>
                <span>姓名:</span><input type="text" value={name} onChange={e => handleChange(e, setName)} />
                <span>年齡:</span><input type="number" value={age} onChange={e => handleChange(e, setAge)} />
                <button onClick={() => handleSubmit()}>{BUTTON_MAP[userForm.type]}</button>
              </div>
          )
            }
          • 根組件
          const App = (props) => {
              return (<div><h2>{props.title}</h2><UserPage /></div>);
            };

            const root = document.getElementById('root');
            ReactDOM.render(<App title="A Simple GraphQL Demo With React Design By ataola, Have Fun!" />, root);

          文件如下:https://gitee.com/taoge2021/study-nodejs/blob/master/07-graphql/express/01-graphql/public/index.html

          總結(jié)

          刀耕火種的時(shí)代已然是離我們很遠(yuǎn),人類(lèi)文明發(fā)展到現(xiàn)在已然是可以用微波爐煤氣灶燒飯做菜,上面的例子只是介紹了GraphQL的使用,并且結(jié)合React打通了這樣一個(gè)流程。實(shí)際上在開(kāi)發(fā)中,我們往往會(huì)采用社區(qū)一些成熟的技術(shù)棧,比如你需要進(jìn)一步了解GraphQL,可以去了解下Apollo這個(gè)庫(kù)。那么前后端的架構(gòu)就可以是 react-apollo,vue-apollo, 后端的話比如express-apollo,koa-apollo等等。我們?cè)趯W(xué)開(kāi)車(chē)的時(shí)候,往往是學(xué)手動(dòng)擋的帕薩特,而在買(mǎi)汽車(chē)的時(shí)候,往往是喜歡買(mǎi)自動(dòng)擋的輝騰,因?yàn)樗容^符合人類(lèi)文明的發(fā)展趨勢(shì),雖然外表上看上去和帕薩特差不多,但是自動(dòng)擋著實(shí)是文明的進(jìn)步?。?/p>


          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久一区二区三区四区免费听 | 久久久久久国产 | 亚洲高清无码一区 | 亚洲天堂电影网站 | 影音先锋色婷婷 |