記一次GraphQL真正的詳細入門(原生,koa2,vue中的實戰, 建議收藏)分享會

lulu_up發表於2020-06-28
記錄了組內技術分享會, 有同樣需求的同學可以參考一下
分享全程下來時間大約 55分鐘

前言

痛點:網上找的資料,文章, GraphQL的官網,一看就是很‘自我’的大神寫的(太爛了)完全管讀者能不能看懂,舉例子只講用法!不告訴程式碼怎麼實現的(但是當你學完這一篇你就可以看懂了), 並且從來不曬出整體程式碼,導致根本不知道他們怎麼玩的,有被冒犯到!!可以說那些資料都不適合入門。
定位:GraphQL並不是必須用的技術, 但它是必須會的技術,之所以說它必會是因為可以靠它為‘前端’這一行業佔領更大的‘領地’, 同時它的思想是值得琢磨與體會的。
是啥:他不是json更不是js, 他是GraphQL自身的語法, 相容性非常好.
選擇:GraphQL為了使開發更高效、簡潔、規範而生,如果對工程對團隊造成了負擔可以果斷捨棄(別猶豫,這小子不是必需品),畢竟服務端多一層處理是會消耗效能的,那麼就要理智計算他的“損失收益比”了。
前提:學習這門技術需要有一定的“前後互動”的知識儲備, 比如會node, 這裡會介紹如何在node端使用, 如何整合入koa2專案裡面使用, 並且會捨棄一些大家都用的技術, 不做跟風黨。

正文

一. GraphQL到底幹啥的?用它有啥好處哦?

這裡是關鍵, 一定要引起大家的興趣, 不然很難進行。

①: 嚴格要求返回值
比如下面是後端返回的程式碼

 {
  name:1,
  name:2,
  name:3,
 }

前端想要的程式碼

{
 name:1
}

從上面可以看出, name2 與 name3 其實我不想要, 那你傳給我那麼多資料幹什麼,單純為了浪費頻寬嗎?但是吧。。也可理解為某些場景下確實很雞肋,但是請求多了效果就厲害了。

②: 設想一個場景, 我想要通過文章的id獲取作者資訊, 再通過作者資訊裡面的作者id請求他其他的作品列表, 那麼我就需要兩步才能完成, 但是如果這兩個請求在服務端完成那麼我們只用寫一個請求, 而且還不用找後端同學寫新介面。

③: 控制預設值: 比如一個作品列表的陣列, 沒有作品的時候後端很肯能給你返的是null, 但是我們更想保證一致性希望返回[],這個時候可以用GraphQL進行規避.

二. 原生GraphQL嚐鮮。

隨便建個空白專案npm install graphql.
入口路徑如下src->index.js

var { graphql, buildSchema } = require('graphql');

// 1: 定義模板/對映,  有用mongoose運算元據庫經驗的同學應該很好理解這裡
var schema = buildSchema(`
  type Query {
    # 我是備註, 這裡的備註都是單個井號;
    hello: String
    name: String
  }
`);

// 2: 資料來源,可以是熱熱乎乎從mongodb裡面取出來的資料
var root = { 
  hello: () => 'Hello!',
  name:'金毛cc',
  age:5
};

// 3: 描述語句, 我要取什麼樣的資料, 我想要hello與name 兩個欄位的資料, 其他的不要給我
const query =  '{ hello, name }'

// 4: 把準備好的食材放入鍋內, 模型->描述->總體的返回值
graphql(schema, query, root).then((response) => {
  console.log(JSON.stringify(response));
});

上面的程式碼直接 node就可以,結果如下: {"data":{"hello":"Hello!","name":"金毛cc"}};

逐一攻克
1: buildSchema 建立資料模型

var schema = buildSchema(
// 1. type: 指定型別的關鍵字
// 2. Query: 你可以理解為返回值的固定型別
// 3. 他並不是json,他是graphql的語法, 一定要注意它沒有用','
// 4. 返回兩個值, 並且值為字串型別, 注意: string小寫會報錯
` type Query {
    hello: String
    name: String
  }
`);

GraphQL 中內建有一些標量型別 String、 Int、 Float、 Boolean、 ID,這幾種都叫scalar型別, 意思是單個型別

