通過與React的簡單對比來入門Flutter

2022-01-05 腳本之家

作者 | ELab.lijianye

出品 | ELab團隊(ID:gh_b1857b91fe44)

Flutter簡介

Flutter是谷歌開源的移動端應用開發框架,採用Dart語言作為開發語言,主要的特點是跨平臺,高性能,高保真。一套代碼同時運行在Android與IOS兩端並且可以保持UI的統一性(Web端也可以使用,但是目前性能不佳)。

Flutter如何做到跨平臺以及統一UI(高保真)?關鍵在於谷歌實現了一個跨平臺的繪圖引擎,我們敲出的頁面實際上是這個繪圖引擎畫出來的一張圖片(這個與遊戲十分類似,我們看到的遊戲畫面也是通過遊戲引擎渲染出來的。還有一個不太準確的類比,我們都知道Java可以在大部分平臺上運行,這都得益於Java是跑在Java虛擬機上,這使得平臺的差異消失了)。當然由於這種形式會造成一些缺點,因為我們的頁面實際上是繪圖引擎畫出的一張圖片,所以類似於「選中某些文字然後複製」這種功能實現起來就會變得比較困難。

為什麼Flutter高性能?因為Dart既支持JIT(即時編譯,以JavaScript為代表的語言使用這種方式),又支持AOT(提前編譯,以C++為代表的語言使用這種方式)。因為這樣Dart可以做到開發時使用JIT避免了每次的改動都要重新編譯,發布時使用AOT,提前編譯好提高程序運行速度。

有沒有類似的開發框架?實際上類似原理的QT mobile在Flutter之前就推出了,但是因為官方推廣不給力以及C++極高的上手門檻導致其一直不溫不火。

Dart簡介

