並不震驚,也可以不看的 前端 Flutter 勸退指南

七月流螢發表於2019-09-07

前言

都 9012 年了,Flutter 有多火,就不需要我多說了,之前掘金首頁頂著好長一段時間的 Flutter 視訊教程推廣足以證明。

那麼正如必須要先 ”入門“ 才能 ”出門“,那麼前端勸退 Flutter 前也必須得先了解 Flutter。

本文就以前端的角度來給大家捋一捋:在前端眼中,Flutter 的開發到底有何不同?

......然後勸退=_=。【想直接被勸退請滑到最後】

什麼是 Flutter

首先,什麼是 Flutter?

官網解釋:

Flutter是一款 Google 開源的 SDK,可跨平臺地為移動端,Web 端,桌面端構建高效能的應用。

當然,當然,雖說是Web端桌面端都能開發,但是我們更多地會著重於 flutter 的移動端跨平臺開發功能。

那麼,在 flutter之前,其實就有很多跨平臺開發的框架了,知名的有 C# 的 Xamarin, 用 js 的有 nativescript ,阿里的 weex 以及大家都比較熟悉的 react native,那麼名氣不大的就更多了

image

所以,flutter 在一堆跨平臺開發框架中憑什麼脫穎而出呢?

為什麼用 Flutter ?

image

  1. 低投入高產出
    一套程式碼,直接產出 Android + iOS 兩個平臺的應用。 這是跨平臺開發框架的共同優勢,不再多說。
  2. 高效率開發
    通過 Flutter 的 JIT(Just In Time)即時編譯功能,能提供 Hot Reload 功能,快速開發應用。有沒有 Hot Reload 的開發效率高低,這點前端同學應該是深有體會了。
  3. 豐富優雅的UI
    框架本身提供 Material Design 以及 Cupertino 的兩種畫風的 UI 元件,不侷限於系統本身 OEM 的限制。
  4. 高效能應用
    和原生一樣的效能。Flutter 的 AOT 將程式碼編譯成 ARM 二進位制,用自身的 自繪引擎(Skia),沒有 Bridge 依賴,可直接訪問系統底層服務。這些能讓 Flutter 效能毫不遜色於 原生應用。

前面的這些都是其次,最關鍵的是什麼呢?

Flutter 有個好“爹”!

大家琢磨一下,當下的主流的三大前端框架,react、react native 是 facebook 的,angular 又是 google 的,只有 vue 是沒有大公司背景。事實上社群的很多開源框架,其實都是大企業內部孵化出來的。

有個好爹,背靠 Google 爸爸,含著金鑰匙出生,一看就前途不可估量。框架的穩定性和成長性就能得到一定保證,給開源社群信心。

這就是,拼爹一時爽,一直拼一直爽。

言歸正傳,這裡提到 flutter 能不侷限於系統 OEM,以及相比其他跨平臺框架提供更優秀的效能,那麼憑啥就 flutter 那麼秀呢?我們可以從 flutter 框架結構上去探索一下。

Flutter 框架結構

flutter 的框架結構圖如下:

image

好了,相信大家不只一次看到這一張圖了。 懂的可能已經瞭然於胸,不懂的可能還是一臉懵逼。

這裡還是簡單說下