2: const query = '{ hello, name }'
做外層{}基本不變, hello的意思就是我要這一層的hello欄位, 注意這裡用','分割, 之後會把這個','優化掉.

到這裡一個最基本的例子寫出來了, 感覺也沒啥是吧, 我當時學到這裡也感覺會很順利, 但是... 接下來文章斷層, 官網表達不清, 社群不完善等等問題克服起來好心酸.

三. 利用庫更好的原生開發

畢竟這樣每次node命令執行不方便, 並且結果出現在控制檯裡也不好看, 所以我們要用一個專業工具'yoga'.

yarn add graphql-yoga

const { GraphQLServer } =  require('graphql-yoga');

// 型別定義 增刪改查
const typeDefs = `
  type Query{
    hello: String! #一定返回字串
    name: String
    id:ID!
  }
`
const resolvers = {
  Query:{
    hello(){
      return '我是cc的主人'
    },
    name(){
      return '魯魯'
    },
    id(){
      return 9
    },
  }
}
const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(()=>{
  console.log('啟動成功, 預設是4000')
})

當然最好用nodemon來啟動檔案 npm install nodemon -g

  1. hello: String! 像這種加了個'!'就是一定有值的意思, 沒值會報錯.
  2. Query 這裡定義的返回值, 對應函式的返回值會被執行.
  3. new GraphQLServer 的傳參 定義的資料模型, 返回值, 因為具體的請求語句需要我們在web上面輸入.
  4. id的型別使用ID這個會把id轉換為字串,這樣設計也許是為了相容所有形式的id.
  5. server.start 很貼心的起一個服務配置好後效果如下:左邊是輸入, 右邊是返回的結果

jm.png

四. 多層物件定義

我們返回data給前端,基本都會有多層, 那麼定義多層就要有講究了

const {GraphQLServer} =  require('graphql-yoga');
const typeDefs = `
  type Query{
     me: User!  # 這裡把me這個key對應的value定義為User型別, 並且必須有值
  }
  type User { # 首字母必須大寫
    name:String
  }
`
const resolvers = {
  Query:{
    me(){
      return {
        id:9,
        name:'lulu'
      }
    }
    
  }
}
const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(()=>{
  console.log('啟動成功, 預設是4000')
})
  1. User型別不是原生自帶 , 所以我們要自己用type關鍵字定義一個User資料型別.(首字母必須大寫)
  2. Query裡面return的值, 必須滿足User型別的定義規則

當我們取name的值時:
id.png

我剛才故意在返回值裡面寫了id, 那麼可以取到值麼?

did.png

結論: 就算資料裡面有, 但是型別上沒有定義, 那麼這個值就是取不到的.

五. 陣列

定義起來會有些特殊

const { GraphQLServer } = require('graphql-yoga');

const typeDefs = `
  type Query {
    # 返回是陣列
    arr:[Int!]! 
  }
`
const resolvers = {
  Query: {
    arr() {
      return [1, 2, 3, 4]
    }
  }
}

const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(() => {
  console.log('啟動成功, 預設是4000')
})
  1. arr:[Int!] 如果寫成 arr:[] 會報錯, 也就是說必須把陣列裡面的型別定義完全.
  2. Query裡面的返回值必須嚴格按照type裡面定義的返回, 不然會報錯.

結果如下:
arr.png

六. 傳參(前端可以傳引數,供給服務端函式的執行)這個思路很神奇吧.

const { GraphQLServer } = require('graphql-yoga');

const typeDefs = `
  type Query{
     greeting(name: String):String # 需要傳參的地方, 必須在這裡定義好
     me: User!
  }
  type User { # 必須大寫
    name:String
  }
`
const resolvers = {
  Query: {
    // 四個引數大有文章
    greeting(parent, args, ctx, info) {
      return '預設值' + args.name
    },
    me() {
      return {
        id: 9,
        name: 'lulu'
      }
    }

  }
}