在使用Dart開發後,這門語言給我的感覺就是一個Java與JavaScript的綜合產物。在靜態語法方面與Java十分相似,包括類型定義,泛型等。而在動態語法方面就和JavaScript很相似,函數式特性,異步的用法等。如果平時只寫JS可能會不太習慣,但是如果你已經習慣使用TS開發(實際上TS就有很多Java,C#的影子),我相信上手Dart語言不是一件很困難的事。

環境搭建

參考官網[1],有非常詳細的教程。

關鍵步驟,安裝Flutter SDK。

IDE推薦使用Android Studio,當然VS CODE裝上對應插件也OK。

入門

從與React對比開始入門。

如果熟悉React的話,你在使用Flutter的時候肯定會充滿即視感,其實這一點也不奇怪,實際上Flutter官方就提到在設計Flutter時受到了React的影響。對於熟悉React的前端開發人員來說,從與React對比開始入門想必是相對來說比較輕鬆的一個方式。

Flutter與React,兩者都作為一個聲明式UI框架,都遵循UI = f(state)的理念,加之Flutter本身就參考了React,所以兩者有大量相似的地方。下面我們從編寫一個經典前端入門應用Todo List開始我們的Flutter之路。

簡要設計

我們的Todo List主要分為兩個頁面,「Todo列表頁」以及「Todo詳情頁」。

Todo詳情頁用於新增Todo/查看Todo詳情。開始編寫代碼

這裡沒有什麼強制規定,基本上代碼都在lib目錄下就OK了。這裡的目錄結構主要是我的開發習慣。

Flutter目錄結構

├── lib // 相當於React項目的src

│ ├── app.dart // 相當於React項目的App.js

│ ├── main.dart // 相當於React項目的index.js

│ ├── models // MVC模型中的model層類比React項目中的狀態管理部分

│ │ ├── todo.dart // Todo類

│ │ └── todo_list.dart // TodoList類

│ └── pages // 頁面

│   ├── detail // 詳情頁

│   │   └── index.dart

│   └── list // 列表頁

│     └── index.dart

├── pubspec.yaml // 相當於package.json

對比的React項目目錄結構

├── src

│  ├── App.js

│  ├── index.js

│  ├── models

│  │  ├── todo.js

│  │  └── todoList.js

│  ├── pages

│  │  ├── detail

│  │  │  └── index.js

│  │  └── list

│  │    └── index.js

├── package.json

main.dart作為入口文件主要有一個主函數main,同時這個主函數也是作為整個應用的入口函數,其中main裡面起到關鍵作用的就是runApp函數,這與React的ReactDOM.render作用類似。

import 'package:flutter/material.dart'; // 谷歌官方組件庫,類比antd
import 'app.dart';

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

對比的React代碼

import React from 'react'

import ReactDOM from 'react-dom'

import App from './App'



ReactDOM.render(

    <App/>,

    document.getElementById('root')

)

Todo List應用有列表以及todo詳情,因此我們這一塊設計兩個類,一個TodoList類對應列表,一個Todo類對應todo詳情。

至於React項目那邊,可能這種設計不是很常見,但是為了方便對比,設計和Flutter保持了一致。

Flutter Todo類代碼:

import 'package:uuid/uuid.dart';

class Todo {
  bool complete; // todo的完成狀態
  final String id; // todo的唯一id
  final DateTime time; // todo創建時間
  String task; // todo的具體任務

  Todo({
    this.task,
    this.complete = false,
    DateTime time,
    String id
  }) : this.time = time ?? DateTime.now(), this.id = id ?? Uuid().v4();
}

React對比代碼:

import {v4} from 'uuid';

class Todo {
    constructor(task, id = v4(), complete = false, time = new Date().toLocaleString()) {
        this.id = id
        this.task = task
        this.complete = complete
        this.time = time
    }
}

export default Todo

Flutter TodoList類代碼:

import 'package:flutter/foundation.dart';
import 'todo.dart' show Todo;

class TodoList with ChangeNotifier {
  Map<String, Todo> _list = new Map(); // 用於保存所有todo

  Map<String, Todo> get list => _list; // 私有變量的getter

  void add(Todo todo) { // 添加todo
    _list[todo.id] = todo;
    notifyListeners(); // 通知組件狀態改變
  }

  void remove(String id) { // 刪除todo
    _list.remove(id);
    notifyListeners();
  }


  void statusChange(Todo todo) { // 改變todo狀態
    todo.complete = !todo.complete;
    _list.update(todo.id, (value) => todo);
    notifyListeners();
  }

  Todo getById(String id) { // 獲取單個todo
    return _list[id];
  }
}

React對比代碼:

import React, {createContext} from 'react'

class TodoList {
    constructor() {
        this._list = new Map()
    }


    get list() {
        return this._list
    }


    add(todo) {
        this._list.set(todo.id, todo)
    }


    remove(id) {
        this._list.delete(id)
    }

    statusChange(todo) {
        this._list.set(todo.id, {...todo, complete: !todo.complete})
    }

    getById(id) {
        return this._list.get(id)
    }

}


export const todoList = new TodoList()
const TodoListContext = createContext(todoList)
export default TodoListContext

Flutter的路由跳轉主要用到Navigator,React那邊對應的就是history。

頁面路由有幾種方式,詳細參考官網。這裡為了與React對比主要介紹命名路由。

import 'package:flutter/material.dart';
import 'pages/detail/index.dart';
import 'pages/list/index.dart';
import 'models/todo_list.dart';
import 'package:provider/provider.dart';

class App extends StatelessWidget {
  const App({Key key}) : super(key: key); 

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => TodoList()),
      ],

      child: MaterialApp(
        title: 'flutter todo list',
        initialRoute: '/',
        routes: {

          '/': (context) => ListPage(),

          '/list': (context) => ListPage(),

          '/detail': (context) => DetailPage(false),

          '/edit': (context) => DetailPage(true),

        },
      ),
    );
  }
}

React代碼

import {BrowserRouter, Switch, Route} from 'react-router-dom'
import TodoListContext, {todoList} from './models/todoList'
import DetailPage from './pages/detail'
import ListPage from './pages/list'
import './App.css'
import 'antd-mobile/dist/antd-mobile.css'

function App() {
    return (
        <TodoListContext.Provider value={todoList}>
            <BrowserRouter>
                <Switch>
                    <Route exact path="/" component={ListPage}/>
                    <Route exact path="/list" component={ListPage}/>
                    <Route exact path="/detail" component={DetailPage}/>
                    <Route exact path="/edit" component={DetailPage}/>
                </Switch>
            </BrowserRouter>
        </TodoListContext.Provider>
    )
}

export default App

在編寫組件之前,先簡要介紹一下Flutter裡面的Widget。與React頁面都由組件組成的理念類似,Flutter的頁面都是由Widget組成的,因此我們可以把Widget通俗的理解成我們所熟悉的組件。