從上往下看, 首先是 Framework,Framework 是用 dart 語言寫的,從上往下,

  1. 有封裝好的 UI 元件 Material 和 Cupertino 【相當於前端的 Ant-Design / Element / iview 等 UI 框架】
  2. 封裝好的 UI 元件 Material 和 Cupertino 由更基礎的 Widget 元件拼裝而成。 【這裡的 Widget 相當於 前端的 HTML div, h1, span 等標籤元素】
  3. 繼續往下, Widget 層是由 Animation(動畫), Painting(繪製), Gesture(手勢) 共同通過 Rendering 構成的 物件。【這裡和前端稍稍有點區別,前端 UI 的結構(html),樣式(CSS),事件互動(JS 是分開的,而在 Flutter ,都是 Widget】

Framework 往下是 Engine, Framework 中的 UI 互動都是有 Engine 來進行繪製渲染的。Engine 層內部會通過 Skia 圖形引擎畫出 UI 元件,Skia 是 Google 開源的 2D 圖形引擎,適用於多個平臺系統,這也是 flutter 能跨平臺的核心元素之一。這也是為什麼前面說 flutter 能不侷限系統 OEM 元件的限制。 也就是說,如果你想要自己封裝一個 ant-design 畫風的 flutter UI 框架,你可以直接通過基礎的 Widget 搭建出自己的 UI 框架。如果底層基礎 UI 滿足不了你的需求。你可以直接用 dart 呼叫 Skia 影象引擎的 API,畫出自己的 UI,沒有任何的限制。

最後是 embedded,嵌入層,這一塊是處理平臺差異性的事情,從而能夠把 flutter 應用嵌入到各個系統平臺。

可以看到 Flutter 沒有用原生系統上的 OEM,而是用 2D 渲染引擎 skia 直接渲染繪製 UI, 這使得其平臺相關層很低,平臺只是提供一個畫布,剩餘的所有渲染相關的邏輯都在Flutter內部,這就使得它具有了很好的跨端一致性。

以上就是 flutter 跨平臺開發的結構了, 那麼這樣設計的優越性在哪呢?我們可以對比下其他應用開發的架構。

跨平臺架構對比

Native

首先我們來看下原生APP開發的架構設計,一般一個 App,會分為兩大塊,分別是 UI 渲染和系統服務呼叫,我們常說的跨平臺開發,其實就是跨的這兩塊。

image
原生 App 的 UI ,會通過平臺提供的原生 OEM 控制元件實現,

而系統服務呼叫,如相機,藍芽等感測器的使用,也會通過平臺系統提供的 API 來實現

那麼這就會粗線一個問題,不同平臺的 OEM 控制元件和 系統服務呼叫規範,以及程式語言不統一,Android 使用 Java / Kotlin,而 iOS 使用 Objective-C / Swift,這就產生了平臺差異性。

一個 app 要開發幾套程式碼,UI 效果還不一定能保持一致,費時費力。

於是,就產生了跨平臺開發的需求。

我們來看下常見的跨平臺架構

Webview

image

首先最常見的跨平臺方案,是直接用 webview ,這其實就是我們常說的 hybrid app 了。

雖說不同平臺的 webview 核心不一定一樣,但是總歸會遵循 w3c 規範, 那麼我們的前端的程式碼可以執行在平臺的 Webview 上,實現 UI 上的跨平臺開發

而系統服務呼叫這一塊呢,就通過 bridge 來通過協議來呼叫原生的方法。

那麼 hybrid app 的方案缺點也是很明顯的, webview 效能比不上原生

為了解決這個 webview 的效能問題,社群又推出了另外一種方案

React Native / Weex

image
如圖所示,React native ,Weex 等框架,是用前端語言描述系統 OEM 之類 實現跨平臺,簡單的來說,是通過寫 js 配置頁面佈局,然後通過 react native 解析成原生的控制元件。

這樣的做法,就明顯提高了效能,因為實質上渲染出來的,還是原生的控制元件。

但是,即便效能提高了,但是依然達不到原生的層次,因為 RN 是通過 Jscore 解析 jsbunder 檔案佈局,和原生直接佈局還是有那麼一丁點差距的。

另外,使用 react native 並不能避免寫原生的程式碼,如果遇到一些平臺相關的複雜問題,還是不得不深入到原生庫中進行必要的調整。去年 Airbnb 就因為類似的原因放棄了 rn。

那麼,用 flutter 就能避免這個問題了麼?我們來看下 flutter 的架構

Flutter

image

前面其實也說過了,flutter 的 UI 渲染是基於 skia 影象引擎完成的,不依賴任何一個系統平臺,平臺僅僅提供一個畫布,讓 影象渲染在畫布上。

那麼直接越過原生的渲染機制,從自身的渲染引擎去渲染檢視,這就和原生一模一樣,沒有了中間商賺差價。

兩者的渲染效能也提升為了 兩者的渲染引擎之間的比較。

至此,我們比較了幾種跨平臺架構的 UI 渲染實現,

那麼關於系統服務的呼叫呢?Flutter 並沒有消除 跨平臺 系統服務呼叫的問題,因為硬體設計層面以及程式語言的差異性是客觀存在的,基本無法避免。

但是不同於之前幾種用 bridge 的方式來呼叫系統服務,flutter 用 Platform channel 的形式去呼叫系統服務,這裡先跳過,下面的章節會詳細講一下這裡的通訊機制

Web VS Flutter

開發語言

image

不同於 Web 把頁面分成了 HTML,CSS,JS, 在 Flutter 中,所有東西都是 widgets 具體 widgets 型別分為:

  • 元素 widget。 如 button,menu,list
  • 樣式 widget。如 font,color
  • 佈局 widget。 如 padding,margin
  • ...

所有的 widget 巢狀組合在一起,就構成了一個 flutter app。

UI 語法

基本樣式

關於樣式語法,前端的程式碼我們很熟悉了,用 HTML 和CSS 能快速實現一個簡單的 UI。

我們來看看一個最基本的盒子模型:

<div class="greybox">
    Lorem ipsum
</div>
<style>
.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Georgia;
}
</style>
複製程式碼
var container = Container( // grey box
  child: Text(
    "Lorem ipsum",
    style: TextStyle(
      fontSize: 24.0,
      fontWeight: FontWeight.w900,
      fontFamily: "Georgia",
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);
複製程式碼

在 flutter ,由於 Flutter 沒有標記語言,我們需要巢狀一個個 Widget 類來實現我們的 UI,這裡的 Container Widget類,其實就相當於 div 標籤。

那麼看到這個程式碼風格,如果有寫過 非 jsx 的 react 的話,你會發現程式碼風格有點像是 React.createElement 的畫風。

React.createElement("div", {
    class: "test-c",
    style: "width: 10px;"
}, "Hello", React.createElement("span", null, "world!"));
複製程式碼

佈局

實現一個 UI ,第二個比較重要的點是佈局, 在 Web 前端,實現佈局的核心要點是 CSS 的屬性:

<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>
<style>
.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
}
</style>
複製程式碼

而在 flutter,則需要一些官方提供的樣式類來實現,例如這裡的 BoxDecoration 類來修飾整個盒子,Alignment 確定文字對齊方式等等

var container = Container( // gray box
  child: Center(
    child:  Container( // red box
      child: Text(
          "Lorem ipsum",
          style: bold24Roboto,
          textAlign: TextAlign.center,
        ),
        decoration: BoxDecoration(
          color: Colors.red[400],
        ),
        padding: EdgeInsets.all(16.0),
      ),
      alignment: Alignment.center,
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);
複製程式碼

互動

Web:

<input name="account" />
<div onclick="handleSubmit()">Submit</div>
複製程式碼

最後一點是互動,類似於大部分的前端 UI 框架,每個元件其實都會暴露出一些事件鉤子,

通過這些鉤子,我們就可以捕獲到使用者的行為,從而實現對應的邏輯,

這裡的 demo 就簡單實現了 輸入校驗, 按鈕的點選提交等基本的互動。

Flutter:

// ...
children: <Widget>[
  TextFormField(
    decoration: InputDecoration(
        hintText: 'Email/Name/Telephone',
        labelText: 'Account *',
      ),
      onSaved: (String value) {
        loginForm.account = value;
      },
      validator: (String value) {
        if (value.isEmpty) return 'Name is required.';
      }
  ),
  RaisedButton(
    child: Text(
      'Login'
    ),
    onPressed: () {
      // print('提交操作');
      // dosomething with loginForm
      handleSubmit()
    },
  ),
]
複製程式碼

其餘的還有 路由,動畫,手勢等互動,這裡不再多說,基本上能用 Web 技術實現的,大都能夠在 flutter 實現

可以看到,flutter 的 UI 部分除了沒有 jsx 之外,其餘部分的設計思想與 react 大同小異。

那麼簡單介紹完語法,我們來實際操作看看,怎麼寫一個 APP

開始一個 Flutter App

首先怎麼安裝 flutter 開發環境這個就不多說了,官網教程教程已經很完善了

目錄結構

簡單說下目錄結構,通過 flutter 建立出一個工程後,會自動生成這樣的目錄結構,

.
├── android         # Android 平臺配置,flutter 自動生成
├── ios             # iOS 平臺配置,flutter 自動生成
├── assets          # 靜態資源目錄
├── build           # 存放構建出的 release 相關檔案
├── lib             # 業務程式碼
├──  └── main.dart  # app 入口
└── pubspec.yaml    # 包管理檔案
複製程式碼

Hello World

上手一個框架,當然要來一個經典的 Hello World。

要實現一個 flutter app, 我們需要 載入 flutter 的基本元件 import 'package:flutter/widgets.dart';, 然後執行基本的 runApp , 那麼一個基本的 hello world 就完成了。

// main.dart 檔案
import 'package:flutter/widgets.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!'
      ),
    ),
  );
}
複製程式碼