const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(() => {
  console.log('啟動成功, 預設是4000')
})
  1. greeting(name: String):String greeting是key沒的說, 他接收一個name引數為字串型別, 這裡必須指明引數名字, 返回值也必須是字串型別, 也就是greeting是一個字串.
  2. greeting(parent, args, ctx, info) { 這裡我們用到 args也就是引數的集合是個物件, 我們args.name就可以取到name的值, 剩下的值後面用到會講.
  3. 既然說了要傳參, 那就必須傳參不然會報錯

changcan.png

因為左側的引數是要放在url請求上的, 所以要用雙引號;

七. 關聯關係

就像資料庫建表一樣, 我們不可能把所有資料放在一張表裡, 我們可能會用一個id來指定另一張表裡面的某些值的集合.
const { GraphQLServer } = require('graphql-yoga');

const typeDefs = `
  type Query{
    lulu: User!
  }
  type User{
    name:String
    age: Int
    chongwu: Chongwu!
  }
  type Chongwu{
     name:String!
     age:Int
  }
`
// 自定義的資料
const chongwuArr = {
  1: {
    name: 'cc',
    age:8
  },
  2: {
    name: '芒果',
    age:6
  },
  9: {
    name: '芒果主人',
    age:24
  }
}

const resolvers = {
  Query: {
    lulu() {
      return {
        name: '魯路修',
        age: 24,
        chongwu: 9
      }
    },
  },
  // 注意, 它是與Query並列的
  User:{
    // 1: parent指的就是 user, 通過他來得到具體的引數
    chongwu(parent,args,ctx,info){
        console.log('=======', parent.chongwu ) // 9
        return chongwuArr[parent.chongwu]
    }
  }
}

const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(() => {
  console.log('啟動成功, 預設是4000')
})

這裡資料量有點多, 我慢慢解析

  1. lulu屬於User類, User類裡面的chongwu(寵物)屬於Chongwu類, 我們需要根據chongwu輸入的id 查詢出 展示哪個寵物.
  2. 由於這個寵物的列表可能來自外部, 所以他的定義方式需要與Query同級.
  3. parent 指的就是父級資料, 也就是通過他可以獲取到輸入的id.

效果如下:
ddai.png

這裡就可以解釋剛開始的一個問題, 就是那個通過文章id找到作者, 通過作者找到其他文章的問題, 這裡的知識點就可以讓我們把兩個介面合二為一, 或者合n為一.

八. 不是獲取, 是操作.

有沒有發現上面我演示的都是獲取資料, 接下來我們來說說運算元據, 也就是'增刪改'沒有'查'
graphql規定此類操作需要放在Mutation這個類裡面, 類似vuex會要求我們按照他的規範進行書寫
const { GraphQLServer } = require('graphql-yoga');

const typeDefs = `
  type Query{
    hello: String!
  }
  # 是操作而不是獲取, 增刪改:系列
  type Mutation{
    createUser(name:String!, age:Int!):CreateUser

    # 這裡面可以繼續書寫create函式...
  }

  type CreateUser{
    id:Int
    msg:String
  }
`

const resolvers = {
  Query: {
    hello() {
      return '我是cc的主人'
    },
  },
  // query並列
  Mutation: {
    createUser(parent, args, ctx, info) {
      const {name,age} = args;
      // 這裡我們拿到了引數, 那麼就可以去awit 建立使用者
       return {
         msg:'建立成功',
         id:999
       }
    }
  }
}



const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(() => {
  console.log('啟動成功, 預設是4000')
})
  1. Mutation是特殊類, 也是與Query並列.
  2. 一個Mutation裡面可以寫多個函式, 因為他是個集合.
  3. 為函式的返回值也可以定義型別

效果如下: 接收id與提示資訊
creat.png

九. input特殊型別

const { GraphQLServer } = require('graphql-yoga');

const typeDefs = `
  type Query{
    hello: String!
  }
  # 是操作而不是獲取, 增刪改:系列
  type Mutation{
    # 這個data隨便叫的, 叫啥都行, 就是單獨搞了個obj包裹起來而已, 不咋地 
    createUser(data: CreateUserInput):CreateUser
  }

  type CreateUser{
    id:Int
    msg:String
  }
   
  # input 定義引數
  input CreateUserInput{
    # 裡面的型別只能是基本型別
    name: String!
    age:Int!
  }
`

const resolvers = {
  Query: {
    hello() {
      return '我是cc的主人'
    },
  },
  // query並列
  Mutation: {
    createUser(parent, args, ctx, info) {
      // **這裡注意了, 這裡就是data了, 而不是分撒開的了**
      const { data } = args;
      return {
        msg: '建立成功',
        id: 999
      }
    }
  }
}

const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(() => {
  console.log('啟動成功, 預設是4000')
})
  1. 把引數放在data裡面, 然後定義data的類
  2. 注意三個關鍵點的程式碼都要改

效果與上面的沒區別, 只是多包了層data如圖:
data.png

這個只能說有利有弊吧, 多包了一層, 還多搞出一個類, 看似可以封裝了實則'雞肋啊雞肋'

十. '更雞肋的'MutationType特殊型別

const {GraphQLServer} =  require('graphql-yoga');

// 非常雞肋, 這種事做不做, 該不該你做, 心裡沒點數
const typeDefs = `
  type Query{
    hello: MutationType
  }

  enum MutationType{
    aaaa
    bbbb
    cccc
  }
`
const resolvers = {
  Query:{
    hello(){
      // 只能返回選單裡面的內容, 這樣可以保證不出格... p用
      return 'bbbb'
    },
  }
}

const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(()=>{
  console.log('啟動成功, 預設是4000')
})
  1. 我定義了一個MutationType的類, 限制只能用'aaa','bbb','ccc'中的一個字串.
  2. 這不貓捉耗子麼? graphql本身定位不是幹這個事的, 這種事情交給統一的資料校驗模組完成, 他做了校驗的話那麼其他情況他管不管? 關了又如何你又不改資料, 就會個報錯公雞想下蛋.
  3. 完全不建議用這個, 當做瞭解, 具體的校驗模組自己在中介軟體或者utils裡面寫.

十一. 整合進koa2專案

1. 終於到實戰了, 講了那麼多的原生就是為了從最基本的技術點來理解這裡
2. 並不一定完全使用graphql的規範, 完全可以只有3個介面用它
3. 我們剛才寫的那些type都是在模板字串裡面, 所以肯定有人要他模板拆解開, 以物件的形式去書寫才符合人類的習慣.

先建立一個koa的工程
// 若果你沒有koa的話, 建議你先去學koa, koa知識點比較少所以我暫時沒寫相應的文章.
koa2 graphqlx // main:工程名 不要與庫重名
npm install graphql koa-graphql koa-mount --save 大朗快把庫安裝好.

app.js檔案裡面
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

////// 看這裡
const mount = require('koa-mount');
const graphqlHTTP = require('koa-graphql');
const GraphQLSchema=require('./schema/default.js');
//////

const index = require('./routes/index')
const users = require('./routes/users')

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// 每一個路徑, 對應一個操作
app.use(mount('/graphql', graphqlHTTP({
  schema: GraphQLSchema,
  graphiql: true // 這裡可以關閉除錯模式, 預設是false
})));

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app
  1. 我直接吧預設配置也粘進來了, 這樣可以保證你拿走就用
  2. graphiql: true 這個時候開啟了除錯模式 會出現下圖的除錯介面, 預設是false
  3. mount 來包裹整體的路由
  4. graphqlHTTP 定義請求相關資料
  5. GraphQLSchema 使我們接下來要寫的一個操作模組.

scxs.png

這個畫面是不是似曾相識!

schema->default.js
const {
  GraphQLID,
  GraphQLInt,
  GraphQLList,
  GraphQLString,
  GraphQLSchema,
  GraphQLNonNull,
  GraphQLObjectType,
  GraphQLInputObjectType,
} = require('graphql');

// id對應的詳情
let idArr = {
  1:{
    name:'我是id1',
    age:'19'
  },
  2:{
    name:'我是id2',
    age:'24'
  }
} 

// 定義id的類
let GID= new GraphQLObjectType({
  name: 'gid',
  fields: {
    name: { type: GraphQLString },
    age: { type: GraphQLString },
  }
})

// 引數型別 不太對
let cs = new GraphQLInputObjectType({
  name:'iddecanshu',
  fields: {
    id: { type: GraphQLString },
  }
})

//定義導航Schema型別
var GraphQLNav = new GraphQLObjectType({
  name: 'nav',
  fields: {
    cc:{ // 傳參
      type:GraphQLString,
      // args:new GraphQLNonNull(cs), // 1; 這種是錯的
      args:{
        data: {
          type:new GraphQLNonNull(cs), // 這種可以用data為載體了
        } 
      },
      // args:{ // 3:這種最好用了。。。
      //   id:{
      //     type:GraphQLString
      //   }
      // },
      resolve(parent,args){
        return '我傳的是' + args.data.id
      }
    },
    // greeting(name: String):String 
    title: { type: GraphQLString },
    url: { type: GraphQLString },
    id: {
      // type:GraphQLList(GID), // 這裡很容易忽略
      type:GraphQLNonNull(GID), // 反覆查詢也沒有專門obj的 這裡用非空代替
      async resolve(parent,args){
        // console.log('wwwwwwwww', idArr[parent.id])
        // 這個bug我tm。。。。。
        // 需要是陣列形式。。。。不然報錯
        // "Expected Iterable, but did not find one for field \"nav.id\".",
        // return [idArr[parent.id]];

        // 2: 更改型別後就對了
        return idArr[parent.id] || {}
      }
    },
  }
})

//定義根
var QueryRoot = new GraphQLObjectType({
  name: "RootQueryType",
  fields: {
    navList: {
      type: GraphQLList(GraphQLNav),
      async resolve(parent, args) {
        var navList = [
          { title: 'title1', url: 'url1', id:'1' },
          { title: 'title2', url: 'url2', id:'2' }
        ]
        return navList;
      }
    }
  }
})

//增加資料
const MutationRoot = new GraphQLObjectType({
  name: "Mutation",
  fields: {
    addNav: {
      type: GraphQLNav,
      args: {
        title: { type: new GraphQLNonNull(GraphQLString) },
      },
      async resolve(parent, args) {
        return {
          msg: '插入成功'
        }
      }
    }
  }
})

module.exports = new GraphQLSchema({
  query: QueryRoot,
  mutation: MutationRoot
});

十二. koa2中的使用原理"逐句"解析

①引入
  1. 這個是原生自帶的, 比如我們會把GraphQLID 這種象徵著單一型別的類單獨拿到.
  2. 定義type的方法也變成了 GraphQLObjectType這樣的例項化類來定義.
const {
  GraphQLID,
  GraphQLInt,
  GraphQLList,
  GraphQLString,
  GraphQLSchema,
  GraphQLNonNull,
  GraphQLObjectType,
  GraphQLInputObjectType,
} = require('graphql');
②單一的類
  1. 我們例項化GraphQLObjectType匯出一個'type'
  2. 使用 type:GraphQLString的形式規定一個變數的型別
  3. name: 這裡的name可以理解為一個說明, 有時候可以通過獲取這個值做一些事.
let GID= new GraphQLObjectType({
  name: 'gid',
  fields: {
    name: { type: GraphQLString },
    age: { type: GraphQLString },
  }
})
③ 定義根類
  1. fields必須要寫, 在它裡面才可以定義引數
  2. GraphQLList意思就是必須為陣列
  3. type不能少, 裡面要規定好這組返回資料的具體型別
  4. resolve也是必須有的沒有會報錯, 並且必須返回值與type一致
var QueryRoot = new GraphQLObjectType({
  name: "RootQueryType",
  fields: {
    navList: {
      type: GraphQLList(GraphQLNav),
      async resolve(parent, args) {
        var navList = [
          { title: 'title1', url: 'url1', id:'1' },
          { title: 'title2', url: 'url2', id:'2' }
        ]
        return navList;
      }
    }
  }
})

十三. koa2裡面的關聯關係與傳參

這裡的關聯關係是指, 之前我們說過的 id 指向另一個表
let GID= new GraphQLObjectType({
  name: 'gid',
  fields: {
    name: { type: GraphQLString },
    age: { type: GraphQLString },
  }
})
var GraphQLNav = new GraphQLObjectType({
  name: 'nav',
  fields: {
    cc:{
      type:GraphQLString,
      args:{
        data: 
          type:new GraphQLNonNull(cs), // 這種可以用data為載體了
        } 
      },
      resolve(parent,args){
        return '我傳的是' + args.data.id
      }
    },
    id: {
      type:GraphQLNonNull(GID),
      async resolve(parent,args){
        return idArr[parent.id] || {}
      }
    },
  }
})
  1. 上面cc這個變數比較特殊, 他需要args這個key來規範引數, 這裡可以直接寫參也可以像這裡一樣抽象一個類.
  2. id他規範了id對應的是一個物件, 裡面有name有age
  3. cc想要拿到傳參就需要args.data 因為這裡我們用的input類來做的