Flutter也和React一樣有無狀態組件與狀態組件兩種Widget。分別通過繼承StatelessWidget,StatefulWidget來實現。

StatelessWidget,根據名字就可以看出來這是無狀態組件。顧名思義就是用於不需要組件內部管理狀態的場景,相信寫過React的前端程式設計師不難理解。上面介紹路由時貼的代碼就是一個StatelessWidget。

StatefulWidget,這個就是狀態組件。一個StatefulWidget對應一個State類,State類就是狀態組件維護的狀態。State中有兩個常用屬性widget與context。widget是這個狀態組件對應的實例,我們一般使用它來獲取在StatefulWidget中定義的屬性。context就是BuildContext類的一個實例,對應著這個組件所在的組件樹的上下文。

正常情況下,我們要編寫一個Widget,用其他Widget組合起來就可以了。當然如果現有的Widget都不符合你的需求,你可以實現自己的一個獨有的Widget。開頭的時候就說過,我們寫的頁面實際上就是繪圖引擎繪製的一張圖片。在Flutter上面要實現一個自定義Widget,其實就是用到Flutter提供的CustomPainter類把Widget繪製出來(CustomPainter其實是一個Canvas)。

現在所有的準備工作都做好了,我們開始實現Todo List應用最關鍵的兩個頁面。

我們先從列表頁開始。列表的主要功能有Todo展示以及添加todo的按鈕。列表頁主要用於展示,沒有必要維護內部狀態,因此我們選用StatelessWidget。

首先根據最基本的頁面結構來開始寫代碼:

class ListPage extends StatelessWidget {
  const ListPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) { // 類似React class組件的Render
    
    return Scaffold( // Material組件,頁面的骨架

      appBar: AppBar( // 頁頭的導航欄

        title: Text('Todo List'),

        leading: Container(), // 用於隱藏左側返回按鈕

      ),

      floatingActionButton: FloatingActionButton( // 頁面上浮動的按鈕

        child: Icon(Icons.add), // 按鈕展示的icon

        onPressed: () {/* todo */}, // 點擊事件

      ),

      body: ListView.builder( // 頁面主體的列表

        itemCount: 0, // 列表包含的列表項總數量

        itemBuilder: (context, index) { // 具體列表項組件

          /* todo */

          return Container();

        },
      ),
    );
  }
}

React對比代碼:

const ListPage = (props) => {
    return (
        <div>
            <NavBar>Todo List</NavBar>
            <List>
                {/* todo */}
            </List>
            
            <Button
                onClick={() => {}}
                icon={<Icon type="plus"/>}
            />
        </div>
    )
}

export default ListPage

可能你會覺得React與Flutter對比起來好像也沒有那麼相似,但是如果你見過在jsx出現以前的React代碼肯定就不會這麼覺得了。下面貼一下沒有jsx的React代碼再來對比一下。

const ListPage = (props) => {
    return createElement(
        'div',
        null,
        [
            createElement(
                NavBar,
                {key: 'listNavBar'},
                'Todo List',
            ),

            createElement(
                List,
                {key: 'listContent'},
            ),

            createElement(
                Button,
                {
                    key: 'listButton',
                    onClick: () => {},
                    icon: createElement(
                        Icon,
                        {type: 'plus'}
                    )
                }
            ),
        ]
    )
}



export default ListPage

從這三段代碼對比我們就能看出,Flutter的組件用法其實很像在React裡面直接使用React.creatElement的形式。從這裡我們也能看出一些Flutter的缺點,對於前端人員來說這種寫法不太直觀。根據jsx的原理,我覺得未來Flutter出現類似於jsx這種寫法也不奇怪,期待dart對應的dsx出現。

由上述代碼我們就可以得到一個這樣的頁面:

接下來我們開始實現具體功能,首先是添加按鈕。添加按鈕是要跳轉到新增Todo的頁面,因此這個按鈕要有一個路由跳轉的回調。下面我們來實現代碼(限於篇幅,往下只會貼React代碼的部分片段,完整代碼請移步到todo_list_react[2], todo_list_react_nojsx[3]):

floatingActionButton: FloatingActionButton(

    child: Icon(Icons.add),

    onPressed: () => Navigator.of(context).pushNamed('/edit'), // 跳轉到新增todo頁

),

Navigator.of(context).pushNamed('/edit'),這個和我們熟悉的history.push('/edit')是類似的。但是卻多了一個.of(context),這個有什麼作用呢?這個of其實和js裡面的bind有點相似,是用來綁定上下文的,這個context就是Widget所在Widget樹的一個上下文。