效果如下:

並不震驚,也可以不看的 前端 Flutter 勸退指南

可以看到,如果沒有樣式的話,應用就是一坨黑....

就像在前端開發時我們喜歡使用的 ant design 或者 iview 之類的 UI 框架,開發 flutter 一般也會用 UI 框架

Material App

Flutter 內建兩套 UI 元件,分別是 Material UI 和 Cupertino UI,

現在簡單看下一個 material 風格的APP是怎麼實現的, 首先 import material 元件

new 一個 MaterialApp 元件,配置 title, app bar 等資訊,就簡單地生成了個 material 畫風的 app 了。

import 'package:flutter/material.dart';
void main() {
  runApp(
    MaterialApp(
     title: 'Hello App',
     home: Scaffold(
       appBar: AppBar(
         title: Text('Hello'),
       ),
       body: Center(
         child: Text(
           'Hello Flutter',
           style: TextStyle(
             fontSize: 30
           ),
         )
       ),
     ),
   )
  );
}
複製程式碼

並不震驚,也可以不看的 前端 Flutter 勸退指南

更復雜的還可以在這裡配置路由相關資訊,這裡就不再多說。

通過這些我們知道怎麼實現一個 flutter app,那麼看到所有有實體的元素,都是稱為 Widgets, 這裡為了方便理解,我們統稱為元件。