實際效果如圖所示:

ccnz.png

十四. 對整合在koa2內的工程化思考(資料模型分塊)

1. 從上面那些例子裡面可看出, 我們可以用/api/blog這種路由path為單位, 去封裝一個個的資料模型
2. 每個模型裡面其實都需要運算元據庫
3. 說實話增加的程式碼有點多, 這裡只演示了2個介面就已經這麼大了
4. 學習成本是不可忽略的, 而且這裡面各種古怪的語法報錯

十五. 前端的呼叫

這裡我們以vue為例
import axios from "axios"; 這個是前提
query= 這個是關鍵點, 我們以後的引數都要走這裡

方式1(暴力調取)
created(){
    // 1: 查詢列表
    // ①: 一定要轉碼, 因為url上不要有{} 空格
    axios
      .get(
        "/graphql?query=%7B%0A%20%20navList%20%7B%0A%20%20%20%20title%0A%20%20%20%20url%0A%20%20%7D%0A%7D%0A"
      )
      .then(res => {
        console.log("返回值: 1", res.data);
      });
     }
方式2(封裝函式)
  methods: {
    getQuery() {
      const res = `
        {
          navList {
            title
            url
            id {
              name
              age
            }
          }
        }`;
      return encodeURI(res);
    },
  },
  created() {
    axios.get(`/graphql?query=${this.getQuery()}`).then(res => {
      console.log("返回值: 2", res.data);
    });
  }