接下來我們實現列表的代碼。列表項由這麼幾部分組成,todo狀態,todo任務,詳情按鈕,刪除。

ListView.builder( // 官方列表組件
  itemCount: list.list.length,
  itemBuilder: (context, index) {
    var v = list.list.values.toList()[index];
    return Card( // 官方卡片組件
      child: Dismissible( // 官方手勢組件
        key: Key(v.id),
        onDismissed: (direction) { // 划動回調,用於刪除todo
          Scaffold.of(context).showSnackBar(SnackBar(content: Text('刪除了任務 ${v.task}')));
          list.remove(v.id);
        },

        background: Container( // 左/右划動展示刪除icon
          color: Colors.red,
          child: ListTile(
            leading: Icon(
              Icons.delete,
              color: Colors.white,
            ),

            trailing: Icon(
              Icons.delete,
              color: Colors.white,
            ),
          ),
        ),

        child: ListTile( // 官方列表項組件
          title: Row(
            children: [
              Icon(v.complete ? Icons.check_circle : Icons.access_time, color: v.complete ? Colors.green : Colors.red,),
              Container( // todo完成狀態
                child: Padding(
                  padding: EdgeInsets.all(8.0),
                  child: v.complete ?
                  Text('已完成', style: TextStyle(color: Colors.white)) :
                  Text('未完成', style: TextStyle(color: Colors.white)),
                ),

                decoration: BoxDecoration(
                  color: Colors.blue,
                  borderRadius: BorderRadius.all(Radius.circular(5)),
                ),

                margin: EdgeInsets.only(right: 10, left: 10),
              ),

              Container( // todo任務
                width: 200,
                child: Text(v.task, overflow: TextOverflow.ellipsis, maxLines: 1),
              ),
            ],
          ),

          trailing: IconButton( // 詳情按鈕
            icon: Icon(Icons.keyboard_arrow_right),

            onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => DetailPage(false, curId: v.id))),
          ),

          onTap: () { // 點擊回調,todo狀態切換
            onPressed: list.statusChange(v);
          },
        ),
      ),
    );
  },
),

結合上面代碼,介紹一些基礎Widget。我這裡簡單分成基礎類,容器類,布局類以及功能類來介紹(詳細API參考官網)。

基礎類,Image,Text等。這些對應img,span等html標籤容器類,Container,Padding這些就是容器類Widget。為了方便理解,我們可以把它當作我們常用div這種html標籤。布局類,Row,Column這些就是布局類Widget。這兩個與我們常用的flex布局很相似,由主軸與交叉軸控制布局,Row就是flex的橫向,Column就是flex的縱向。功能類,Dismissible,Navigator這些Widget。這些對應某些功能,如手勢,路由等。

這裡吐槽一下Flutter的樣式寫法,看一下上面的一些樣式代碼,再對比我們前端人員熟悉的css,Flutter的樣式編寫方式明顯很繁瑣很不直觀。不管是原生,css module,css in js還是less這種css預處理器都吊打Flutter這種樣式處理。

增加了列表的代碼後,可以得到下面的效果:

頁面部分已經完成了,但是我們的組件數據從哪裡得來呢?這就要回到上面路由的那部分代碼。

MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => TodoList()),
      ],
      // ...
);

Flutter和React的理念很像,狀態的改變會導致頁面的改變。這裡介紹一下跨組件如何共享狀態,Flutter也能使用我們熟悉的一些狀態管理庫Redux,Mobx。這裡使用官方推薦的Provider。

其實我們可以對比這React的Context來看。上面的代碼其實就相當於:

<TodoListContext.Provider value={todoList}>

    {/* ... */}

</TodoListContext.Provider>

對於被包裹的組件使用狀態也很類似。

Flutter:

TodoList list = Provider.of<TodoList>(context);

React:

const list = useContext(TodoListContext)

與React稍有不同的是,Flutter裡使用Provider要手動通知組件。

class TodoList with ChangeNotifier { // ChangeNotifier通知的類
  Map<String, Todo> _list = new Map(); // 用於保存所有todo
  Map<String, Todo> get list => _list; // 私有變量的getter

  void add(Todo todo) { // 添加todo
    _list[todo.id] = todo;
    notifyListeners(); // 通知組件狀態改變的方法
  }
  // ...
}