Widgets

image

而元件又細分為 Stateless Widget 和 Stateful Widget,這裡可以很容易聯想到 react 的 無狀態元件和 有狀態元件

事實上 flutter 的這兩種元件確實和 react 的差不多

我們首先看下 無狀態元件(StatelessWidget)

無狀態元件 StatelessWidget

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hello App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Hello'),
        ),
        body: Center(
            child: Text(
          'Hello Flutter',
          style: TextStyle(fontSize: 30),
        )),
      ),
    );
  }
}

複製程式碼
  • StatelessWidget 對應 react 的函式元件 Functional Component
  • 此處的 build 方法對應 react 的 render 方法

再來看看 狀態元件(StatefulWidget)

狀態元件 StatefulWidget

Flutter 的狀態由兩個類組成: 分別是 StatefulWidget 和 State 類。 寫法雖然不同,但是概念都大同小異:

  • StatefulWidget 對應 React.Component
  • StatefulWidget 類管理父元件傳遞的 Prop
  • State 類中管理自身的 State
  • 通過 setState 更新狀態
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Counter App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Counter'),
        ),
        body: Center(
            child: Counter(10) ),
      ),
    );
  }
}


class Counter extends StatefulWidget {
  // 這個類是 state 的配置,可以在此定義父元件傳遞下來的 prop
  final int increaseNum;
  // 建構函式
  Counter(this.increaseNum);
  
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    print('count: $_counter');