方式3(函式傳參)
  methods: {
    getQuery2(id) {
      const res = `
        {
          navList {
            cc(data:{id:"${id}"})
            title
            url
            id {
              name
              age
            }
          }
        }`;
      return encodeURI(res);
    }
  },
  created() {
    axios.get(`/graphql?query=${this.getQuery2(1)}`).then(res => {
      console.log("返回值: 3", res.data);
    });
  }

十六. 前端外掛的調研

  1. 一看前面的傳參方式就會發覺, 這肯定不合理啊, 一定要把字串解構出來.
  2. vue-apollo技術棧是當前比較主流的, 但是這個庫寫的太碎了, 並且配置起來還要更改我本來的程式碼習慣.
  3. 又在github上面找了一些模板的庫, 但是並沒有讓我從書寫字串的尷尬中解脫出來

所以暫時沒有找到我認可的庫, 當然了自己暫時也並不想把時間放在開發這個外掛上.

十七. 我想要的外掛什麼樣的

  1. 可以使我,採用物件或者json的方式來定義query
  2. 不需要定義那麼多概念, 開箱即用, 我只需要那個核心的模板解決方案
  3. 不要改變我本來的程式碼書寫方式, 比如說vue-apollo提供了新的請求方式, 可是我的專案都是axios, 我就是不想換 憑什麼要換