簡單解釋一下上面的代碼。dart裡面有幾種復用代碼的方式,extends(繼承),implements(實現),with(混入)。繼承應該大家比較熟悉這裡就不展開了。實現的話,其實和Java很類似,子類不可以繼承多個父類,但是可以實現多個接口,但是因為dart裡沒有接口(interface),這個關鍵字是用來實現多個抽象類使用的。混入的話,就是和vue裡面的mixins類似了,這裡相信大家也比較熟悉就不展開了。

至此列表頁已經完成。我們開始實現新增todo/todo詳情頁。新增頁很簡單,我們只需要一個輸入框輸入任務然後提交就可以了。詳情頁的話就是展示todo的信息。

因為新增頁/詳情頁是需要狀態的,所以我們使用StatefulWidget來實現頁面。

class DetailPage extends StatefulWidget {
  DetailPage(this._isCreate, {String curId}) {
    this._curId = curId; 
  }

  final bool _isCreate; // 是否是新增todo
  String _curId; // 查看詳情頁時對應todo的id

  @override
  _DetailPageState createState() => _DetailPageState(); // 狀態組件都需要實現的方法
}



class _DetailPageState extends State<DetailPage> { // 狀態組件對應的狀態
  final _formKey = GlobalKey<FormState>(); // 對比React Form的ref
  bool _isCreate; // 是否新增todo
  String _task; // todo任務
  String _curId; // 當前todo id

  @override
  void initState() { // 初始化生命周期
    // TODO: implement initState
 super.initState();
    _isCreate = widget._isCreate; // widget是上面StatefulWidget的實例
    _curId = widget._curId; // 用來獲取StatefulWidget聲明的屬性
  }



  @override
  Widget build(BuildContext context) {
    TodoList list = Provider.of<TodoList>(context);
    return Scaffold( // 頁面骨架
      appBar: AppBar(
        title: _isCreate ? Text('新增Todo') : Text('Todo詳情頁'),
        leading: IconButton(
          icon: Icon(Icons.arrow_back_ios),
          onPressed: () => Navigator.of(context).pushNamed('/'),
        ),
      ),

      body: _isCreate ? // 新增頁或者詳情頁
      Form( // 表單
        key: _formKey, // 類似React ref
        child: Column(
          children: [
            TextFormField( // 輸入框
              decoration: InputDecoration(
                labelText: '任務',
                prefixIcon: Icon(Icons.article),
              ),

              validator: (value) { // 校驗回調
                if (value == null || value.isEmpty) {
                  return '必須填寫';
                }

                return null;
              },

              onSaved: (value) { // 表單保存時觸發的回調
                _task = value;
              },
            ),

            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16.0),
              child: ElevatedButton( // 表單提交按鈕
                onPressed: () {
                  if (_formKey.currentState.validate()) { // 表單校驗通過
                    _formKey.currentState.save(); // 保存field
                    list.add(new Todo( // 添加todo
                      task: _task,
                      time: new DateTime.now(),
                      complete: false,
                    ));

                    Navigator.of(context).pushNamed('/'); // 路由回列表頁
                  }  
                },

                child: Text('提交'),
              ),
            )
          ],
        ),
      ) : // 詳情頁代碼