    setState(() {
      // setState 的回撥告訴 flutter 去變更 當前 State, 並且 setState() 的呼叫會觸發 build() 從而更新檢視
      _counter += widget.increaseNum;
    });
  }

  @override
  Widget build(BuildContext context) {
    // 每次呼叫 setState 都會觸發 build 方法,同時,類似於 react 的 render 方法,
    // flutter 框架為了讓 重新 build 更加快,也已經對此做了優化
    return Row(
      crossAxisAlignment: CrossAxisAlignment.center,
      mainAxisAlignment: MainAxisAlignment.center,

      children: <Widget>[
        RaisedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        Text('Count: $_counter'),
      ],
    );
  }
}
複製程式碼

學過 React 的同學,是不是對此有種似曾相識的感覺呢?

生命週期

元件出來了,生命週期還遠麼?

類似 React ,Flutter 也有自己的元件生命週期:

  • initState: 初始化狀態
  • didChangeDependencies: state依賴關係變更
  • build: 構建檢視
  • didUpdateWidget: 狀態變更,重新渲染檢視
  • deactivate: ui 元件被暫時移除(如切換頁面)
  • dispose: ui 被永久銷燬

image

到此我們的 UI 元件部分就告一段落。

跨平臺開發,“跨” 的除了平臺 UI 部分外,還有跨了前面提到的平臺系統服務呼叫

Native 服務呼叫

不管是哪一個跨平臺開發的解決方案,基本上都是在UI層面去完成跨平臺,一次開發執行多處,但是當你需要完成特定的功能時,比如:開啟相簿獲取照片,這在這一層面上就無法撼動了,你依然需要使用 Native 的方式來完成。

例如 h5 本身是無法呼叫系統底層 API 的,在 h5 我們就會用到 jsbridge 來給 native 傳送命令,從而讓 native 呼叫系統 API。

而在 flutter, 官方提供了一些外掛(plugins packages)來實現常用的功能,例如:本地圖片選擇,相機功能等,讓我們能夠簡單直接地使用到不同平臺的系統介面。

這裡也提供了一個喚起相機的 demo :

import 'package:image_picker/image_picker.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  File _image;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Image Picker Example'),
      ),
      body: Center(
        child: _image == null
            ? Text('No image selected.')
            : Image.file(_image),
      ),
      // 點選按鈕進行拍照
      floatingActionButton: FloatingActionButton(
        onPressed: getImage,
        tooltip: 'Pick Image',
        child: Icon(Icons.add_a_photo),
      ),
    );
  }


  Future getImage() async {
    // 開啟相機拍攝,並獲得圖片資源
    var image = await ImagePicker.pickImage(source: ImageSource.camera);

    setState(() {
      _image = image;
    });
  }

}
複製程式碼

那麼 外掛是怎麼呼叫 系統服務的呢?這裡就需要用到 flutter 的 methodchannel / platform channel 通訊機制。

Native 服務呼叫-實現自己的 Flutter Plugin

image

如圖所示,flutter 通過 methodchannel 機制來呼叫不同平臺 native 層的 api。由於程式碼最終會被編譯成機器碼,所以這個呼叫過程基本上和原生呼叫差不多,都是無損的,不像通過 bridge 方式呼叫,需要協議轉化。

要自己實現一個 底層服務呼叫的 FlutterPlugin,可以參考官方文件,簡單來說步驟如下:

  1. 定義 plugin package 的 API
  2. 實現不同的底層邏輯
    • Android 平臺的功能實現 (Java / Kotlin)
    • iOS 平臺的功能實現 (Object-C / Swift)
  3. 在 dart 使用 method channel 呼叫原生 api

勸退

看了那麼多,是不是感覺這不像勸退,而是一篇 Flutter 吹文?

別急,這就勸退了。

Flutter 雖然看起來很強大,但是實際上深入琢磨一下,其實也有不少侷限性。

國內環境複雜,小程式橫行

大家都知道,國內的流量幾乎都被幾個大公司壟斷, 而 App 的推廣下載成本也很高。

所以各大公司才推出了五花八門的小程式,到目前為止,已知的有:

微信/百度/支付寶/位元組跳動/ QQ 小程式以及快應用等......

為了快速引流,考慮投入產出比,小公司更願意會用 小程式/快應用/H5 方案,而不是用獲客成本更高的 App 方案。

例如京東的 taro 框架或類似的跨端小程式開發框架,就比 Flutter 更加符合中國特色。