十八. 學習graphql有多少阻礙

  1. 網上的資料真的對初學者很不友好, 根本不舉例子, 導致每個知識點都會我自己試了幾十次試出來的.
  2. 光學這一個技術不夠, 還要思考服務端的重構與新規範, 前端也要新規範.
  3. 這個技術也出來好幾年了, 但是社群真的不敢恭維, 所以啊還是需要自己更多的精力投入進來.

十九. 此類技術趨勢的思考

  1. 前端工程師越來越不滿足自己的程式碼只在瀏覽器上執行了, 我們也要參與到服務端互動的環節中.
  2. 身為當今2020年的前端如果眼光還侷限在web端有可能會被後浪排在沙灘上了.
  3. 個人不太喜歡這種丟擲很多新的知識點, 和架構體系的庫, 應該是越少的學習成本與程式碼成本做到最多的事.
  4. 前端技術還在摸索階段, 也可以說是擴充套件階段, 預計這幾年還是會出現各種新的概念, 但是吧正所謂"合久必分,分久必合", 在不就的將來前端的範圍很可能重新劃定.

二十. end

說了這麼多也是因為學習期間遇到了好多坑, 不管怎麼樣最後還是可以使用起來了, 在之後的使用過程中還是會不斷的總結並記錄, 遇到問題我們可以一起討論.
希望和你一起進步.

相關文章