      Column(
        children: [
          Card(
            child: Container(
              padding: EdgeInsets.all(16),
              width: MediaQuery.of(context).size.width,
              child: Row(
                children: [
                  Text('任務:', style: TextStyle(fontSize: 16)),
                  Expanded(
                    child: Text('${list.getById(_curId).task}', style: TextStyle(fontSize: 16)),
                  ),
                ],
              ),
            ),
          ),

          Card(
            child: Container(
              padding: EdgeInsets.all(16),
              child: Row(
                children: [
                  Text('任務狀態:', style: TextStyle(fontSize: 16)),
                  Text('${list.getById(_curId).complete ? '已完成' : '未完成'}', style: TextStyle(fontSize: 16)),
                ],
              ),
            ),
          ),

          Card(
            child: Container(
              padding: EdgeInsets.all(16),
              child: Row(
                children: [
                  Text('任務創建時間:', style: TextStyle(fontSize: 16), textAlign: TextAlign.left),
                  Text('${list.getById(_curId).time}', style: TextStyle(fontSize: 16)),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

在Flutter裡面State是有生命周期的,雖然上面代碼只用到了initState,但是我覺得了解具體的生命周期是有必要的。

根據上圖介紹一下主要的生命周期。

initState,Widget首次掛進widget樹時調用,對比React的componentDidMount。didChangeDependencies,State對象中的依賴變化時調用,上面介紹Provider的時候,狀態通知給Widget時就會觸發。build,構建Widget子樹,對比React的render。reassemble,開發專用,熱重載的時候回調用。didUpdateWidget,Widget重新構建時會檢測Widget是否要更新。新舊widget的key和runtimeType同時相等時說明這個Widget還是它自己,只需要更新不需要卸載。這時didUpdateWidget就會被調用。Widget的key對比React組件的key,runtimeType對比React.createElement的type(div變span)。deactive,Widget從Widget樹中移除然後又重新插入widget樹中就會被調用。換成我們熟悉的概念就是dom節點在dom樹中的位置改變。dispose,Widget在Widget樹中被移除就會調用。也就是組件卸載。

完成上面代碼就得到了如下頁面:

 

至此我們的Todo List應用已完成。

學習資料推薦

《Flutter實戰》[4]

官網實用教程[5]

參考資料[1]

官網: https://flutter.cn/

[2]

todo_list_react: https://codesandbox.io/s/0v2vz

[3]

todo_list_react_nojsx: https://codesandbox.io/s/t6qtk

[4]

《Flutter實戰》: https://book.flutterchina.club/

[5]

官網實用教程: https://flutter.cn/docs/cookbook

相關焦點

  • 半小時帶你入門 Flutter
    img國際慣例,吹一波先~直接移步Flutter官宣ppt關於Dart作為Flutter入門文章,Dart必然少不了,當然,作為Flutter入門篇,Dart預發基礎必然不會過多介紹。所有的布局使用一種語言,聚集在一處,Flutter很容易提供高級工具,使布局更簡單Dart對於IOS、Android、Web FE來說,都還比較友好。
  • 用前端最舒服的躺姿 "搞定" Flutter
    這個前浪,就是"react Native","weex"。目前隨便在搜尋引擎上 搜索"Flutter reactNative",就全是這兩個技術的對比,評測。一股股濃濃 : 不服來 「掰」 啊 !!!的味道。
  • 從 Vue2.0 到 React17 —— React 開發入門
    Vue會提供一系列技術支持來完成一個組件的開發,可以從這一系列技術支持出發,去React中尋找對應的技術支持來入門React,比如React中如何開發組件的UI,React中如何使用組件,React中如何定義組件數據等等。本專欄將按照這個思路帶領你從Vue2.0入門React17。
  • 深度測評 | 五大主流多端開發框架全面對比
    unzip ~/Downloads/flutter\_macos\_vX.X.X-stable.zipexport PATH="$PATH:`pwd`/flutter/bin"如果過程中遇到問題可以使用 flutter doctor 來查看問題進行修復,有報錯或者缺失環境,會有提示你如何修改,比較方便。
  • flutter實現簡單的旋轉動畫
    前言flutter實現簡單的旋轉動畫,和大家一起學習探討。
  • Flutter 原理及美團實踐
    在Flutter中,所有功能都可以通過組合多個Widget來實現,包括對齊方式、按行排列、按列排列、網格排列甚至事件處理等等。Flutter控制項主要分為兩大類,StatelessWidget和StatefulWidget,StatelessWidget用來展示靜態的文本或者圖片,如果控制項需要根據外部數據或者用戶操作來改變的話,就需要使用StatefulWidget。
  • Flutter 完整開發實戰詳解 (Flutter 畫面渲染的全面解析) | 開發者說·DTalk
    本文原作者: 戀貓de小郭,原文發布於微信公眾號: GSYTech https://mp.weixin.qq.com/s/aVdZVMqnrdy2vdATU9jBVg作為系列文章的第二十一篇,本篇將通過不一樣的角度來介紹
  • || 附《react中文入門教程》電子版
    Material-UI是一款React組件庫來實現Google的Material Design風格UI界面框架。也是首個React的UI工具集之一。使用它可以快速搭建出賞心悅目的應用界面。官網:https://react-bootstrap.github.io中文文檔: http://react.tgwoo.comGithub: https://github.com/react-bootstrap/react-bootstrap/Ant-design
  • 探索Flutter混合開發技術方案(下)——淺析Flutter Boost原理
    可以看到這裡是調用了invokeChannelUnsafe方法,最終通過FlutterBoost.instance().channel().invokeMethod調用Plugin層的方法與原生進行交互。
  • 愛奇藝開播助手Flutter跨平臺Hybrid實踐
    愛奇藝開播助手項目,又稱"直播機",該項目目標是通過一個移動平臺為主播提供多樣化的直播內容。
  • 28個頂級的React UI組件庫,請查收!
    也可以在 Bit 中添加這個 Bit Scope(https://bitsrc.io/khan/react-components#components)庫來安裝單個組件。,其組件使用可靠的開發工作流程來構建漂亮而實用的 Web 項目。
  • 手寫React-Router源碼,深入理解其原理
    本文會繼續深入React-Router講講他的源碼,套路還是一樣的,我們先用官方的API實現一個簡單的例子,然後自己手寫這些API來替換官方的並且保持功能不變。gt; <Link to="/">回首頁</Link> </> );}export default Login;這樣我們就完成了一個最簡單的React-Router的應用示例,我們來分析下我們用到了他的哪些API,這些API就是我們今天要手寫的目標,仔細一看,我們好像只用到了幾個組件
  • React 輪播動畫探索
    , ], })); } elseif (this.state.chatBoxTopIndex === 1) { // 展示中,通過 swiper 實例插入幻燈片 this.addBubble(data); } else { // 即將展示,向狀態中插入幻燈片 this.setState((prev
  • 新手入門系列之-React / Vue 應用持續集成Docker 化
    樸素的Dockerfile 首先準備一個有標準運行指令的Web應用,用腳手架creat-react-app或Vue CLI等生成的即可。運行以下命令來構建Docker映像。react-docker 可以替換為你要為鏡像命名的任何值。
  • 【第609期】ReactNative動畫研究與實踐
    他的知乎專欄:https://zhuanlan.zhihu.com/tw1993正文從這開始~本次專題文章的題目為《ReactNative動畫研究與實踐》,既然有研究,那我們就爭取一次將ReactNative動畫相關的內容都說清楚,提出問題-論證問題-解決問題的方式來弄。
  • Flutter項目實戰:編寫一個非常精美的Flutter Todo-List項目
    各位開發者們請扶好你們的秀髮,下面就我來帶領各位參觀參觀這個項目的內部構造第三方庫項目中使用了一些非常優秀的第三方庫,也特別感謝這些開發者們,讓我的發量保持健康下面就是這些控制項的信息控制項說明dio網絡請求shared_preferences本地存儲provider狀態管理test單元測試carousel_slider滑動控制項circle_list環形列表
  • Flutter 開發從 0 到 1(六)Markdown 與代碼高亮
    《Fluuter 開發從 0 到 1》博客詳情是 Markdown,今天就來說說 Flutter Markdown 如何實現的。先劇透下,今天的主角是 flutter_markdown,可以實現從使用簡單的 Markdown 標記格式化的純文本數據創建富文本輸出,包括文本樣式,表格,連結等。
  • 一名 Vue 程式設計師總結的 React 基礎
    5、shouldComponentUpdate該方法通過返回 true 或者 false 來確定是否需要觸發新的渲染。因為渲染觸發最後一道關卡,所以也是性能優化的必爭之地。通過添加判斷條件來阻止不必要的渲染。注意:首次渲染或使用 forceUpdate() 時不會調用該方法。
  • 精通react/vue組件設計之配合React Portals實現一個(Drawer)組件
    通過組件的設計過程,大家會接觸到一個完成健壯的組件設計思路和方法,也能在實現組件的過程逐漸對react/vue的高級知識和技巧有更深的理解和掌握,並且在企業實際工作做遊刃有餘.通過以上需求分析, 是不是覺得一個抽屜組件要實現這麼多功能很複雜呢?
  • FlutterBoost1.0到2.0,我一共做了這幾件事...
    但這也帶來了一些問題:在切換的過程中因需要對老頁面截圖及加載之前截圖圖片等耗時工作,會偶爾出現白屏或者黑屏問題——截圖和加載都在CPU線程上進行,會影響主線程渲染;而且在頁面切換的時候,截圖和加載圖片操作雖然處於降低內存的目的,但會帶來短暫的內存飆漲(見下圖),雖然持續的時間很短,但帶來了OOM的abort的風險。