taro 是一個多端統一開發框架,支援用 React 的開發方式編寫一次程式碼,生成能執行在微信/百度/支付寶/位元組跳動/ QQ 小程式、快應用、H5、React Native 等的應用。

放棄了 Web 生態

大家可以看到,整篇下來,除了 react-style 的設計思想之外,flutter 和前端其實關係不大

江湖傳言道:一切能用js實現的應用,都將用js實現。

但是很可惜的是,基於各方面的考慮,google 選擇了 dart 這門並不算熱門的語言作為 flutter 的開發語言,而不是 JavaScript / Typescript。給前端開發接觸 flutter 設定了一定的門檻。

但是,flutter 也不是隻給我們前端用的,站在前端角度,我們當然希望用 js/ts咯。 但這對於 Android/ios 等終端開發來說,其實也是同樣需要一定的成本,可以算是一視同仁了。

社群活躍度欠缺

那麼又由於 dart 語言這兩年才被 flutter 帶起來的緣故,之前一直火不起來,直到 flutter 出來後才強行續命。至此 dart 的社群生態,開源庫等等都比較欠缺,不像前端社群,有豐富的 npm 包。

那麼,大家可以想下,在 flutter 之前,你有聽過 dart 語言麼?

google 為什麼用 dart 作為 flutter 的開發語言呢?

其實是因為…… dart 也有個好爹 Orz,他的爹也是Google。

看到沒有,有個好爹多麼重要,三線語言 dart 這不就被捧得大紅大紫了麼[滑稽]

純前端的侷限性

flutter 用自繪引擎徹底解決了 UI 層面的平臺差異性,但是前面也提到了,系統硬體服務(如相機藍芽等服務)的差異性是無可避免的。

作為一個純前端來說,理想情況下,用 flutter 可以完成所有原生能實現的功能。

但現實往往是不理想的,跨端開發往往會遇到一些平臺相關的問題,如 flutter plugin的相機拍照 ,在某個型號的安卓裝置上有點小bug。如果你是個純前端,運氣好的話能在開源社群找到解決方案,運氣不好,只能向終端(iOS/Android)開發尋求技術支援。

那麼,還需要 iOS/Android 開發來兜底的跨端開發框架,還是一個跨端開發框架麼?

要開發一個成熟的 App,你敢放心交給純前端用 flutter 負責麼?

當然,這並非是 flutter 弊端,而是所有跨平臺方案共同的問題。要是沒這問題,react native 早就一統江湖了,airbnb 也不至於棄坑 react native了。

只要跨平臺框架還存在需要程式設計師自行解決的平臺差異bug,那麼 純前端程式設計師全盤負責移動端開發 就是個偽命題。

並不震驚,也可以不看的 前端 Flutter 勸退指南

那麼,是不是 flutter 就與前端絕緣了呢? 也並非如此。 如果你要開發一個重 UI 展示 ,呼叫系統服務比較少的簡單應用,那麼 flutter 是個不錯的選擇。

總結

事實上,可以看出,最適合用 flutter 的是哪些程式設計師呢?

既會 iOS 開發,又懂一些 Android 開發,這不需要太精通, 能搜尋解決常見終端問題即可的程式設計師。那麼學 flutter 就是如虎添翼了。

如果真的有前端有志於做一名 flutter 開發工程師,那麼不妨簡單學習下 Android 和 iOS 開發。

網際網路寒冬什麼人才最吃香?

多面手,綜合性人才,爆棧工程師...

勸退完畢。




本文首發於 github 部落格
如文章對你有幫助,你的 star 是對我最大的支援
其他文章:


插播廣告:
深圳 Shopee 長期內推
崗位:前端,後端(要轉go),產品,UI,測試,安卓,IOS,運維 全都要。
薪酬福利:20K-50K?,7點下班?,免費水果?,免費晚餐?,15天年假?,14天帶薪病假。 崗位詳情參考PS: 網投了就不能內推了哦。
簡歷發郵箱:chenweiyu6909@gmail.com 或者加我微信:cwy13920

相關文章