Flutter: Menggunakan kembali logika keadaan terlalu bertele-tele atau terlalu sulit

Dibuat pada 2 Mar 2020  ·  420Komentar  ·  Sumber: flutter/flutter

.

Terkait dengan diskusi seputar kait #25280

TL;DR: Sulit untuk menggunakan kembali logika State . Kami berakhir dengan metode build kompleks dan sangat bersarang atau harus menyalin-tempel logika di beberapa widget.

Tidak mungkin untuk menggunakan kembali logika seperti itu melalui mixin maupun fungsi.

Masalah

Menggunakan kembali logika State di beberapa StatefulWidget sangat sulit, segera setelah logika itu bergantung pada beberapa siklus hidup.

Contoh tipikal adalah logika membuat TextEditingController (tetapi juga AnimationController , animasi implisit, dan banyak lagi). Logika itu terdiri dari beberapa langkah:

  • mendefinisikan variabel pada State .
    dart TextEditingController controller;
  • membuat pengontrol (biasanya di dalam initState), dengan kemungkinan nilai default:
    dart <strong i="25">@override</strong> void initState() { super.initState(); controller = TextEditingController(text: 'Hello world'); }
  • membuang pengontrol ketika State dibuang:
    dart <strong i="30">@override</strong> void dispose() { controller.dispose(); super.dispose(); }
  • melakukan apa pun yang kita inginkan dengan variabel itu di dalam build .
  • (opsional) ekspos properti itu di debugFillProperties :
    dart void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('controller', controller)); }

Ini, dengan sendirinya, tidak rumit. Masalahnya dimulai ketika kita ingin menskalakan pendekatan itu.
Aplikasi Flutter biasa mungkin memiliki lusinan bidang teks, yang berarti logika ini diduplikasi beberapa kali.

Salin-tempel logika ini di mana saja "berfungsi", tetapi menciptakan kelemahan dalam kode kita:

  • mudah untuk lupa menulis ulang salah satu langkah (seperti lupa menelepon dispose )
  • itu menambahkan banyak kebisingan dalam kode

Masalah Mixin

Upaya pertama untuk memfaktorkan logika ini adalah dengan menggunakan mixin:

mixin TextEditingControllerMixin<T extends StatefulWidget> on State<T> {
  TextEditingController get textEditingController => _textEditingController;
  TextEditingController _textEditingController;

  <strong i="11">@override</strong>
  void initState() {
    super.initState();
    _textEditingController = TextEditingController();
  }

  <strong i="12">@override</strong>
  void dispose() {
    _textEditingController.dispose();
    super.dispose();
  }

  <strong i="13">@override</strong>
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty('textEditingController', textEditingController));
  }
}

Kemudian digunakan cara ini:

class Example extends StatefulWidget {
  <strong i="17">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example>
    with TextEditingControllerMixin<Example> {
  <strong i="18">@override</strong>
  Widget build(BuildContext context) {
    return TextField(
      controller: textEditingController,
    );
  }
}

Tetapi ini memiliki kekurangan yang berbeda:

  • Mixin hanya dapat digunakan sekali per kelas. Jika StatefulWidget membutuhkan beberapa TextEditingController , maka kita tidak dapat menggunakan pendekatan mixin lagi.

  • "Keadaan" yang dideklarasikan oleh mixin mungkin bertentangan dengan mixin lain atau State itu sendiri.
    Lebih khusus lagi, jika dua mixin mendeklarasikan anggota menggunakan nama yang sama, akan ada konflik.
    Skenario terburuk, jika anggota yang berkonflik memiliki tipe yang sama, ini akan gagal secara diam-diam.

Ini membuat mixin tidak ideal dan terlalu berbahaya untuk menjadi solusi yang benar.

Menggunakan pola "pembangun"

Solusi lain mungkin menggunakan pola yang sama dengan StreamBuilder & co.

Kita bisa membuat widget TextEditingControllerBuilder , yang mengatur controller tersebut. Kemudian metode build dapat menggunakannya dengan bebas.

Widget semacam itu biasanya diimplementasikan dengan cara ini:

class TextEditingControllerBuilder extends StatefulWidget {
  const TextEditingControllerBuilder({Key key, this.builder}) : super(key: key);

  final Widget Function(BuildContext, TextEditingController) builder;

  <strong i="12">@override</strong>
  _TextEditingControllerBuilderState createState() =>
      _TextEditingControllerBuilderState();
}

class _TextEditingControllerBuilderState
    extends State<TextEditingControllerBuilder> {
  TextEditingController textEditingController;

  <strong i="13">@override</strong>
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(
        DiagnosticsProperty('textEditingController', textEditingController));
  }

  <strong i="14">@override</strong>
  void dispose() {
    textEditingController.dispose();
    super.dispose();
  }

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    return widget.builder(context, textEditingController);
  }
}

Kemudian digunakan seperti:

class Example extends StatelessWidget {
  <strong i="19">@override</strong>
  Widget build(BuildContext context) {
    return TextEditingControllerBuilder(
      builder: (context, controller) {
        return TextField(
          controller: controller,
        );
      },
    );
  }
}

Ini memecahkan masalah yang dihadapi dengan mixin. Tapi itu menciptakan masalah lain.

  • Penggunaannya sangat bertele-tele. Itu secara efektif 4 baris kode + dua tingkat lekukan untuk satu deklarasi variabel.
    Ini bahkan lebih buruk jika kita ingin menggunakannya berkali-kali. Meskipun kita dapat membuat TextEditingControllerBuilder di dalam yang lain sekali, ini secara drastis mengurangi keterbacaan kode:

    <strong i="28">@override</strong>
    Widget build(BuildContext context) {
    return TextEditingControllerBuilder(
      builder: (context, controller1) {
        return TextEditingControllerBuilder(
          builder: (context, controller2) {
            return Column(
              children: <Widget>[
                TextField(controller: controller1),
                TextField(controller: controller2),
              ],
            );
          },
        );
      },
    );
    }
    

    Itu kode yang sangat menjorok hanya untuk mendeklarasikan dua variabel.

  • Ini menambahkan beberapa overhead karena kami memiliki instance State dan Element .

  • Sulit untuk menggunakan TextEditingController luar build .
    Jika kita ingin siklus hidup State melakukan beberapa operasi pada pengontrol tersebut, maka kita memerlukan GlobalKey untuk mengaksesnya. Sebagai contoh:

    class Example extends StatefulWidget {
    <strong i="43">@override</strong>
    _ExampleState createState() => _ExampleState();
    }
    
    class _ExampleState extends State<Example> {
    final textEditingControllerKey =
        GlobalKey<_TextEditingControllerBuilderState>();
    
    <strong i="44">@override</strong>
    void didUpdateWidget(Example oldWidget) {
      super.didUpdateWidget(oldWidget);
    
      if (something) {
        textEditingControllerKey.currentState.textEditingController.clear();
      }
    }
    
    <strong i="45">@override</strong>
    Widget build(BuildContext context) {
      return TextEditingControllerBuilder(
        key: textEditingControllerKey,
        builder: (context, controller) {
          return TextField(controller: controller);
        },
      );
    }
    }
    
P5 crowd framework passed first triage proposal new feature

Komentar yang paling membantu

Saya akan menambahkan beberapa pemikiran dari perspektif React.
Maaf jika tidak relevan, tetapi saya ingin menjelaskan secara singkat bagaimana pendapat kami tentang Hooks.

Kait jelas merupakan hal yang "menyembunyikan". Atau, tergantung bagaimana Anda melihatnya, enkapsulasi mereka. Secara khusus, mereka merangkum keadaan dan efek lokal (saya pikir "efek" kami adalah hal yang sama dengan "sekali pakai"). "Ketidakjelasan" adalah bahwa mereka secara otomatis melampirkan masa pakai ke Komponen di dalamnya yang mereka panggil.

Implisititas ini tidak melekat dalam model. Anda dapat membayangkan argumen yang secara eksplisit dijalin melalui semua panggilan — dari Komponen itu sendiri di seluruh Kait khusus, hingga ke setiap Kait primitif. Namun dalam praktiknya, kami menemukan itu berisik dan sebenarnya tidak berguna. Jadi kami membuat keadaan global implisit saat ini yang sedang dijalankan. Ini mirip dengan bagaimana throw dalam VM mencari ke atas untuk blok catch alih-alih Anda melewati sekitar errorHandlerFrame dalam kode.

Oke, jadi fungsinya dengan keadaan tersembunyi implisit di dalamnya, sepertinya buruk? Tapi di React, begitu juga Components secara umum. Itulah inti dari Komponen. Itu adalah fungsi yang memiliki masa pakai yang terkait dengannya (yang sesuai dengan posisi di hierarki UI). Alasan Components sendiri bukan footgun sehubungan dengan status adalah karena Anda tidak hanya memanggilnya dari kode acak. Anda memanggil mereka dari Komponen lain. Jadi masa pakai mereka masuk akal karena Anda tetap berada dalam konteks kode UI.

Namun, tidak semua masalah berbentuk komponen. Komponen menggabungkan dua kemampuan: status+efek, dan seumur hidup terikat pada posisi pohon. Tapi kami telah menemukan bahwa kemampuan pertama berguna dengan sendirinya. Sama seperti fungsi yang berguna secara umum karena memungkinkan Anda merangkum kode, kami kekurangan primitif yang memungkinkan kami merangkum (dan menggunakan kembali) bundel status+efek tanpa harus membuat simpul baru di pohon. Demikianlah apa yang dimaksud dengan Hooks. Komponen = Kait + UI yang dikembalikan.

Seperti yang saya sebutkan, fungsi arbitrer yang menyembunyikan keadaan kontekstual itu menakutkan. Inilah sebabnya mengapa kami menegakkan konvensi melalui linter. Kait memiliki "warna" — jika Anda menggunakan Kait, fungsi Anda juga merupakan Kait. Dan linter memberlakukan bahwa hanya Komponen atau Hook lain yang dapat menggunakan Hooks. Ini menghilangkan masalah fungsi arbitrer yang menyembunyikan status UI kontekstual karena sekarang mereka tidak lebih implisit daripada Komponen itu sendiri.

Secara konseptual, kami tidak melihat panggilan Hook sebagai panggilan fungsi biasa. Seperti useState() lebih use State() jika kita memiliki sintaks. Itu akan menjadi fitur bahasa. Anda dapat memodelkan sesuatu seperti Kait dengan Efek Aljabar dalam bahasa yang memiliki pelacakan efek. Jadi dalam pengertian itu, mereka akan menjadi fungsi biasa, tetapi fakta bahwa mereka "menggunakan" Status akan menjadi bagian dari tanda tangan tipe mereka. Kemudian Anda dapat menganggap React itu sendiri sebagai "penangan" untuk efek ini. Bagaimanapun, ini sangat teoretis tetapi saya ingin menunjuk pada karya sebelumnya dalam hal model pemrograman.

Secara praktis, ada beberapa hal di sini. Pertama, perlu dicatat bahwa Hooks bukanlah API "ekstra" untuk Bereaksi. Mereka adalah Bereaksi API untuk menulis Komponen pada saat ini. Saya pikir saya akan setuju bahwa sebagai fitur tambahan mereka tidak akan terlalu menarik. Jadi saya tidak tahu apakah mereka benar-benar masuk akal untuk Flutter yang memiliki paradigma keseluruhan yang bisa dibilang berbeda.

Mengenai apa yang mereka izinkan, saya pikir fitur utamanya adalah kemampuan untuk merangkum logika negara+efektif, dan kemudian menyatukannya seperti yang Anda lakukan dengan komposisi fungsi biasa. Karena primitif dirancang untuk menulis, Anda dapat mengambil beberapa output Hook seperti useState() , meneruskannya sebagai input ke cusom useGesture(state) , lalu meneruskannya sebagai input ke beberapa kustom useSpring(gesture) panggilan yang memberi Anda nilai terhuyung-huyung, dan seterusnya. Masing-masing bagian itu sama sekali tidak menyadari yang lain dan mungkin ditulis oleh orang yang berbeda tetapi mereka menyusun dengan baik bersama-sama karena keadaan dan efek dienkapsulasi dan "melekat" pada Komponen terlampir. Berikut adalah demo kecil dari sesuatu seperti ini, dan sebuah artikel di mana saya secara singkat merangkum apa itu Hooks.

Saya ingin menekankan ini bukan tentang mengurangi boilerplate tetapi tentang kemampuan untuk menyusun pipa logika enkapsulasi stateful secara dinamis. Perhatikan bahwa ini sepenuhnya reaktif — yaitu tidak berjalan sekali, tetapi bereaksi terhadap semua perubahan properti dari waktu ke waktu. Salah satu cara untuk memikirkannya adalah mereka seperti plugin dalam pipa sinyal audio. Sementara saya benar-benar waspada tentang "fungsi yang memiliki ingatan" dalam praktiknya, kami belum menemukan itu menjadi masalah karena mereka benar-benar terisolasi. Faktanya, isolasi itu adalah fitur utama mereka. Itu akan berantakan jika tidak. Jadi setiap kodependensi harus diekspresikan secara eksplisit dengan mengembalikan dan meneruskan nilai ke hal berikutnya dalam rantai. Dan fakta bahwa setiap Hook kustom dapat menambah atau menghapus status atau efek tanpa merusak (atau bahkan memengaruhi) konsumennya adalah fitur penting lainnya dari perspektif perpustakaan pihak ketiga.

Saya tidak tahu apakah ini membantu sama sekali, tetapi semoga ini memberikan perspektif tentang model pemrograman.
Senang menjawab pertanyaan lain.

Semua 420 komentar

cc @dnfield @Hixie
Seperti yang diminta, itulah detail lengkap tentang apa saja masalah yang diselesaikan oleh kait.

Saya khawatir bahwa setiap upaya untuk membuat ini lebih mudah dalam kerangka kerja sebenarnya akan menyembunyikan kompleksitas yang harus dipikirkan pengguna.

Sepertinya beberapa di antaranya dapat dibuat lebih baik untuk penulis perpustakaan jika kami sangat mengetik kelas yang perlu dibuang dengan semacam abstract class Disposable . Dalam kasus seperti itu, Anda harus dapat dengan lebih mudah menulis kelas yang lebih sederhana seperti ini jika Anda ingin:

class AutomaticDisposingState<T> extends State<T> {
  List<Disposable> _disposables;

  void addDisposable(Disposable disposable) {
    assert(!_disposables.contains(disposable));
    _disposables.add(disposable);
  }

  <strong i="8">@override</strong>
  void dispose() {
    for (final Disposable disposable in _disposables)
      disposable.dispose();
    super.dispose();
  }
}

Yang menghilangkan beberapa baris kode yang berulang. Anda bisa menulis kelas abstrak serupa untuk properti debug, dan bahkan yang menggabungkan keduanya. Status init Anda bisa terlihat seperti:

<strong i="12">@override</strong>
void initState() {
  super.initState();
  controller = TextEditingController(text: 'Hello world');
  addDisposable(controller);
  addProperty('controller', controller);
}

Apakah kami hanya melewatkan memberikan informasi pengetikan seperti itu untuk kelas sekali pakai?

Saya khawatir bahwa setiap upaya untuk membuat ini lebih mudah dalam kerangka kerja sebenarnya akan menyembunyikan kompleksitas yang harus dipikirkan pengguna.

Widget menyembunyikan kerumitan yang harus dipikirkan pengguna.
Saya tidak yakin itu benar-benar masalah.

Pada akhirnya terserah pengguna untuk memfaktorkannya sesuai keinginan mereka.


Masalahnya bukan hanya tentang sekali pakai.

Ini melupakan bagian pembaruan dari masalah. Logika panggung juga dapat mengandalkan siklus hidup seperti didChangeDependencies dan didUpdateWidget.

Beberapa contoh konkrit:

  • SingleTickerProviderStateMixin yang memiliki logika di dalam didChangeDependencies .
  • AutomaticKeepAliveClientMixin, yang bergantung pada super.build(context)

Ada banyak contoh dalam kerangka di mana kita ingin menggunakan kembali logika keadaan:

  • pembuat aliran
  • TweenAnimationBuilder
    ...

Ini hanyalah cara untuk menggunakan kembali status dengan mekanisme pembaruan.

Tetapi mereka menderita masalah yang sama seperti yang disebutkan di bagian "pembangun".

Itu menyebabkan banyak masalah.
Misalnya salah satu masalah paling umum di Stackoverflow adalah orang yang mencoba menggunakan StreamBuilder untuk efek samping, seperti "push a route on change".

Dan pada akhirnya satu-satunya solusi mereka adalah "mengeluarkan" StreamBuilder.
Ini melibatkan:

  • mengubah widget menjadi stateful
  • dengarkan streaming secara manual di initState+didUpdateWidget+didChangeDependencies
  • batalkan langganan sebelumnya di didChangeDependencies/didUpdateWidget saat aliran berubah
  • batalkan langganan di buang

Itu _banyak pekerjaan_, dan secara efektif tidak dapat digunakan kembali.

Masalah

Menggunakan kembali logika State di beberapa StatefulWidget sangat sulit, segera setelah logika itu bergantung pada beberapa siklus hidup.

Contoh tipikal adalah logika membuat TextEditingController (tetapi juga AnimationController , animasi implisit, dan banyak lagi). Logika itu terdiri dari beberapa langkah:

  • mendefinisikan variabel pada State .
    dart TextEditingController controller;
  • membuat pengontrol (biasanya di dalam initState), dengan kemungkinan nilai default:
    dart <strong i="19">@override</strong> void initState() { super.initState(); controller = TextEditingController(text: 'Hello world'); }
  • membuang pengontrol ketika State dibuang:
    dart <strong i="24">@override</strong> void dispose() { controller.dispose(); super.dispose(); }
  • melakukan apa pun yang kita inginkan dengan variabel itu di dalam build .
  • (opsional) ekspos properti itu di debugFillProperties :
    dart void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('controller', controller)); }

Ini, dengan sendirinya, tidak rumit. Masalahnya dimulai ketika kita ingin menskalakan pendekatan itu.
Aplikasi Flutter biasa mungkin memiliki lusinan bidang teks, yang berarti logika ini diduplikasi beberapa kali.

Salin-tempel logika ini di mana saja "berfungsi", tetapi menciptakan kelemahan dalam kode kita:

  • mudah untuk lupa menulis ulang salah satu langkah (seperti lupa menelepon dispose )
  • itu menambahkan banyak kebisingan dalam kode

Saya benar-benar kesulitan memahami mengapa ini menjadi masalah. Saya telah menulis banyak aplikasi Flutter tetapi sepertinya tidak terlalu menjadi masalah? Bahkan dalam kasus terburuk, ada empat baris untuk mendeklarasikan properti, menginisialisasi, membuangnya, dan melaporkannya ke data debug (dan sebenarnya biasanya lebih sedikit, karena Anda biasanya dapat mendeklarasikannya pada baris yang sama saat Anda menginisialisasinya, aplikasi umumnya tidak perlu khawatir tentang menambahkan status ke properti debug, dan banyak dari objek ini tidak memiliki status yang perlu dibuang).

Saya setuju bahwa mixin per jenis properti tidak berfungsi. Saya setuju pola builder tidak bagus (secara harfiah menggunakan jumlah baris yang sama dengan skenario terburuk yang dijelaskan di atas).

Dengan NNBD (khususnya dengan late final sehingga penginisialisasi dapat mereferensikan this ) kita dapat melakukan sesuatu seperti ini:

typedef Initializer<T> = T Function();
typedef Disposer<T> = void Function(T value);

mixin StateHelper<T extends StatefulWidget> on State<T> {
  bool _active = false;
  List<Property<Object>> _properties = <Property<Object>>[];

  <strong i="8">@protected</strong>
  void registerProperty<T>(Property<T> property) {
    assert(T != Object);
    assert(T != dynamic);
    assert(!_properties.contains(property));
    _properties.add(property);
    if (_active)
      property._initState();
  }

  <strong i="9">@override</strong>
  void initState() {
    _active = true;
    super.initState();
    for (Property<Object> property in _properties)
      property._initState();
  }

  <strong i="10">@override</strong>
  void dispose() {
    for (Property<Object> property in _properties)
      property._dispose();
    super.dispose();
    _active = false;
  }

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    for (Property<Object> property in _properties)
      property._debugFillProperties(properties);
  }
}

class Property<T> {
  Property(this.owner, this.initializer, this.disposer, [ this.debugName ]) {
    owner.registerProperty(this);
  }

  final StateHelper<StatefulWidget> owner;
  final Initializer<T> initializer;
  final Disposer<T> disposer;
  final String debugName;

  T value;

  void _initState() {
    if (initializer != null)
      value = initializer();
  }

  void _dispose() {
    if (disposer != null)
      disposer(value);
    value = null;
  }

  void _debugFillProperties(DiagnosticPropertiesBuilder properties) {
    properties.add(DiagnosticsProperty(debugName ?? '$T property', value));
  }
}

Anda akan menggunakannya seperti ini:

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  <strong i="14">@override</strong>
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage> {
  late final Property<int> _counter = Property<int>(this, null, null);
  late final Property<TextEditingController> _text = Property<TextEditingController>(this,
    () => TextEditingController(text: 'button'),
    (TextEditingController value) => value.dispose(),
  );

  void _incrementCounter() {
    setState(() {
      _counter.value += 1;
    });
  }

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the ${_text.value.text} this many times:',
            ),
            Text(
              '${_counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
            TextField(
              controller: _text.value,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Tampaknya tidak benar-benar membuat segalanya lebih baik. Masih empat baris.

Widget menyembunyikan kerumitan yang harus dipikirkan pengguna.

Apa yang mereka sembunyikan?

Masalahnya bukanlah jumlah garis, tetapi apa garis-garis ini.

StreamBuilder mungkin berisi sebanyak stream.listen + setState + subscription.close .
Tetapi menulis StreamBuilder dapat dilakukan tanpa melibatkan refleksi apa pun.
Tidak ada kesalahan yang mungkin terjadi dalam prosesnya. Itu hanya "lewati aliran, dan buat widget darinya".

Sedangkan menulis kode secara manual melibatkan lebih banyak pemikiran:

  • Bisakah aliran berubah seiring waktu? Jika kami lupa menanganinya, kami memiliki bug.
  • Apakah kami lupa menutup langganan? Bug lain
  • Nama variabel apa yang saya gunakan untuk berlangganan? Nama itu mungkin tidak tersedia
  • Bagaimana dengan pengujian? Apakah saya harus menduplikasi tes? Dengan StreamBuilder , tidak perlu menulis tes unit untuk mendengarkan streaming, itu akan berlebihan. Tetapi jika kita menulisnya secara manual sepanjang waktu, sangat mungkin untuk membuat kesalahan
  • Jika kita mendengarkan dua aliran sekaligus, sekarang kita memiliki banyak variabel dengan nama yang sangat mirip yang mencemari kode kita, ini dapat menyebabkan kebingungan.

Apa yang mereka sembunyikan?

  • FutureBuilder/StreamBuilder menyembunyikan mekanisme mendengarkan dan melacak Snapshot saat ini.
    Logika beralih di antara dua Future juga cukup rumit, mengingat ia tidak memiliki subscription.close() .
  • AnimatedContainer menyembunyikan logika membuat tween antara nilai sebelumnya dan baru.
  • Listview menyembunyikan logika "pasang widget saat muncul"

aplikasi umumnya tidak perlu khawatir tentang menambahkan status ke properti debug

Mereka tidak melakukannya, karena mereka tidak ingin berurusan dengan kerumitan dalam mempertahankan metode debugFillProperties.
Tetapi jika kami memberi tahu pengembang, "Apakah Anda ingin ini keluar dari kotak, semua parameter dan properti status Anda tersedia di devtool Flutter?" Saya yakin mereka akan mengatakan ya

Banyak orang telah menyatakan kepada saya keinginan mereka untuk benar-benar setara dengan devtool React. Devtool Flutter belum ada.
Di React, kita dapat melihat semua status widget + parameternya, dan mengeditnya, tanpa melakukan apa pun.

Demikian pula, orang-orang cukup terkejut ketika saya memberi tahu mereka bahwa ketika menggunakan provider + beberapa paket saya yang lain, secara default seluruh status aplikasi mereka terlihat oleh mereka, tanpa harus melakukan apa pun ( modulo bug devtool yang mengganggu ini )

Saya harus mengakui bahwa saya bukan penggemar FutureBuilder, itu menyebabkan banyak bug karena orang tidak memikirkan kapan harus memicu Future. Saya pikir tidak masuk akal bagi kami untuk menjatuhkan dukungan untuk itu. StreamBuilder baik-baik saja, saya kira tapi kemudian saya pikir Streams itu sendiri terlalu rumit (seperti yang Anda sebutkan dalam komentar Anda di atas) jadi ...

Mengapa seseorang harus memikirkan kerumitan membuat Tweens?

ListView tidak benar-benar menyembunyikan logika pemasangan widget seperti yang terlihat; itu adalah bagian besar dari API.

Masalahnya bukanlah jumlah garis, tetapi apa garis-garis ini.

Saya benar-benar tidak mengerti kekhawatiran di sini. Garis-garisnya tampak seperti pelat ketel sederhana. Deklarasikan barangnya, inisialisasi barangnya, buang barangnya. Jika bukan jumlah baris, lalu apa masalahnya?

Saya setuju dengan Anda bahwa FutureBuilder bermasalah.

Ini agak di luar topik, tetapi saya menyarankan bahwa dalam pengembangan, Flutter harus memicu hot-reload palsu setiap beberapa detik. Ini akan menyoroti penyalahgunaan FutureBuilder, kunci, dan banyak lagi.

Mengapa seseorang harus memikirkan kerumitan membuat Tweens?

ListView tidak benar-benar menyembunyikan logika pemasangan widget seperti yang terlihat; itu adalah bagian besar dari API.

Kami setuju itu. Maksud saya adalah bahwa kita tidak dapat mengkritik sesuatu seperti kait dengan "itu menyembunyikan logika", karena apa yang dilakukan kait sangat setara dengan apa yang dilakukan TweenAnimationBuilder / AnimatedContainer /....
Logikanya tidak tersembunyi

Pada akhirnya, saya pikir animasi adalah perbandingan yang bagus. Animasi memiliki konsep implisit vs eksplisit ini.
Animasi implisit disukai karena kesederhanaan, komposisi, dan keterbacaannya.
Animasi eksplisit lebih fleksibel, tetapi lebih kompleks.

Saat kami menerjemahkan konsep ini ke mendengarkan aliran, StreamBuilder adalah _pendengaran implisit_, sedangkan stream.listen adalah _eksplisit_.

Lebih khusus lagi, dengan StreamBuilder Anda _tidak_ lupa menangani skenario di mana aliran berubah, atau lupa menutup langganan.
Anda juga dapat menggabungkan beberapa StreamBuilder bersama-sama

stream.listen sedikit lebih maju dan lebih rawan kesalahan.

Pembangun sangat kuat untuk menyederhanakan aplikasi.
Tapi seperti yang kita sepakati sebelumnya, pola Builder tidak ideal. Baik untuk menulis maupun menggunakan.
Masalah ini, dan apa yang dipecahkan oleh kait, adalah tentang sintaks alternatif untuk *Pembangun

Misalnya, flutter_hooks memiliki ekuivalen ketat dengan FutureBuilder dan StreamBuilder :

Widget build(context) {
  final AsyncSnapshot<T> snapshot = useStream(stream);
}

Dalam kelanjutannya, AnimatedContainer & serupa dapat diwakili oleh useAnimatedSize / useAnimatedDecoractedBox / ... sehingga kita memiliki:

double opacity;

Widget build(context) {
  final double animatedOpacity = useAnimatedDouble(opacity, duration: Duration(milliseconds: 200));
  return Opacity(
    opacity: animatedOpacity,
    child: ...,
  );
}

Maksud saya adalah bahwa kita tidak dapat mengkritik sesuatu seperti kait dengan "itu menyembunyikan logika",

Bukan itu argumennya. Argumennya adalah "itu menyembunyikan logika yang harus dipikirkan pengembang ".

Apakah Anda memiliki contoh logika yang harus dipikirkan oleh para pengembang?

Seperti, siapa yang memiliki TextEditingController (siapa yang membuatnya, siapa yang membuangnya).

Suka dengan kode ini?

Widget build(context) {
  final controller = useTextEditingController();
  final focusNode = useFocusNode();
}

Kait menciptakannya dan membuangnya.

Saya tidak yakin apa yang tidak jelas tentang ini.

Iya benar sekali. Saya tidak tahu apa siklus hidup pengontrol dengan kode itu. Apakah itu bertahan sampai akhir lingkup leksikal? Seumur hidup negara? Sesuatu yang lain? Siapa yang memilikinya? Jika saya memberikannya kepada orang lain, dapatkah mereka mengambil alih kepemilikan? Tak satu pun dari ini jelas dalam kode itu sendiri.

Sepertinya argumen Anda lebih disebabkan oleh kurangnya pemahaman tentang apa yang dilakukan kait daripada masalah nyata.
Pertanyaan-pertanyaan ini memiliki jawaban yang jelas yang konsisten dengan semua kait:

Saya tidak tahu apa siklus hidup pengontrol dengan kode itu

Anda juga tidak perlu memikirkannya. Bukan lagi tanggung jawab pengembang.

Apakah itu bertahan sampai akhir lingkup leksikal? Seumur hidup Negara

Seumur hidup Negara

Siapa yang memilikinya?

Hook memiliki pengontrol. Ini adalah bagian dari API useTextEditingController yang memiliki pengontrol.
Ini berlaku untuk useFocusNode , useScrollController , useAnimationController , ...

Di satu sisi, pertanyaan-pertanyaan ini berlaku untuk StreamBuilder :

  • Kami tidak perlu memikirkan siklus hidup StreamSubscription
  • Langganan berlaku seumur hidup Negara
  • StreamBuilder memiliki StreamSubscription

Secara umum, Anda dapat memikirkan:

final value = useX(argument);

sebagai setara ketat dengan:

XBuilder(
  argument: argument,
  builder: (context, value) {

  },
);

Mereka memiliki aturan yang sama dan perilaku yang sama.

Itu bukan lagi tanggung jawab pengembang

Saya pikir pada dasarnya itulah ketidaksepakatan di sini. Memiliki API seperti fungsi yang mengembalikan nilai yang memiliki waktu hidup yang ditentukan yang tidak jelas, IMHO, pada dasarnya sangat berbeda dari API berdasarkan meneruskan nilai itu ke penutupan.

Saya tidak punya masalah dengan seseorang yang membuat paket yang menggunakan gaya ini, tetapi ini adalah gaya yang bertentangan dengan jenis yang ingin saya sertakan dalam API flutter inti.

@Hixie
Saya tidak berpikir apa yang dikatakan @rrousselGit adalah bahwa mereka adalah hal yang sama tetapi hanya bahwa mereka memiliki "aturan yang sama dan perilaku yang sama" mengenai siklus hidup? Benar?

Mereka tidak memecahkan masalah yang sama sekalipun.

Mungkin saya salah di sini, tetapi musim gugur yang lalu ketika mencoba flutter, saya percaya bahwa jika saya membutuhkan tiga pembangun itu dalam satu widget, itu akan banyak bersarang. Dibandingkan dengan tiga kait (tiga baris).
Juga. Kait dapat dikomposisi jadi jika Anda perlu membagikan logika status yang terdiri dari beberapa kait, Anda dapat membuat kait baru yang menggunakan kait lain dan beberapa logika tambahan dan cukup gunakan satu kait baru.

Hal-hal seperti berbagi logika status dengan mudah di antara widget adalah hal yang saya lewatkan ketika mencoba flutter fall tahun 2019.

Tentu saja ada banyak solusi lain yang mungkin. Mungkin sudah diselesaikan dan saya tidak menemukannya di dokumen.
Tetapi jika tidak, ada banyak hal yang dapat dilakukan untuk mempercepat pembangunan jika ada hal seperti kait atau solusi lain untuk masalah yang sama jika tersedia sebagai warga negara kelas satu.

Saya jelas tidak menyarankan menggunakan pendekatan pembangun, seperti yang disebutkan OP, yang memiliki semua jenis masalah. Apa yang saya sarankan adalah hanya menggunakan initState/dispose. Saya tidak begitu mengerti mengapa itu menjadi masalah.

Saya ingin tahu bagaimana perasaan orang tentang kode di https://github.com/flutter/flutter/issues/51752#issuecomment -664787791. Saya tidak berpikir itu lebih baik daripada initState/dispose, tetapi jika orang menyukai kait, apakah mereka juga menyukainya? Apakah kait lebih baik? Lebih buruk?

@Hixie Hooks bagus untuk digunakan karena mereka useAnimationController , saya tidak perlu memikirkan initState dan membuangnya lagi. Ini menghilangkan tanggung jawab dari pengembang. Saya tidak perlu khawatir apakah saya membuang setiap pengontrol animasi yang saya buat.

initState dan dispose baik-baik saja untuk satu hal tetapi bayangkan harus melacak beberapa jenis status yang berbeda. Kait menyusun berdasarkan unit abstraksi logis alih-alih menyebarkannya dalam siklus hidup kelas.

Saya pikir apa yang Anda tanyakan sama dengan menanyakan mengapa memiliki fungsi ketika kami dapat menangani efek secara manual setiap saat. Saya setuju itu tidak persis sama, tetapi secara umum terasa serupa. Tampaknya Anda belum pernah menggunakan kait sebelumnya sehingga masalahnya tidak tampak terlalu jelas bagi Anda, jadi saya akan mendorong Anda untuk melakukan proyek ukuran kecil atau menengah menggunakan kait, dengan paket flutter_hooks mungkin, dan lihat bagaimana rasanya. Saya mengatakan ini dengan segala hormat, sebagai pengguna Flutter saya telah mengalami masalah ini yang hook memberikan solusi, seperti yang lainnya. Saya tidak yakin bagaimana meyakinkan Anda bahwa masalah ini benar-benar ada untuk kami, beri tahu kami jika ada cara yang lebih baik.

Saya akan menambahkan beberapa pemikiran dari perspektif React.
Maaf jika tidak relevan, tetapi saya ingin menjelaskan secara singkat bagaimana pendapat kami tentang Hooks.

Kait jelas merupakan hal yang "menyembunyikan". Atau, tergantung bagaimana Anda melihatnya, enkapsulasi mereka. Secara khusus, mereka merangkum keadaan dan efek lokal (saya pikir "efek" kami adalah hal yang sama dengan "sekali pakai"). "Ketidakjelasan" adalah bahwa mereka secara otomatis melampirkan masa pakai ke Komponen di dalamnya yang mereka panggil.

Implisititas ini tidak melekat dalam model. Anda dapat membayangkan argumen yang secara eksplisit dijalin melalui semua panggilan — dari Komponen itu sendiri di seluruh Kait khusus, hingga ke setiap Kait primitif. Namun dalam praktiknya, kami menemukan itu berisik dan sebenarnya tidak berguna. Jadi kami membuat keadaan global implisit saat ini yang sedang dijalankan. Ini mirip dengan bagaimana throw dalam VM mencari ke atas untuk blok catch alih-alih Anda melewati sekitar errorHandlerFrame dalam kode.

Oke, jadi fungsinya dengan keadaan tersembunyi implisit di dalamnya, sepertinya buruk? Tapi di React, begitu juga Components secara umum. Itulah inti dari Komponen. Itu adalah fungsi yang memiliki masa pakai yang terkait dengannya (yang sesuai dengan posisi di hierarki UI). Alasan Components sendiri bukan footgun sehubungan dengan status adalah karena Anda tidak hanya memanggilnya dari kode acak. Anda memanggil mereka dari Komponen lain. Jadi masa pakai mereka masuk akal karena Anda tetap berada dalam konteks kode UI.

Namun, tidak semua masalah berbentuk komponen. Komponen menggabungkan dua kemampuan: status+efek, dan seumur hidup terikat pada posisi pohon. Tapi kami telah menemukan bahwa kemampuan pertama berguna dengan sendirinya. Sama seperti fungsi yang berguna secara umum karena memungkinkan Anda merangkum kode, kami kekurangan primitif yang memungkinkan kami merangkum (dan menggunakan kembali) bundel status+efek tanpa harus membuat simpul baru di pohon. Demikianlah apa yang dimaksud dengan Hooks. Komponen = Kait + UI yang dikembalikan.

Seperti yang saya sebutkan, fungsi arbitrer yang menyembunyikan keadaan kontekstual itu menakutkan. Inilah sebabnya mengapa kami menegakkan konvensi melalui linter. Kait memiliki "warna" — jika Anda menggunakan Kait, fungsi Anda juga merupakan Kait. Dan linter memberlakukan bahwa hanya Komponen atau Hook lain yang dapat menggunakan Hooks. Ini menghilangkan masalah fungsi arbitrer yang menyembunyikan status UI kontekstual karena sekarang mereka tidak lebih implisit daripada Komponen itu sendiri.

Secara konseptual, kami tidak melihat panggilan Hook sebagai panggilan fungsi biasa. Seperti useState() lebih use State() jika kita memiliki sintaks. Itu akan menjadi fitur bahasa. Anda dapat memodelkan sesuatu seperti Kait dengan Efek Aljabar dalam bahasa yang memiliki pelacakan efek. Jadi dalam pengertian itu, mereka akan menjadi fungsi biasa, tetapi fakta bahwa mereka "menggunakan" Status akan menjadi bagian dari tanda tangan tipe mereka. Kemudian Anda dapat menganggap React itu sendiri sebagai "penangan" untuk efek ini. Bagaimanapun, ini sangat teoretis tetapi saya ingin menunjuk pada karya sebelumnya dalam hal model pemrograman.

Secara praktis, ada beberapa hal di sini. Pertama, perlu dicatat bahwa Hooks bukanlah API "ekstra" untuk Bereaksi. Mereka adalah Bereaksi API untuk menulis Komponen pada saat ini. Saya pikir saya akan setuju bahwa sebagai fitur tambahan mereka tidak akan terlalu menarik. Jadi saya tidak tahu apakah mereka benar-benar masuk akal untuk Flutter yang memiliki paradigma keseluruhan yang bisa dibilang berbeda.

Mengenai apa yang mereka izinkan, saya pikir fitur utamanya adalah kemampuan untuk merangkum logika negara+efektif, dan kemudian menyatukannya seperti yang Anda lakukan dengan komposisi fungsi biasa. Karena primitif dirancang untuk menulis, Anda dapat mengambil beberapa output Hook seperti useState() , meneruskannya sebagai input ke cusom useGesture(state) , lalu meneruskannya sebagai input ke beberapa kustom useSpring(gesture) panggilan yang memberi Anda nilai terhuyung-huyung, dan seterusnya. Masing-masing bagian itu sama sekali tidak menyadari yang lain dan mungkin ditulis oleh orang yang berbeda tetapi mereka menyusun dengan baik bersama-sama karena keadaan dan efek dienkapsulasi dan "melekat" pada Komponen terlampir. Berikut adalah demo kecil dari sesuatu seperti ini, dan sebuah artikel di mana saya secara singkat merangkum apa itu Hooks.

Saya ingin menekankan ini bukan tentang mengurangi boilerplate tetapi tentang kemampuan untuk menyusun pipa logika enkapsulasi stateful secara dinamis. Perhatikan bahwa ini sepenuhnya reaktif — yaitu tidak berjalan sekali, tetapi bereaksi terhadap semua perubahan properti dari waktu ke waktu. Salah satu cara untuk memikirkannya adalah mereka seperti plugin dalam pipa sinyal audio. Sementara saya benar-benar waspada tentang "fungsi yang memiliki ingatan" dalam praktiknya, kami belum menemukan itu menjadi masalah karena mereka benar-benar terisolasi. Faktanya, isolasi itu adalah fitur utama mereka. Itu akan berantakan jika tidak. Jadi setiap kodependensi harus diekspresikan secara eksplisit dengan mengembalikan dan meneruskan nilai ke hal berikutnya dalam rantai. Dan fakta bahwa setiap Hook kustom dapat menambah atau menghapus status atau efek tanpa merusak (atau bahkan memengaruhi) konsumennya adalah fitur penting lainnya dari perspektif perpustakaan pihak ketiga.

Saya tidak tahu apakah ini membantu sama sekali, tetapi semoga ini memberikan perspektif tentang model pemrograman.
Senang menjawab pertanyaan lain.

Saya jelas tidak menyarankan menggunakan pendekatan pembangun, seperti yang disebutkan OP, yang memiliki semua jenis masalah. Apa yang saya sarankan adalah hanya menggunakan initState/dispose. Saya tidak begitu mengerti mengapa itu menjadi masalah.

Saya ingin tahu bagaimana perasaan orang tentang kode di #51752 (komentar) . Saya tidak berpikir itu lebih baik daripada initState/dispose, tetapi jika orang menyukai kait, apakah mereka juga menyukainya? Apakah kait lebih baik? Lebih buruk?

Kata kunci late membuat segalanya lebih baik, tetapi masih mengalami beberapa masalah:

Property mungkin berguna untuk status yang mandiri atau yang tidak bergantung pada parameter yang dapat berubah seiring waktu. Tetapi mungkin sulit untuk digunakan ketika dalam situasi yang berbeda.
Lebih tepatnya, ia tidak memiliki bagian "pembaruan".

Misalnya, dengan StreamBuilder aliran yang didengarkan dapat berubah seiring waktu. Tetapi tidak ada solusi mudah untuk mengimplementasikan hal seperti itu di sini, karena objek hanya diinisialisasi sekali.

Demikian pula, kait memiliki setara dengan Key Widget – yang dapat menyebabkan bagian dari status dihancurkan dan dibuat ulang ketika kunci itu berubah.

Contohnya adalah useMemo , yang merupakan pengait yang menyimpan instance objek.
Dikombinasikan dengan kunci, kita dapat menggunakan useMemo untuk mengambil data implisit.
Misalnya, widget kami mungkin menerima ID pesan – yang kemudian kami gunakan untuk mengambil detail pesan. Namun ID pesan tersebut dapat berubah seiring waktu, jadi kami mungkin perlu mengambil kembali detailnya.

Dengan useMemo , ini mungkin terlihat seperti:

String messageId;

Widget build(context) {
  final Future<Message> message = useMemo(() => fetchMessage(messageId), [messageId]);

}

Dalam situasi ini, bahkan jika metode build dipanggil lagi 10 kali, selama messageId tidak berubah, pengambilan data tidak dilakukan lagi.
Tetapi ketika messageId berubah, Future dibuat.


Perlu dicatat bahwa saya tidak berpikir flutter_hooks dalam keadaan saat ini disempurnakan untuk Dart. Implementasi saya lebih merupakan POC daripada arsitektur yang lengkap.
Tapi saya percaya bahwa kami memiliki masalah dengan penggunaan kembali kode StatefulWidgets.

Saya tidak ingat di mana, tetapi saya ingat menyarankan bahwa kait di dunia ideal akan menjadi generator fungsi khusus, di sebelah async* & sync* , yang mungkin mirip dengan apa yang Dan sarankan dengan use State daripada useState

@gaearon

Saya ingin menekankan ini bukan tentang mengurangi boilerplate tetapi tentang kemampuan untuk menyusun pipa logika enkapsulasi stateful secara dinamis.

Bukan itu masalah yang dibahas di sini. Saya sarankan mengajukan bug terpisah untuk berbicara tentang ketidakmampuan untuk melakukan apa yang Anda gambarkan. (Kedengarannya seperti masalah yang sangat berbeda dan sejujurnya lebih menarik daripada yang dijelaskan di sini.) Bug ini secara khusus tentang bagaimana beberapa logika terlalu bertele-tele.

Tidak, dia benar, itu adalah kata-kata saya yang mungkin membingungkan.
Seperti yang saya sebutkan sebelumnya, ini bukan tentang jumlah baris kode, tetapi baris kode itu sendiri.

Ini tentang memfaktorkan negara.

Bug ini sangat jelas tentang masalah "Menggunakan kembali logika status terlalu bertele-tele/sulit" dan semua tentang bagaimana ada terlalu banyak kode dalam suatu Status ketika Anda memiliki properti yang perlu memiliki kode untuk mendeklarasikannya, di initState, di buang, dan di debugFillProperties. Jika masalah yang Anda pedulikan adalah sesuatu yang berbeda maka saya sarankan mengajukan bug baru yang menjelaskan masalah itu.

Saya sangat, sangat menyarankan untuk melupakan kait (atau solusi apa pun) sampai Anda sepenuhnya memahami masalah yang ingin Anda selesaikan. Hanya dengan memiliki pemahaman yang jelas tentang masalah yang Anda akan dapat mengartikulasikan argumen yang meyakinkan mendukung fitur baru, karena kita harus mengevaluasi fitur terhadap masalah yang mereka pecahkan.

Saya pikir Anda salah paham dengan apa yang saya katakan dalam masalah itu.

Masalahnya bukan boilerplate, tetapi dapat digunakan kembali.

Boilerplate adalah konsekuensi dari masalah penggunaan kembali, bukan penyebabnya

Apa yang dijelaskan oleh masalah ini adalah:

Kami mungkin ingin menggunakan kembali/menyusun logika keadaan. Tetapi opsi yang tersedia adalah mixin, Builder, atau tidak menggunakannya kembali – semuanya memiliki masalah sendiri.

Masalah opsi yang ada mungkin terkait dengan boilerplate, tetapi masalah yang kami coba selesaikan tidak.
Meskipun mengurangi boilerplate Builders adalah satu jalur (yang dilakukan oleh kait), mungkin ada jalur yang berbeda.

Misalnya, sesuatu yang ingin saya sarankan untuk sementara waktu adalah menambahkan metode seperti:

context.onDidChangeDependencies(() {

});
context.onDispose(() {

});

Tetapi ini memiliki masalah mereka sendiri dan tidak sepenuhnya menyelesaikan masalah, jadi saya tidak melakukannya.

@rrousselGit , jangan ragu untuk mengedit pernyataan masalah asli di atas sini untuk lebih mencerminkan masalah. Juga jangan ragu untuk membuat dokumen desain: https://flutter.dev/docs/resources/design-docs yang dapat kita ulangi bersama-sama (sekali lagi, seperti yang disarankan seketat mungkin dari pernyataan masalah ). Saya ingin Anda merasa diberdayakan seperti engineer Flutter lainnya -- Anda adalah bagian dari tim, jadi mari kita ulangi bersama!

Saya telah melihat kembali masalah ini beberapa kali. Sejujurnya, saya tidak mengerti dari mana kesalahpahaman itu berasal, jadi saya tidak yakin apa yang harus diperbaiki.
Komentar asli berulang kali menyebutkan keinginan untuk dapat digunakan kembali/faktorisasi. Penyebutan tentang boilerplate bukan "Flutter bertele-tele" tetapi "Beberapa logika tidak dapat digunakan kembali"

Saya tidak berpikir saran dokumen desain itu adil. Dibutuhkan banyak waktu untuk menulis dokumen seperti itu, dan saya melakukan ini di waktu luang saya.
Saya pribadi puas dengan hook. Saya tidak menulis masalah ini untuk kepentingan saya, tetapi untuk meningkatkan kesadaran tentang masalah yang berdampak pada banyak orang.

Beberapa minggu yang lalu, saya dipekerjakan untuk membahas arsitektur tentang aplikasi Flutter yang ada. Mereka mungkin persis seperti yang disebutkan di sini:

  • Mereka memiliki beberapa logika yang perlu digunakan kembali di beberapa widget (menangani status pemuatan / menandai "pesan" sebagai telah dibaca ketika beberapa widget menjadi terlihat / ...)
  • Mereka mencoba menggunakan mixin, yang menyebabkan kelemahan arsitektur utama.
  • Mereka juga mencoba menangani "buat/perbarui/buang" secara manual dengan menulis ulang logika itu di banyak lokasi, tetapi itu menyebabkan bug.
    Di beberapa tempat, mereka lupa menutup langganan. Di tempat lain, mereka tidak menangani skenario di mana instans stream berubah
  • menandai "pesan" sebagai telah dibaca ketika beberapa widget terlihat

Itu kasus yang menarik karena mirip dengan masalah yang saya alami di salah satu aplikasi saya sendiri, jadi saya melihat bagaimana saya menerapkan kode di sana dan saya benar-benar tidak melihat banyak masalah yang dijelaskan oleh bug ini, yang mungkin menjadi mengapa saya mengalami kesulitan memahami masalah. Ini adalah kode yang dimaksud:

https://github.com/jocosocial/rainbowmonkey/blob/master/lib/src/views/forums.dart

Apakah Anda memiliki contoh aplikasi aktual yang dapat saya pelajari untuk melihat masalah dalam tindakan?

(BTW, secara umum saya sangat menyarankan untuk tidak menggunakan Streams sama sekali. Saya pikir mereka umumnya memperburuk keadaan.)

(BTW, secara umum saya sangat menyarankan untuk tidak menggunakan Streams sama sekali. Saya pikir mereka umumnya memperburuk keadaan.)

(Saya sepenuh hati setuju. Tetapi komunitas saat ini memiliki reaksi sebaliknya. Mungkin mengekstrak ChangeNotifier/Listenable/ValueNotifier dari Flutter ke dalam paket resmi akan membantu)

Apakah Anda memiliki contoh aplikasi aktual yang dapat saya pelajari untuk melihat masalah dalam tindakan?

Sayangnya tidak. Saya hanya bisa berbagi pengalaman yang saya miliki sambil membantu orang lain. Saya tidak memiliki aplikasi di tangan.

Itu kasus yang menarik karena mirip dengan masalah yang saya alami di salah satu aplikasi saya sendiri, jadi saya melihat bagaimana saya menerapkan kode di sana dan saya benar-benar tidak melihat banyak masalah yang dijelaskan oleh bug ini, yang mungkin menjadi mengapa saya mengalami kesulitan memahami masalah. Ini adalah kode yang dimaksud:

Dalam implementasi Anda, logikanya tidak terikat dengan siklus hidup apa pun dan ditempatkan di dalam _build_, jadi itu mengatasi masalah.
Mungkin masuk akal dalam kasus khusus itu. Saya tidak yakin apakah contoh itu bagus.

Contoh yang lebih baik mungkin pull-to-refresh.

Dalam pull-to-refresh yang khas, kita akan menginginkan:

  • pada build pertama, tangani status pemuatan/kesalahan
  • saat menyegarkan:

    • jika layar dalam keadaan error, tampilkan layar loading sekali lagi

    • jika penyegaran dilakukan selama pemuatan, batalkan permintaan HTTP yang tertunda

    • jika layar menunjukkan beberapa data:

    • terus tampilkan data saat status baru sedang dimuat

    • jika penyegaran gagal, tetap tampilkan data yang diperoleh sebelumnya dan tampilkan snackbar dengan kesalahan

    • jika pengguna muncul dan masuk kembali ke layar saat penyegaran tertunda, tampilkan layar pemuatan

    • pastikan RefreshIndicator mengatakan terlihat saat penyegaran tertunda

Dan kami ingin menerapkan fitur seperti itu untuk semua sumber daya dan banyak layar. Selain itu, beberapa layar mungkin ingin menyegarkan banyak sumber daya sekaligus.

ChangeNotifier + provider + StatefulWidget akan mengalami banyak kesulitan dalam memfaktorkan logika ini.

Sedangkan eksperimen terbaru saya (yang berbasis kekekalan & bergantung pada flutter_hooks ) mendukung seluruh spektrum di luar kotak:

final productsProvider = FutureProvider<List<Product>>.autoDispose((ref) async {
  final cancelToken = CancelToken();
  ref.onDispose(cancelToken.cancel);

  return await repository.fetchProducts(cancelToken: cancelToken);
});

// ...

Widget build(context) {
  // Listens to the Future created by productsProvider and handles all the refresh logic
  AsyncValue<List<Product>> products = useRefreshProvider(
    productsProvider,
    // TODO consider making a custom hook to encaplusate the snackbar logic
    onErrorAfterRefresh: (err, stack) => Scaffold.of(context).showSnackBar(...),
  );

  return RefreshIndicator(
    onRefresh: () => context.refresh(productsProvider),
    child: products.when(
      loading: () {
        return const SingleChildScrollView(
          physics: AlwaysScrollableScrollPhysics(),
          child: CircularProgressIndicator(),
        );
      },
      error: (err, stack) {
        return SingleChildScrollView(
          physics: const AlwaysScrollableScrollPhysics(),
          child: Text('Oops, something unexpected happened\n$err'),
        );
      },
      data: (products) {
        return ListView.builder(
          itemCount: products.length,
          itemBuilder: (context, index) {
            return ProductItem(products[index]);
          },
        );
      },
    ),
  );
}

Logika ini sepenuhnya mandiri. Itu dapat digunakan kembali dengan sumber daya apa pun di dalam layar apa pun.

Dan jika satu layar ingin menyegarkan banyak sumber daya sekaligus, kita dapat melakukan:

AsyncValue<First> first = useRefreshProvider(
  firstProvider,
  onErrorAfterRefresh: ...
);
AsyncValue<Second> second = useRefreshProvider(
  secondProvider,
  onErrorAfterRefresh: ...
);

return RefreshIndicator(
  onRefresh: () {
     return Future.wait([context.refesh(firstProvider), context.refresh(secondProvider)]);
  }
  ...
)

Saya akan merekomendasikan menempatkan semua logika itu di status aplikasi, di luar widget, dan hanya memiliki status aplikasi yang mencerminkan status aplikasi saat ini. Tarik untuk menyegarkan tidak memerlukan status di dalam widget, itu hanya harus memberi tahu keadaan sekitar bahwa penyegaran tertunda dan kemudian menunggu masa depannya selesai.

Bukan tanggung jawab keadaan sekitar untuk menentukan cara membuat kesalahan vs memuat vs data

Memiliki logika ini dalam keadaan sekitar tidak menghapus semua logika dari UI
UI masih perlu menentukan apakah akan menampilkan kesalahan di layar penuh atau di snack-bar
Masih perlu memaksa kesalahan untuk disegarkan saat halaman dimuat ulang

Dan ini kurang dapat digunakan kembali.
Jika logika rendering sepenuhnya ditentukan dalam widget daripada keadaan sekitar, maka itu akan bekerja dengan _any_ Futuredan bahkan dapat dimasukkan langsung ke dalam Flutter.

Saya tidak begitu mengerti apa yang Anda anjurkan dalam komentar terakhir Anda. Maksud saya adalah Anda tidak memerlukan perubahan pada kerangka kerja untuk melakukan sesuatu yang sederhana seperti kode indikator penyegaran di atas, seperti yang ditunjukkan oleh kode yang saya kutip sebelumnya.

Jika kita memiliki banyak jenis interaksi ini, tidak hanya untuk indikator penyegaran, tetapi untuk animasi, dan lainnya, lebih baik untuk merangkumnya di tempat yang paling dekat dengan kebutuhan daripada menempatkannya dalam status aplikasi, karena status aplikasi tidak perlu mengetahui secara spesifik setiap interaksi tunggal dalam aplikasi jika tidak diperlukan di banyak tempat dalam aplikasi.

Saya tidak berpikir kami menyetujui kompleksitas fitur dan kegunaannya kembali.
Apakah Anda memiliki contoh yang menunjukkan bahwa fitur tersebut mudah?

Saya menautkan ke sumber satu aplikasi yang saya tulis di atas. Ini tentu saja bukan kode yang sempurna, dan saya berencana untuk menulis ulang sedikit untuk rilis berikutnya, tetapi saya tidak mengalami masalah yang Anda jelaskan dalam masalah ini.

Tapi Anda adalah salah satu pemimpin teknologi Flutter.
Bahkan ketika menghadapi masalah, Anda akan memiliki keterampilan yang cukup untuk segera menemukan solusi.

Namun di sisi lain, sejumlah besar orang tidak mengerti apa yang salah dengan kode berikut:

FutureBuilder<User>(
  future: fetchUser(),
  builder: ...,
)

Fakta ini dibuktikan dengan betapa populernya Q/AI yang dibuat di StackOverflow .

Masalahnya bukan tidak mungkin untuk mengabstraksi logika keadaan dengan cara yang dapat digunakan kembali dan kuat (jika tidak, tidak ada gunanya membuat masalah ini).
Masalahnya adalah bahwa hal itu membutuhkan waktu dan pengalaman untuk melakukannya.

Dengan memberikan solusi resmi, ini mengurangi kemungkinan bahwa aplikasi tidak dapat dipelihara – yang meningkatkan produktivitas dan pengalaman pengembang secara keseluruhan.
Tidak semua orang bisa memberikan saran Properti Anda. Jika hal seperti itu dibangun di dalam Flutter, itu akan didokumentasikan, mendapatkan visibilitas, dan pada akhirnya membantu orang-orang yang tidak pernah memikirkannya sejak awal.

Masalahnya adalah itu benar-benar tergantung pada apa aplikasi Anda, seperti apa keadaan Anda, dan sebagainya. Jika pertanyaannya di sini hanyalah "bagaimana Anda mengelola status aplikasi" maka jawabannya tidak seperti kait, banyak dokumentasi yang berbicara tentang berbagai cara untuk melakukannya dan merekomendasikan teknik yang berbeda untuk situasi yang berbeda... pada dasarnya, kumpulan ini dokumen: https://flutter.dev/docs/development/data-and-backend/state-mgmt

Ada status sementara dan aplikasi, tetapi tampaknya ada kasus penggunaan lain juga: status yang hanya berkaitan dengan satu jenis widget tetapi Anda tetap ingin berbagi di antara jenis widget itu.

Misalnya, ScrollController dapat memanggil beberapa jenis animasi, tetapi tidak selalu tepat untuk memasukkannya ke dalam status aplikasi global, karena bukan data yang perlu digunakan di semua aplikasi. Namun, beberapa ScrollController s mungkin memiliki logika yang sama, dan Anda ingin berbagi logika siklus hidup di antara masing-masing. Statusnya masih hanya ScrollController s, jadi bukan status aplikasi global, tetapi menyalin-menempelkan logika rentan terhadap kesalahan.

Selain itu, Anda mungkin ingin mengemas logika ini agar lebih dapat disusun untuk proyek masa depan Anda, tetapi juga untuk orang lain. Jika Anda melihat situs useHooks , Anda akan melihat banyak potongan logika yang mengelompokkan tindakan umum tertentu. Jika Anda menggunakan useAuth Anda menulisnya sekali dan tidak perlu khawatir apakah Anda melewatkan panggilan initState atau dispose , atau apakah fungsi async memiliki then dan catch . Fungsi ini ditulis hanya sekali sehingga ruang untuk kesalahan pada dasarnya menghilang. Oleh karena itu, solusi semacam ini tidak hanya lebih dapat disusun untuk beberapa bagian dari aplikasi yang sama dan di antara beberapa aplikasi, tetapi juga lebih aman bagi pemrogram akhir.

Saya tidak keberatan dengan orang yang menggunakan kait. Sejauh yang saya tahu, tidak ada yang mencegahnya. (Jika sesuatu _is_ mencegahnya, silakan laporkan bug tentang itu.)

Bug ini bukan tentang kait, ini tentang "Menggunakan kembali logika status terlalu bertele-tele/sulit", dan saya masih berjuang untuk memahami mengapa ini memerlukan perubahan apa pun pada Flutter. Ada banyak contoh (termasuk kait) yang menunjukkan bagaimana menghindari verbositas dengan menyusun aplikasi seseorang dengan satu atau lain cara, dan sudah ada banyak dokumentasi tentangnya.

Saya mengerti, jadi Anda bertanya mengapa, jika ada sesuatu seperti paket hooks yang dibuat tanpa perubahan pada Flutter, perlu ada solusi pihak pertama untuk hooks? Saya kira @rrousselGit dapat menjawab ini dengan lebih baik tetapi jawabannya mungkin melibatkan dukungan yang lebih baik, dukungan yang lebih terintegrasi, dan lebih banyak orang yang menggunakannya.

Saya setuju dengan Anda bahwa selain itu, saya juga bingung mengapa perubahan mendasar perlu dilakukan pada Flutter untuk mendukung kait, karena paket flutter_hooks tampaknya sudah ada.

Saya masih berjuang untuk memahami mengapa ini memerlukan perubahan apa pun pada Flutter.

Mengatakan bahwa masalah ini terpecahkan karena komunitas membuat paket seperti mengatakan bahwa Dart tidak memerlukan kelas data + tipe serikat karena saya membuat Freezed .
Freezed mungkin cukup disukai oleh masyarakat sebagai solusi dari kedua permasalahan tersebut, namun kita masih bisa berbuat lebih baik lagi.

Tim Flutter memiliki lebih banyak pengaruh daripada yang pernah dimiliki komunitas. Anda memiliki kedua kemampuan untuk memodifikasi seluruh tumpukan; orang-orang yang ahli di setiap bagian individu; dan gaji untuk mensponsori pekerjaan yang dibutuhkan.

Masalah ini membutuhkan itu.
Ingat: Salah satu tujuan tim React adalah agar hook menjadi bagian dari bahasa, seperti halnya JSX.

Bahkan tanpa dukungan bahasa, kami masih membutuhkan pekerjaan di penganalisis; papan panah; bergetar/devtools; dan banyak kait untuk menyederhanakan semua hal berbeda yang dilakukan Flutter (seperti untuk animasi implisit, formulir, dan lainnya).

Itu argumen yang bagus, saya setuju, meskipun filosofi umum Flutter memiliki inti yang kecil. Karena alasan itu, kami semakin menambahkan fungsionalitas baru sebagai paket meskipun itu berasal dari Google, karakter lih dan animasi . Itu memberi kita fleksibilitas yang lebih besar untuk belajar dan berubah seiring waktu. Kami akan melakukan hal yang sama untuk ruang ini, kecuali ada alasan teknis yang meyakinkan mengapa sebuah paket tidak mencukupi (dan dengan metode ekstensi, itu bahkan lebih kecil kemungkinannya dari sebelumnya).

Menempatkan hal-hal ke dalam inti Flutter itu rumit. Salah satu tantangannya adalah, seperti yang Anda ketahui dengan baik dari pengalaman langsung, adalah bahwa keadaan adalah area yang berkembang saat kita semua belajar lebih banyak tentang apa yang bekerja dengan baik dalam arsitektur UI reaktif. Dua tahun lalu, jika kami dipaksa untuk memilih pemenang, kami mungkin telah memilih BLoC, tetapi tentu saja paket penyedia Anda mengambil alih dan sekarang menjadi rekomendasi default kami.

Saya dapat dengan nyaman membayangkan kontributor yang dipekerjakan Google yang mendukung flutter_hooks atau paket kait serupa yang memiliki daya tarik (walaupun kami memiliki banyak pekerjaan lain yang bersaing untuk mendapatkan perhatian kami , tentu saja). Secara khusus, kami harus Jika Anda mencari kami untuk mengambil alih dari Anda, itu jelas pertanyaan yang berbeda.

Argumen yang menarik, @timsneath. Komunitas Rust juga melakukan hal serupa, karena begitu diperkenalkan ke dalam inti atau pustaka standar dari suatu bahasa atau kerangka kerja, sangat sulit untuk menghapusnya. Dalam kasus Rust, itu tidak mungkin karena mereka ingin mempertahankan kompatibilitas mundur selamanya. Oleh karena itu, mereka menunggu sampai paket datang dan saling bersaing sampai hanya beberapa pemenang yang muncul, kemudian mereka melipatnya ke dalam bahasa.

Ini bisa menjadi kasus serupa dengan Flutter. Mungkin ada sesuatu yang lebih baik daripada hook di kemudian hari, seperti halnya React harus berpindah dari kelas ke hook tetapi masih harus mempertahankan kelas, dan orang harus bermigrasi. Maka mungkin lebih baik untuk memiliki solusi manajemen negara yang bersaing sebelum ditambahkan ke inti. Dan mungkin kita masyarakat harus berinovasi di atas kait atau mencoba menemukan solusi yang lebih baik.

Saya memahami kekhawatiran itu, tetapi ini bukan tentang solusi manajemen negara.

Fitur tersebut lebih dekat dengan Inheritedwidget & StatefulWidget. Ini adalah primitif tingkat rendah, yang bisa serendah fitur bahasa.

Kait mungkin independen dari kerangka kerja, tetapi itu hanya karena keberuntungan.
Seperti yang saya sebutkan sebelumnya, jalur lain untuk masalah ini mungkin:

context.onDispose(() {

});

Dan pendengar acara serupa.
Tapi itu tidak mungkin untuk diterapkan di luar kerangka kerja.

Saya tidak tahu tim apa yang akan muncul.
Tapi kita tidak bisa mengecualikan kemungkinan bahwa solusi seperti itu harus berada tepat di sebelah Elemen

Apakah ekstensi membantu dengan itu?

(Mungkin kita harus membicarakannya dalam masalah yang berbeda. Ini agak di luar topik di sini. Saya benar-benar lebih suka jika kita memiliki satu masalah per masalah yang dilihat orang, jadi kita bisa mendiskusikan solusi di tempat yang tepat. Bukan jelas bagaimana context.onDispose akan membantu dengan verbositas.)

Saya sangat curiga ada beberapa proposal bahasa yang sangat bagus yang bisa kami buat terkait dengan ini.

Saya pikir akan sangat membantu untuk membicarakannya secara lebih spesifik daripada bagaimana mereka dapat mengaktifkan idiom manajemen negara tertentu. Kami kemudian dapat lebih serius mempertimbangkan apa yang akan mereka aktifkan dan pengorbanan apa yang mungkin mereka dapatkan.

Secara khusus, kami akan dapat mempertimbangkan bagaimana dan apakah mereka dapat bekerja di runtime VM dan JS

Tidak jelas bagaimana context.onDispose akan membantu dengan verbositas.)

Seperti yang saya sebutkan sebelumnya, masalah ini lebih tentang penggunaan kembali kode daripada verbositas. Tetapi jika kita dapat menggunakan kembali lebih banyak kode, ini secara implisit akan mengurangi verbositas.

Cara context.onDispose terkait dengan masalah ini adalah, dengan sintaks saat ini yang kami miliki:

AnimationController controller;

<strong i="11">@override</strong>
void initState() {
  controller = AnimationController(...);
}

<strong i="12">@override</strong>
void dispose() {
  controller.dispose();
}

Masalahnya adalah:

  • ini terkait erat dengan definisi kelas, jadi tidak dapat digunakan kembali
  • seiring bertambahnya widget, hubungan antara inisialisasi dan pembuangan menjadi lebih sulit dibaca karena ada ratusan baris kode di tengahnya.

Dengan context.onDispose , kita dapat melakukan:

<strong i="21">@override</strong>
void initState() {
  controller = AnimationController(...);
  context.onDispose(controller.dispose);
}

Bagian yang menarik adalah:

  • ini tidak lagi digabungkan dengan definisi kelas, sehingga dapat diekstraksi menjadi suatu fungsi.
    Kami secara teoritis dapat memiliki logika semi-kompleks:
    ``` anak panah
    AnimationController someReusableLogic(Konteks BuildContext) {
    pengontrol akhir = AnimationController(...);
    controller.onDispose(controller.dispose);
    controller.maju();
    batal pendengar() {}
    controller.addListener(pendengar);
    context.onDispose(() => controller.removeListener(pendengar));
    }
    ...

@mengesampingkan
batal initState() {
controller = someReusableLogic(konteks);
}
```

  • semua logika dibundel bersama. Bahkan jika widget tumbuh menjadi 300 panjang, logika controller masih mudah dibaca.

Masalah dengan pendekatan ini adalah:

  • context.myLifecycle(() {...}) tidak hot-reloadable
  • tidak jelas bagaimana someReusableLogic membaca properti dari StatefulWidget tanpa menghubungkan fungsi dengan definisi widget.
    Misalnya, AnimationController 's Duration dapat diteruskan sebagai parameter widget. Jadi kita perlu menangani skenario di mana durasinya berubah.
  • tidak jelas bagaimana menerapkan fungsi yang mengembalikan objek yang dapat berubah seiring waktu, tanpa harus menggunakan ValueNotifier dan berurusan dengan pendengar

    • Ini sangat penting untuk keadaan yang dihitung.


Saya akan memikirkan proposal bahasa. Saya punya beberapa ide, tetapi tidak ada yang layak untuk dibicarakan sekarang.

Seperti yang saya sebutkan sebelumnya, masalah ini lebih tentang penggunaan kembali kode daripada verbositas

Oke. Bisakah Anda mengajukan bug baru yang membicarakannya secara khusus? Bug ini secara harfiah disebut "Menggunakan kembali logika keadaan terlalu bertele-tele/sulit". Jika verbositas bukan masalahnya maka _this_ bukan masalahnya.

Dengan context.onDispose , kita dapat melakukan:

<strong i="11">@override</strong>
void initState() {
  controller = AnimationController(...);
  context.onDispose(controller.dispose);
}

Saya tidak yakin mengapa context relevan dalam hal ini (dan onDispose melanggar konvensi penamaan kami). Jika Anda hanya ingin cara mendaftarkan sesuatu untuk dijalankan selama pembuangan, Anda dapat melakukannya dengan mudah hari ini:

mixin StateHelper<T extends StatefulWidget> on State<T> {
  List<VoidCallback> _disposeQueue;

  void queueDispose(VoidCallback callback) {
    _disposeQueue ??= <VoidCallback>[];
    _disposeQueue.add(callback);
  }

  <strong i="17">@override</strong>
  void dispose() {
    if (_disposeQueue != null) {
      for (VoidCallback callback in _disposeQueue)
        callback();
    }
    super.dispose();
  }
}

Sebut saja seperti ini:

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage> {
  TextEditingController controller;

  <strong i="21">@override</strong>
  void initState() {
    super.initState();
    controller = TextEditingController(text: 'button');
    queueDispose(controller.dispose);
  }

  ...
AnimationController someReusableLogic(BuildContext context) {
  final controller = AnimationController(...);
  controller.onDispose(controller.dispose);
  controller.forward();
  void listener() {}
  controller.addListener(listener);
  context.onDispose(() => controller.removeListener(listener));
}
...

<strong i="25">@override</strong>
void initState() {
  controller = someReusableLogic(context);
}

Anda juga dapat melakukannya:

AnimationController someReusableLogic<T extends StatefulWidget>(StateHelper<T> state) {
  final controller = AnimationController(...);
  state.queueDispose(controller.dispose);
  controller.forward();
  void listener() {}
  controller.addListener(listener);
  state.queueDispose(() => controller.removeListener(listener));
  return controller;
}
...

<strong i="6">@override</strong>
void initState() {
  controller = someReusableLogic(this);
}

Masalah dengan pendekatan ini adalah:

  • context.myLifecycle(() {...}) tidak hot-reloadable

Dalam konteks ini sepertinya tidak masalah karena hanya untuk hal-hal yang disebut initState? Apakah saya melewatkan sesuatu?

  • tidak jelas bagaimana someReusableLogic membaca properti dari StatefulWidget tanpa menghubungkan fungsi dengan definisi widget.
    Misalnya, AnimationController 's Duration dapat diteruskan sebagai parameter widget. Jadi kita perlu menangani skenario di mana durasinya berubah.

Cukup mudah untuk menambahkan antrean didChangeWidget seperti antrean buang:

mixin StateHelper<T extends StatefulWidget> on State<T> {
  List<VoidCallback> _disposeQueue;
  List<VoidCallback> _didUpdateWidgetQueue;

  void queueDispose(VoidCallback callback) {
    _disposeQueue ??= <VoidCallback>[];
    _disposeQueue.add(callback);
  }

  void queueDidUpdateWidget(VoidCallback callback) {
    _didUpdateWidgetQueue ??= <VoidCallback>[];
    _didUpdateWidgetQueue.add(callback);
  }

  <strong i="24">@override</strong>
  void didUpdateWidget(T oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (_didUpdateWidgetQueue != null) {
      for (VoidCallback callback in _didUpdateWidgetQueue)
        callback();
    }
  }

  <strong i="25">@override</strong>
  void dispose() {
    if (_disposeQueue != null) {
      for (VoidCallback callback in _disposeQueue)
        callback();
    }
    super.dispose();
  }
}

AnimationController conditionalAnimator(StateHelper state, ValueGetter<bool> isAnimating, VoidCallback listener) {
  final controller = AnimationController(vsync: state as TickerProvider, duration: const Duration(seconds: 1));
  state.queueDispose(controller.dispose);
  controller.addListener(listener);
  state.queueDispose(() => controller.removeListener(listener));
  if (isAnimating())
    controller.repeat();
  state.queueDidUpdateWidget(() {
    if (isAnimating()) {
      controller.repeat();
    } else {
      controller.stop();
    }
  });
  return controller;
}

Digunakan seperti ini:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  <strong i="6">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(animating: false),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.animating}) : super(key: key);

  final bool animating;

  <strong i="7">@override</strong>
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage>, SingleTickerProviderStateMixin {
  AnimationController controller;

  <strong i="8">@override</strong>
  void initState() {
    super.initState();
    controller = conditionalAnimator(this, () => widget.animating, () { print(controller.value); });
  }

  <strong i="9">@override</strong>
  Widget build(BuildContext context) {
    return Center(
      child: FadeTransition(
        opacity: controller,
        child: Text('Hello', style: TextStyle(fontSize: 100.0, color: Colors.white)),
      ),
    );
  }
}
  • tidak jelas bagaimana menerapkan fungsi yang mengembalikan objek yang dapat berubah seiring waktu, tanpa harus menggunakan ValueNotifier dan berurusan dengan pendengar

    • Ini sangat penting untuk keadaan yang dihitung.

Tidak yakin apa artinya ini di sini, apa yang salah dengan ValueNotifier dan, katakanlah, ValueListenableBuilder?

Seperti yang saya sebutkan sebelumnya, masalah ini lebih tentang penggunaan kembali kode daripada verbositas

Oke. Bisakah Anda mengajukan bug baru yang membicarakannya secara khusus? Bug ini secara harfiah disebut "Menggunakan kembali logika keadaan terlalu bertele-tele/sulit". Jika verbositas bukan masalahnya maka ini bukan masalahnya.

Saya mulai merasa tidak nyaman dengan diskusi ini. Saya sudah menjawab poin ini sebelumnya:
Topik masalah ini adalah dapat digunakan kembali, dan verbositas dibahas sebagai konsekuensi dari masalah dapat digunakan kembali; bukan sebagai topik utama.

Hanya ada satu poin di komentar teratas yang menyebutkan verbositas, dan itu dengan StreamBuilder, menargetkan terutama 2 level lekukan.

Saya tidak yakin mengapa konteks relevan dalam [...]. Jika Anda hanya ingin cara mendaftarkan sesuatu untuk dijalankan selama pembuangan, Anda dapat melakukannya dengan mudah hari ini:

Ketika saya membahas context.onDispose , saya menyebutkan secara eksplisit bahwa saya pikir itu bukan solusi yang baik.
Saya menjelaskannya karena Anda bertanya bagaimana hubungannya dengan diskusi.

Adapun mengapa context bukannya StateHelper , itu karena ini lebih fleksibel (seperti bekerja dengan StatelessWidget)

context.myLifecycle(() {...}) tidak hot-reloadable

Dalam konteks ini sepertinya tidak masalah karena hanya untuk hal-hal yang disebut initState? Apakah saya melewatkan sesuatu?

Kami dapat mengubah:

initState() {
  context.myLifecycle(() => print('hello'));
}

ke dalam:

initState() {
  context.myLifecycle(() => print('world'));
}

Ini tidak akan menerapkan perubahan pada panggilan balik myLifecycle .

Tetapi jika kita menggunakan:

myLifecycle() {
  super.myLifecycle();
  print('hello');
}

maka hot-reload akan bekerja.

Tidak yakin apa artinya ini di sini, apa yang salah dengan ValueNotifier dan, katakanlah, ValueListenableBuilder?

Sintaks ini dirancang untuk menghindari keharusan menggunakan Builder, jadi kami kembali ke masalah awal.

Selanjutnya, jika kita benar-benar ingin membuat fungsi kita dapat dikomposisi, alih-alih saran ValueGetter + queueDidUpdateWidget , fungsi harus menggunakan ValueNotifier sebagai parameter:

AnimationController conditionalAnimator(StateHelper state, ValueListenable<bool> isAnimating, VoidCallback listener) {
...
}

karena kita mungkin ingin mendapatkan isAnimating dari tempat selain didUpdateWidget tergantung pada widget yang menggunakan fungsi ini.
Di satu tempat, mungkin didUpdateWidget; di lain mungkin didChangeDependencies; dan di tempat lain mungkin di dalam callback dari stream.listen .

Tapi kemudian kita membutuhkan cara untuk mengubah skenario ini menjadi ValueNotifier dengan mudah dan membuat fungsi kita mendengarkan notifier tersebut.
Jadi kita membuat hidup kita jauh lebih sulit.
Lebih dapat diandalkan dan lebih mudah untuk menggunakan ConditionalAnimatorBuilder daripada pola ini menurut saya.

Adapun mengapa context bukannya StateHelper , itu karena ini lebih fleksibel (seperti bekerja dengan StatelessWidget)

StatelessWidget adalah untuk widget stateless. Intinya adalah mereka tidak akan membuat status, membuang sesuatu, bereaksi pada didUpdateWidget, dll.

Reload panas, ya. Itu sebabnya kami menggunakan metode daripada menempatkan penutupan di initState.

Maaf saya terus mengatakan ini, dan saya mengerti bahwa itu pasti membuat frustrasi, tetapi saya masih tidak mengerti apa masalah yang kami coba selesaikan di sini. Saya pikir itu bertele-tele, per ringkasan bug asli dan sebagian besar deskripsi asli, tetapi saya mengerti bahwa bukan itu. Jadi apa masalahnya? Sepertinya ada banyak keinginan yang saling eksklusif di sini, tersebar di banyak banyak komentar di bug ini:

  • Mendeklarasikan cara membuang sesuatu harus dilakukan di tempat yang sama yang mengalokasikannya...
  • ...dan tempat yang mengalokasikannya hanya perlu dijalankan sekali karena mengalokasikannya...
  • dan perlu bekerja dengan hot reload (yang menurut definisi tidak menjalankan kembali kode yang hanya berjalan sekali)...
  • ...dan itu harus dapat membuat status yang berfungsi dengan widget stateless (yang menurut definisi tidak memiliki status)...
  • ...dan perlu mengaktifkan pengait ke hal-hal seperti didUpdateWidget dan didChangeDependencies...

Tarian berulang yang kita lakukan di sini bukanlah cara yang produktif untuk menyelesaikan sesuatu. Seperti yang telah saya coba katakan sebelumnya, cara terbaik untuk mendapatkan sesuatu di sini adalah dengan menjelaskan masalah yang Anda hadapi dengan cara yang dapat kami pahami, dengan semua kebutuhan yang dijelaskan di satu tempat dan dijelaskan dengan kasus penggunaan. Saya sarankan untuk tidak mencantumkan solusi, terutama solusi yang Anda tahu tidak memenuhi kebutuhan Anda. Pastikan saja kebutuhan yang membuat solusi tersebut tidak sesuai tercantum dalam deskripsi.

Sejujurnya, pada dasarnya bagi saya sepertinya Anda meminta desain kerangka kerja yang sama sekali berbeda. Tidak apa-apa, tapi itu bukan Flutter. Jika kami melakukan kerangka kerja yang berbeda, itu akan menjadi kerangka kerja yang berbeda, dan kami masih memiliki banyak pekerjaan yang harus dilakukan pada kerangka kerja _ini_. Sebenarnya, banyak dari apa yang Anda gambarkan sangat mirip dengan bagaimana Jetpack Compose dirancang. Saya bukan penggemar berat desain itu karena memerlukan sihir kompiler, jadi men-debug apa yang terjadi sangat sulit, tapi mungkin itu lebih sesuai keinginan Anda?

Sepertinya ada banyak keinginan yang saling eksklusif di sini, tersebar di banyak banyak komentar di bug ini:

Mereka tidak saling eksklusif. Hooks melakukan semua ini. Saya tidak akan membahas detailnya karena kami tidak ingin fokus pada solusi, tetapi mereka mencentang semua kotak.

Seperti yang telah saya coba katakan sebelumnya, cara terbaik untuk mendapatkan sesuatu di sini adalah dengan menjelaskan masalah yang Anda hadapi dengan cara yang dapat kami pahami, dengan semua kebutuhan yang dijelaskan di satu tempat dan dijelaskan dengan kasus penggunaan.

Saya masih gagal memahami bagaimana komentar teratas itu gagal melakukan itu.
Tidak jelas bagi saya apa yang tidak jelas bagi orang lain.

Sebenarnya, banyak dari apa yang Anda gambarkan sangat mirip dengan bagaimana Jetpack Compose dirancang. Saya bukan penggemar berat desain itu karena memerlukan sihir kompiler, jadi men-debug apa yang terjadi sangat sulit, tapi mungkin itu lebih sesuai keinginan Anda?

Saya tidak akrab dengannya, tetapi dengan pencarian cepat, saya akan mengatakan _yes_.

Mereka tidak saling eksklusif.

Apakah semua poin-poin yang saya sebutkan di atas bagian dari masalah yang kami coba selesaikan di sini?

tapi mereka mencentang semua kotak

Bisakah Anda membuat daftar kotak?

Saya masih gagal memahami bagaimana komentar teratas itu gagal melakukan itu.

Misalnya, OP secara eksplisit mengatakan bahwa masalahnya adalah tentang StatefulWidgets, tetapi salah satu komentar terbaru tentang masalah ini mengatakan saran tertentu tidak baik karena tidak bekerja dengan StatelessWidgets.

Di OP Anda mengatakan:

Sulit untuk menggunakan kembali logika State . Kami berakhir dengan metode build kompleks dan sangat bersarang atau harus menyalin-tempel logika di beberapa widget.

Maka dari ini saya berasumsi bahwa persyaratannya antara lain:

  • Solusi tidak boleh bersarang terlalu dalam.
  • Solusi tidak boleh memerlukan banyak kode serupa di tempat yang mencoba menambahkan status.

Poin pertama (tentang bersarang) tampaknya baik-baik saja. Jelas tidak mencoba menyarankan bahwa kita harus melakukan hal-hal yang sangat bersarang. (Yang mengatakan, kami mungkin tidak setuju tentang apa yang sangat bersarang; itu tidak didefinisikan di sini. Komentar lain kemudian menyiratkan bahwa pembangun menyebabkan kode yang sangat bersarang, tetapi menurut pengalaman saya, pembuat cukup bagus, seperti yang ditunjukkan dalam kode yang saya kutip sebelumnya.)

Poin kedua sepertinya langsung menjadi syarat agar kita tidak bertele-tele. Tapi kemudian Anda telah menjelaskan beberapa kali bahwa ini bukan tentang verbositas.

Pernyataan berikutnya yang dibuat OP yang menggambarkan masalah adalah:

Menggunakan kembali logika State di beberapa StatefulWidget sangat sulit, segera setelah logika itu bergantung pada beberapa siklus hidup.

Sejujurnya saya tidak begitu tahu apa artinya ini. "Sulit" bagi saya biasanya berarti bahwa sesuatu melibatkan banyak logika rumit yang sulit dipahami, tetapi mengalokasikan, membuang, dan bereaksi terhadap peristiwa siklus hidup sangat sederhana. Pernyataan berikutnya yang memberikan masalah (di sini saya melewatkan contoh yang secara eksplisit digambarkan sebagai "tidak rumit" dan oleh karena itu mungkin bukan deskripsi masalah) adalah:

Masalahnya dimulai ketika kita ingin menskalakan pendekatan itu.

Ini menyarankan kepada saya bahwa dengan "sangat sulit" yang Anda maksud "sangat bertele-tele" dan bahwa kesulitannya berasal dari banyak kemunculan kode yang serupa, karena satu-satunya perbedaan antara contoh "tidak rumit" yang Anda berikan dan "sangat sulit" " hasil penskalaan contoh secara harfiah hanya kode yang sama terjadi berkali-kali (yaitu verbositas, kode boilerplate).

Hal ini selanjutnya didukung oleh pernyataan berikut yang menjelaskan suatu masalah:

Salin-tempel logika ini di mana saja "berfungsi", tetapi menciptakan kelemahan dalam kode kita:

  • mudah untuk lupa menulis ulang salah satu langkah (seperti lupa menelepon dispose )

Jadi agaknya sangat sulit karena verbositas membuatnya mudah untuk membuat kesalahan saat menyalin dan menempelkan kode? Tapi sekali lagi, ketika saya mencoba untuk mengatasi masalah ini, yang akan saya gambarkan sebagai "verbositas", Anda mengatakan masalahnya bukan verbositas.

  • itu menambahkan banyak kebisingan dalam kode

Sekali lagi ini terdengar seperti hanya mengatakan verbositas/boilerplate kepada saya, tetapi sekali lagi Anda telah menjelaskan bahwa bukan itu.

Sisa OP hanya menjelaskan solusi yang tidak Anda sukai, jadi mungkin tidak menjelaskan masalahnya.

Apakah ini menjelaskan bagaimana OP gagal menjelaskan masalahnya? Segala sesuatu di OP yang benar-benar menggambarkan masalah tampaknya menggambarkan verbositas, tetapi setiap kali saya menyarankan bahwa itu masalahnya, Anda mengatakan itu bukan dan ada masalah lain.

Saya pikir kesalahpahaman bermuara pada arti kata.
Sebagai contoh:

itu menambahkan banyak kebisingan dalam kode

Sekali lagi ini terdengar seperti hanya mengatakan verbositas/boilerplate kepada saya, tetapi sekali lagi Anda telah menjelaskan bahwa bukan itu.

Poin ini bukan tentang jumlah controller.dispose() , tetapi nilai yang dibawa oleh baris kode ini kepada pembaca.
Garis itu harus selalu ada dan selalu sama. Dengan demikian, nilainya bagi pembaca hampir nol.

Yang penting bukanlah keberadaan garis ini, tetapi ketidakhadirannya.

Masalahnya adalah, semakin banyak controller.dispose() kita miliki, semakin besar kemungkinan kita melewatkan masalah aktual dalam metode pembuangan kita.
Jika kita memiliki 1 pengontrol dan 0 pembuangan, mudah ditangkap
Jika kita memiliki 100 pengontrol dan 99 pembuangan, sulit untuk menemukan yang hilang.

Kemudian kita memiliki:

Jadi agaknya sangat sulit karena verbositas membuatnya mudah untuk membuat kesalahan saat menyalin dan menempelkan kode? Tapi sekali lagi, ketika saya mencoba untuk mengatasi masalah ini, yang akan saya gambarkan sebagai "verbositas", Anda mengatakan masalahnya bukan verbositas.

Seperti yang saya sebutkan di poin sebelumnya, tidak semua baris kode sama.

Jika kita membandingkan:

+ T state;

<strong i="24">@override</strong>
void initState() {
  super.initState();
+  state = widget.valueNotifier.value;
+  widget.valueNotifier.addListener(_listener);
}

+ void _listener() => seState(() => state = widget.valueNotifier.value);

void dispose() {
+ widget.valueNotifier.removeListener(_listener);
  super.dispose();
}

vs:

+ ValueListenableBuilder<T>(
+   valueListenable: widget.valueNotifier,  
+   builder: (context, value, child) {

+    },
+ );

maka kedua cuplikan ini memiliki jumlah baris yang sama dan melakukan hal yang sama.
Tapi ValueListenableBuilder lebih disukai.

Alasannya adalah, bukan jumlah garis yang penting, tetapi apa garis itu.

Cuplikan pertama memiliki:

  • 1 deklarasi properti
  • 1 deklarasi metode
  • 1 tugas
  • 2 panggilan metode
  • yang semuanya tersebar di 2 siklus hidup yang berbeda. 3 jika kami menyertakan build

Cuplikan kedua memiliki:

  • 1 kelas instantiasi
  • 1 fungsi anonim
  • tidak ada siklus hidup. 1 jika kami menyertakan build

Yang membuat ValueListenableBuilder _simpler_.

Ada juga yang tidak dikatakan oleh baris-baris ini:
ValueListenableBuilder menangani perubahan valueListenable dari waktu ke waktu.
Bahkan dalam skenario di mana widget.valueNotifier tidak berubah seiring waktu saat kita berbicara, tidak ada salahnya.
Suatu hari, pernyataan itu bisa berubah. Dalam hal ini, ValueListenableBuilder dengan anggun menangani perilaku baru, sedangkan, dengan cuplikan pertama, kami sekarang memiliki bug.

Jadi ValueListenableBuilder tidak hanya lebih sederhana, tetapi juga lebih tahan terhadap perubahan kode – untuk jumlah baris yang sama persis.


Dengan itu, saya pikir kita berdua bisa sepakat bahwa ValueListenableBuilder lebih disukai.
Pertanyaannya kemudian, "Mengapa tidak memiliki yang setara dengan ValueListenableBuilder untuk setiap logika status yang dapat digunakan kembali?"

Misalnya, alih-alih:

final controller = TextEditingController(text: 'hello world');
...
controller.dispose();

kita akan memiliki:

TextEditingControllerBuilder(
  initialText: 'hello world',
  builder: (context, controller) {

  },
);

dengan manfaat tambahan bahwa perubahan ke initialText dapat dimuat ulang.

Contoh ini mungkin sedikit sepele, tetapi kita dapat menggunakan prinsip ini untuk logika keadaan yang dapat digunakan kembali yang sedikit lebih maju (seperti ModeratorBuilder ).

Ini "baik" dalam potongan kecil. Tetapi ini menyebabkan beberapa masalah karena kami ingin menskalakan pendekatan:

  • Pembangun kembali ke masalah "terlalu banyak kebisingan".

Misalnya, saya telah melihat beberapa orang mengelola model mereka dengan cara ini:

class User {
  final ValueNotifier<String> name;
  final ValueNotifier<int> age;
  final ValueNotifier<Gender> gender;
}

Namun kemudian, widget mungkin ingin mendengarkan name , age dan gender sekaligus.
Yang berarti kita harus melakukan:

return ValueListenableBuilder<String>(
  valueListenable: user.name,
  builder: (context, userName, _) {
    return ValueListenableBuilder<int>(
      valueListenable: user.age,
      builder: (context, userAge, _) {
        return ValueListenableBuilder<Gender>(
          valueListenable: user.gender,
          builder: (context, userGender, _) {
            return Text('$userGender. $userName ($userAge)');
          },
        );
      },
    );
  },
);

Ini jelas tidak ideal. Kami menghilangkan polusi di dalam initState / dispose untuk mencemari metode build .

(mari kita abaikan Listenable.merge demi contoh. Tidak masalah di sini; ini lebih tentang komposisi)

Jika kami menggunakan Builders secara ekstensif, mudah untuk melihat diri kami dalam skenario yang tepat ini – dan tanpa yang setara dengan Listenable.merge (bukan berarti saya suka konstruktor ini, untuk memulainya )

  • Menulis pembuat kustom itu membosankan

    Tidak ada solusi mudah untuk membuat Builder. Tidak ada alat refactoring yang akan membantu kami di sini – kami tidak bisa hanya "mengekstrak sebagai Builder".
    Selain itu, itu belum tentu intuitif. Membuat Builder khusus bukanlah hal pertama yang akan dipikirkan orang – terutama karena banyak yang akan menentang boilerplate (walaupun saya tidak).

    Orang-orang lebih cenderung membuat solusi manajemen status khusus dan berpotensi berakhir dengan kode yang buruk.

  • Memanipulasi pohon Pembangun itu membosankan

    Katakanlah kita ingin menghapus ValueListenableBuilder dalam contoh sebelumnya atau menambahkan yang baru, itu tidak mudah.
    Kita bisa menghabiskan beberapa menit untuk menghitung () dan {} untuk memahami mengapa kode kita tidak dikompilasi.


Kait ada untuk memecahkan masalah Builder yang baru saja kami sebutkan.

Jika kita memfaktorkan ulang contoh sebelumnya ke kait, kita akan memiliki:

final userName = useValueListenable(user.name);
final useAge = useValueListenable(user.age);
final useGender = useValueListenable(user.gender);

return Text('$userGender. $userName ($userAge)');

Ini identik dengan perilaku sebelumnya, tetapi kode sekarang memiliki lekukan linier.
Yang berarti:

  • kode secara drastis lebih mudah dibaca
  • lebih mudah untuk mengedit. Kita tidak perlu takut (){}; untuk menambahkan baris baru.

Itulah salah satu provider yang disukai. Itu menghapus banyak sarang dengan memperkenalkan MultiProvider .

Demikian pula, sebagai lawan dari pendekatan initState / dispose , kami mendapat manfaat dari hot-reload.
Jika kami menambahkan useValueListenable , perubahan akan segera diterapkan.

Dan tentu saja, kami masih memiliki kemampuan untuk mengekstrak primitif yang dapat digunakan kembali:

String useUserLabel(User user) {
  final userName = useValueListenable(user.name);
  final useAge = useValueListenable(user.age);
  final useGender = useValueListenable(user.gender);

  return '$userGender. $userName ($userAge)';
}

Widget build(context) {
  final label = useUserLabel(user);
  return Text(label);
}

dan perubahan tersebut dapat diotomatisasi dengan extract as function , yang akan berfungsi di sebagian besar skenario.


Apakah itu menjawab pertanyaan Anda?

Tentu. Masalah dengan sesuatu seperti itu adalah bahwa ia tidak memiliki cukup informasi untuk benar-benar melakukan hal yang benar. Sebagai contoh:

Widget build(context) {
  if (random.nextBool())
    final title = useLabel(title);
  final label = useLabel(name);
  return Text(label);
}

...akan menjadi buggy dengan cara yang sangat membingungkan.

Anda dapat mengatasinya dengan sihir kompiler (begitulah Compose melakukannya) tetapi untuk Flutter yang melanggar beberapa keputusan desain mendasar kami. Anda dapat mengatasinya dengan kunci, tetapi kemudian kinerja sangat menurun (karena pencarian variabel berakhir dengan pencarian peta, hash, dan sebagainya), yang bagi Flutter melanggar beberapa tujuan desain dasar kami.

Solusi Properti yang saya sarankan sebelumnya, atau sesuatu yang berasal dari itu, sepertinya menghindari keajaiban kompiler sambil tetap mencapai tujuan yang telah Anda jelaskan memiliki semua kode di satu tempat. Saya tidak begitu mengerti mengapa itu tidak berhasil untuk ini. (Jelas itu akan diperluas untuk juga terhubung ke didChangeDependencies dan seterusnya untuk menjadi solusi lengkap.) (Kami tidak akan memasukkan ini ke dalam kerangka dasar karena akan melanggar persyaratan kinerja kami.)

Justru karena bug yang mungkin terjadi, seperti yang Anda katakan, adalah alasan mengapa kait tidak boleh dipanggil secara kondisional. Lihat dokumen Rules of Hooks dari ReactJS untuk lebih jelasnya. Inti dasarnya adalah karena dalam implementasinya mereka dilacak berdasarkan urutan panggilan, menggunakannya secara kondisional akan merusak urutan panggilan itu dan dengan demikian tidak memungkinkan mereka untuk dilacak dengan benar. Untuk menggunakan hook dengan benar, Anda memanggil mereka di tingkat atas di build tanpa logika kondisional. Dalam versi JS, Anda kembali

const [title, setTitle] = useLabel("title");

Setara Dart bisa serupa, hanya lebih lama karena tidak membongkar seperti yang dilakukan JS:

var titleHook = useLabel("title");
String title = titleHook.property;
Function setTitle = titleHook.setter;

Jika Anda menginginkan logika bersyarat, Anda kemudian dapat memutuskan untuk menggunakan title dalam metode build _setelah memanggilnya di tingkat atas_, karena sekarang urutan panggilan masih dipertahankan. Banyak dari masalah yang Anda angkat telah dijelaskan dalam dokumen kait yang saya tautkan di atas.

Tentu. Dan Anda dapat melakukannya dalam satu paket. Saya hanya mengatakan bahwa persyaratan semacam itu akan melanggar filosofi desain kami, itulah sebabnya kami tidak akan menambahkannya ke kerangka kerja Flutter. (Khususnya, kami mengoptimalkan untuk keterbacaan dan debuggability; memiliki kode yang sepertinya berfungsi tetapi, karena kondisi (yang mungkin tidak jelas dalam kode) terkadang tidak berfungsi, bukanlah sesuatu yang ingin kami dorong atau aktifkan di kerangka inti.)

Perilaku debugging/kondisional tidak menjadi masalah. Itulah mengapa plugin penganalisa itu penting. Plugin semacam itu akan:

  • peringatkan jika suatu fungsi menggunakan pengait tanpa diberi nama useMyFunction
  • peringatkan jika kail digunakan secara kondisional
  • peringatkan jika hook digunakan dalam loop/callback.

Ini mencakup semua kesalahan potensial. Bereaksi membuktikan bahwa ini adalah hal yang layak.

Kemudian kita ditinggalkan dengan manfaat:

  • kode yang lebih mudah dibaca (seperti yang ditunjukkan sebelumnya)
  • lebih baik hot-reload
  • kode yang lebih dapat digunakan kembali/dikomposisi
  • lebih fleksibel – kita dapat dengan mudah membuat status terkomputasi.

Tentang status yang dihitung, kait cukup kuat untuk men-cache instance suatu objek. Ini dapat digunakan untuk membangun kembali widget hanya ketika parameternya berubah.

Misalnya, kita dapat memiliki:

class Example extends HookWidget {
  final int userId;

  Widget build(context) {
    // Calls fetchUser whenever userId changes
    // It is the equivalent to both initState and didUpdateWidget
    final future = useMemo1(() => fetchUser(userId), userId);

    final snapshot = useFuture(future);
    if (!snapshot.hasData)
      return Text('loading');
    return Text(snapshot.data.name);
  }  
}

Kait useMemo semacam itu memungkinkan pengoptimalan kinerja yang mudah dan menangani init + pembaruan secara deklaratif, yang juga menghindari bug.

Ini adalah sesuatu yang terlewatkan oleh proposal Property / context.onDispose .
Mereka sulit digunakan untuk status deklaratif tanpa menghubungkan logika dengan siklus hidup atau memperumit kode dengan ValueNotifier .

Lebih lanjut mengapa proposal ValueGetter tidak praktis:

Kami mungkin ingin melakukan refactor:

final int userId;

Widget build(context) {
  final future = useMemo1(() => fetchUser(userId), userId);

ke dalam:

Widget build(context) {
  final userId = Model.of(context).userId;
  final future = useMemo1(() => fetchUser(userId), userId);

Dengan kait, perubahan ini bekerja dengan sempurna, karena useMemo tidak terikat dengan siklus hidup apa pun.

Tetapi dengan Property + ValueGetter , kita harus mengubah implementasi Property untuk membuat ini berfungsi – yang tidak diinginkan karena kode Property mungkin digunakan kembali di banyak tempat. Jadi kami kehilangan reusability sekali lagi.

FWIW cuplikan ini setara dengan:

class Example extends StatefulWidget {
  final int userId;
  <strong i="45">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<User> future;

  <strong i="46">@override</strong>
  void initState() {
    super.initState();
    future = fetchUser(widget.userId);
  }

  <strong i="47">@override</strong>
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.userId != widget.userId) {
      future = fetchUser(widget.userId);
    }
  }

  <strong i="48">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: future,
      builder: (context, snapshot) {
        if (!snapshot.hasData)
          return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

Saya kira kita harus menemukan solusi yang memecahkan masalah yang sama seperti yang disebutkan @rrousselGit tetapi juga

Mungkin langkah selanjutnya adalah membuat solusi unik untuk Flutter yaitu versi kait kerangka kerja ini, dengan batasan Flutter, sama seperti Vue telah membuat versinya dengan batasan Vue. Saya menggunakan kait React secara teratur dan saya akan mengatakan bahwa hanya memiliki plugin penganalisis terkadang tidak cukup, mungkin harus lebih terintegrasi ke dalam bahasa.

Bagaimanapun, saya tidak berpikir kita akan pernah mencapai konsensus. Sepertinya kami tidak setuju bahkan pada apa yang bisa dibaca

Sebagai pengingat, saya membagikan ini hanya karena saya tahu bahwa komunitas memiliki beberapa masalah dengan masalah ini. Saya pribadi tidak keberatan jika Flutter tidak melakukan apa-apa tentang ini (walaupun menurut saya ini menyedihkan), selama kita memiliki:

  • sistem plugin penganalisis yang tepat
  • kemampuan untuk menggunakan paket di dalam dartpad

Jika Anda ingin mengejar plugin kait, yang sangat saya anjurkan, tetapi mengalami beberapa masalah, maka saya sarankan untuk mengajukan masalah untuk masalah tersebut, dan mengajukan PR untuk memperbaiki masalah tersebut. Kami lebih dari senang untuk bekerja dengan Anda dalam hal itu.

Ini adalah versi baru dari ide Property . Ini menangani didUpdateWidget dan pembuangan (dan dapat dengan mudah dibuat untuk menangani hal-hal lain seperti didChangeDependencies); ini mendukung hot reload (Anda dapat mengubah kode yang mendaftarkan properti dan hot reload, dan itu akan melakukan hal yang benar); itu tipe-aman tanpa perlu tipe eksplisit (bergantung pada inferensi); ia memiliki segalanya di satu tempat kecuali deklarasi dan penggunaan properti, dan kinerjanya harus cukup baik (meskipun tidak sebagus cara yang lebih bertele-tele dalam melakukan sesuatu).

Manajer Properti/Properti:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

typedef InitStateCallback<T> = T Function(T oldValue);
typedef DidUpdateWidgetCallback<T, W extends StatefulWidget> = T Function(W oldWidget);

class Property<T, W extends StatefulWidget> {
  Property({
    T value,
    this.initState,
    this.didUpdateWidget,
    this.dispose,
  }) : _value = value;

  T get value {
    assert(_value != null);
    return _value;
  }
  T _value;

  final InitStateCallback<T> initState;
  void _initState(Property<T, W> oldProperty) {
    if (initState != null)
      _value = initState(oldProperty?.value);
    assert(_value != null);
  }

  final DidUpdateWidgetCallback<T, W> didUpdateWidget;
  void _didUpdateWidget(StatefulWidget oldWidget) {
    if (didUpdateWidget != null) {
      final T newValue = didUpdateWidget(oldWidget);
      if (newValue != null)
        _value = newValue;
    }
  }

  final ValueSetter<T> dispose;
  void _dispose() {
    if (dispose != null)
      dispose(value);
  }
}

mixin PropertyManager<W extends StatefulWidget> on State<W> {
  final Set<Property<Object, W>> _properties = <Property<Object, W>>{};
  bool _ready = false;

  Property<T, W> register<T>(Property<T, W> oldProperty, Property<T, W> property) {
    assert(_ready);
    if (oldProperty != null) {
      assert(_properties.contains(oldProperty));
      _properties.remove(oldProperty);
    }
    assert(property._value == null);
    property._initState(oldProperty);
    _properties.add(property);
    return property;
  }

  <strong i="9">@override</strong>
  void initState() {
    super.initState();
    _ready = true;
    initProperties();
  }

  <strong i="10">@override</strong>
  void reassemble() {
    super.reassemble();
    initProperties();
  }

  <strong i="11">@protected</strong>
  <strong i="12">@mustCallSuper</strong>
  void initProperties() { }

  <strong i="13">@override</strong>
  void didUpdateWidget(W oldWidget) {
    super.didUpdateWidget(oldWidget);
    for (Property<Object, W> property in _properties)
      property._didUpdateWidget(oldWidget);
  }

  <strong i="14">@override</strong>
  void dispose() {
    _ready = false;
    for (Property<Object, W> property in _properties)
      property._dispose();
    super.dispose();
  }
}

Inilah cara Anda menggunakannya:

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'properties.dart';

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

class MyApp extends StatelessWidget {
  <strong i="18">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Example(userId: 1),
    );
  }
}

class User {
  User(this.name);
  final String name;
}

Future<User> fetchUser(int userId) async {
  await Future.delayed(const Duration(seconds: 2));
  return User('user$userId');
}

class Example extends StatefulWidget {
  Example({ Key key, this.userId }): super(key: key);

  final int userId;

  <strong i="19">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with PropertyManager {
  Property future;

  <strong i="20">@override</strong>
  void initProperties() {
    super.initProperties();
    future = register(future, Property(
      initState: (_) => fetchUser(widget.userId),
      didUpdateWidget: (oldWidget) {
        if (oldWidget.userId != widget.userId)
          return fetchUser(widget.userId);
      }
    ));
  }

  <strong i="21">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: future.value,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

Untuk kenyamanan, Anda dapat membuat subkelas Properti yang disiapkan untuk hal-hal seperti AnimationControllers dan sebagainya,

Anda mungkin dapat membuat versi ini yang akan bekerja di metode State.build juga...

Saya berbagi beberapa keraguan yang dibawa oleh
Masalah saya dengan pendekatan paket yang diusulkan @timsneath adalah bahwa kode yang menggunakan Hooks terlihat sangat berbeda dari kode tanpa. Jika mereka tidak memasukkannya ke dalam kanon resmi, kita akan berakhir dengan kode Flutter yang tidak dapat dibaca oleh orang yang hanya mengikuti kanon Flutter.
Jika paket mulai mengimplementasikan hal-hal yang seharusnya menjadi responsbilitas kerangka kerja, kita akan mendapatkan banyak dialek Flutter berbeda yang membuat pembelajaran basis kode baru menjadi sulit. Jadi bagi saya, saya mungkin akan mulai menggunakan kait saat mendapatkan bagian dari Flutter.
Ini seperti pandangan saya saat ini pada paket yang dibekukan. Saya menyukai fungsionalitasnya tetapi kecuali Serikat dan kelas data bukan bagian dari Dart, saya tidak ingin memasukkannya ke dalam basis kode saya karena itu akan mempersulit orang untuk membaca kode saya.

@escamoteur Jadi saya mengerti, apakah Anda menyarankan agar kami mengubah cara kerja widget secara mendasar? Atau apakah Anda menyarankan bahwa harus ada beberapa kemampuan baru yang spesifik? Mengingat bagaimana hal-hal seperti Hooks dan proposal Properti di atas dimungkinkan tanpa perubahan apa pun pada kerangka inti, tidak jelas bagi saya apa yang sebenarnya ingin Anda ubah.

Ini ortogonal dari percakapan tentang perubahan yang diusulkan itu sendiri, tetapi saya pikir apa yang saya dengar dari @escamoteur , @rrousselGit dan lainnya baik di sini maupun di tempat lain adalah bahwa menjadi _in_ kerangka kerja dianggap sebagai cara penting untuk membangun legitimasi tertentu mendekati. Koreksi saya jika Anda tidak setuju.

Saya memahami pemikiran itu -- karena ada banyak hal yang muncul dari kerangka kerja (misalnya DartPad tidak mendukung paket pihak ketiga hari ini, beberapa pelanggan curiga tentang berapa banyak paket yang mereka andalkan setelah dibakar dengan NPM, rasanya lebih 'resmi', dijamin akan maju dengan perubahan seperti null-safety).

Tetapi ada juga biaya yang signifikan untuk dimasukkan: khususnya, ini memperkeras pendekatan dan API. Itu sebabnya kami berdua memegang standar yang sangat tinggi untuk apa yang kami tambahkan, terutama ketika tidak ada kesepakatan bulat (lihat manajemen negara bagian), di mana ada kemungkinan evolusi, atau di mana kami dapat dengan mudah menambahkan sesuatu sebagai sebuah paket.

Saya ingin tahu apakah kita perlu mendokumentasikan filosofi paket-pertama kita, tetapi sekali lagi, _kemana_ ia pergi terpisah dari diskusi tentang _apa_ yang mungkin ingin kita ubah untuk meningkatkan penggunaan kembali logika keadaan.

Kebijakan paket kami didokumentasikan di sini: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#deciding -where-to-put-code

Saya sepenuhnya memahami pendekatan paket-pertama dan setuju bahwa itu adalah hal yang penting.
Tetapi saya juga percaya bahwa beberapa masalah perlu diselesaikan di inti, bukan dengan paket.

Itu sebabnya saya tidak berargumen bahwa provider harus digabungkan di Flutter, tetapi juga percaya bahwa masalah ini menjelaskan masalah yang harus diselesaikan Flutter secara asli (tentu saja tidak dengan kait).

Dengan Penyedia, Flutter mengirimkan primitif bawaan untuk menyelesaikan masalah seperti ini: InheritedWidgets.
Penyedia hanya menambahkan lapisan opini di atas untuk membuatnya "" lebih baik "".

Hook berbeda. Mereka _adalah_ yang primitif. Mereka adalah solusi tingkat rendah yang tidak memiliki pendapat untuk masalah tertentu: Menggunakan kembali logika di berbagai keadaan.
Itu bukan produk akhir, tetapi sesuatu yang diharapkan digunakan orang untuk membuat paket khusus (seperti yang saya lakukan dengan hooks_riverpod )

Akan sangat membantu bagi saya (dalam hal memahami keinginan di sini, dan kebutuhan yang dipenuhi kait dan sebagainya) jika seseorang dapat memberikan ulasan terperinci tentang bagaimana pendekatan Properti yang saya corat-coret di atas dibandingkan dengan kait. (Tujuan saya dengan ide Properti sangat banyak untuk melapisi opini di atas kerangka kerja untuk memecahkan masalah bagaimana menggunakan kembali logika di beberapa negara bagian.)

Saya pikir proposal Properti gagal menyelesaikan tujuan utama dari masalah ini: Logika negara seharusnya tidak peduli dari mana parameter berasal dan dalam situasi apa mereka memperbarui.

Proposal ini meningkatkan keterbacaan sampai batas tertentu dengan mengelompokkan kembali semua logika di satu tempat; tetapi gagal menyelesaikan masalah penggunaan kembali

Lebih khusus lagi, kami tidak dapat mengekstrak:

Property(
  initState: (_) => fetchUser(widget.userId),
  didUpdateWidget: (oldWidget) {
    if (oldWidget.userId != widget.userId)
      return fetchUser(widget.userId);
  }
)

dari _ExampleState dan menggunakannya kembali di widget yang berbeda, karena logika terikat ke Example dan initState + didUpdateWidget

Apa yang akan terlihat seperti dengan kait?

Saya setuju dengan @timsneath setelah melihat sesuatu yang serupa di komunitas Rust. Sangat sulit untuk mengekstrak sesuatu dari inti setelah keluar. Pola BLoC telah ditentukan sebelum penyedia muncul, tetapi sekarang penyedia adalah versi yang disarankan. Mungkin flutter_hooks bisa menjadi versi "diberkati" dengan cara yang sama. Saya mengatakan ini karena di masa depan mungkin ada perbaikan pada kait yang dibuat orang. Bereaksi, setelah memiliki kait sekarang, tidak dapat benar-benar mengubahnya atau melepaskannya. Mereka harus mendukung mereka, seperti halnya komponen kelas, pada dasarnya selamanya, karena mereka berada di inti. Oleh karena itu, saya setuju dengan filosofi paket.

Masalahnya tampaknya adopsi akan rendah dan orang akan menggunakan apa pun yang cocok untuk mereka. Ini dapat diselesaikan seperti yang saya katakan dengan merekomendasikan orang untuk menggunakan flutter_hooks. Ini juga mungkin bukan masalah besar jika kita secara analog melihat berapa banyak solusi manajemen negara yang ada, bahkan jika banyak orang menggunakan penyedia. Saya juga telah mengalami beberapa masalah dan "gagal" dalam kerangka kerja lain yang harus diatasi untuk menciptakan solusi yang unggul untuk logika siklus hidup yang dapat dikomposisi dan digunakan kembali.

Apa yang akan terlihat seperti dengan kait?

Tanpa menggunakan kait primitif apa pun yang dikirimkan oleh React/flutter_hooks, kita dapat memiliki:

class FetchUser extends Hook<AsyncSnapshot<User>> {
  const FetchUser(this.userId);
  final int userId;

  <strong i="8">@override</strong>
  _FetchUserState createState() => _FetchUserState();
}

class _FetchUserState extends HookState<AsyncSnapshot<User>, FetchUser> {
  Future<User> userFuture;

  <strong i="9">@override</strong>
  void initHook() {
    userFuture = fetchUser(hook.userId);
  }  

  void didUpdateHook(FetchUser oldHook) {
    if (oldHook.userId != hook.userId)
      userFuture = fetchUser(hook.userId);
  }


  <strong i="10">@override</strong>
  User build() {
    return useFuture(userFuture);
  }
}

Kemudian digunakan:

class Example extends HookWidget {
  const Example({Key key, this.userId}) : super(key: key);

  final int userId;

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    AsyncSnapshot<User> user = use(FetchUser(userId));

    if (!user.hasData)
      return CircularProgressIndicator();
    return Text(user.data.name);
  }
}

Dalam situasi ini, logika sepenuhnya independen dari Example dan siklus hidup dari StatefulWidget .
Jadi kita bisa menggunakannya kembali di widget lain yang mengelola userId berbeda. Mungkin widget lain itu adalah StatefulWidget yang mengelola userId internal. Mungkin itu akan mendapatkan userId dari InheritedWidget sebagai gantinya.

Sintaks ini harus memperjelas bahwa kait seperti objek State independen dengan siklus hidupnya sendiri.

Sebagai catatan tambahan, satu kelemahan dari pendekatan paket-pertama adalah: Penulis paket cenderung tidak menerbitkan paket yang mengandalkan kait untuk memecahkan masalah.

Misalnya, salah satu masalah umum yang dihadapi pengguna Penyedia adalah, mereka ingin secara otomatis menghapus status penyedia saat tidak lagi digunakan.
Masalahnya, pengguna Provider juga sangat menyukai sintaks context.watch / context.select , berlawanan dengan sintaks Consumer(builder: ...) / Selector(builder:...) verbose.
Tetapi kami tidak dapat memiliki kedua sintaks Nice ini _and_ menyelesaikan masalah yang disebutkan sebelumnya tanpa kait (atau https://github.com/flutter/flutter/pull/33213, yang ditolak).

Masalahnya adalah:
Penyedia tidak dapat bergantung pada flutter_hooks untuk menyelesaikan masalah ini.
Karena seberapa populer Penyedia, tidak masuk akal untuk bergantung pada kait.

Jadi pada akhirnya, saya memilih:

  • Forking Provider (dengan codename Riverpod )
  • secara sukarela kehilangan "Flutter favorite"/rekomendasi Google sebagai akibatnya
  • memecahkan masalah ini (dan beberapa lagi)
  • tambahkan ketergantungan pada kait untuk menawarkan sintaks yang diinginkan orang yang menikmati context.watch .

Saya cukup puas dengan apa yang saya buat, karena menurut saya ini membawa peningkatan yang signifikan atas Penyedia (Itu membuat InheritedWidgets compile-safe).
Tapi cara untuk sampai ke sana meninggalkan saya rasa yang buruk.

Pada dasarnya ada tiga perbedaan sejauh yang saya tahu antara versi kait dan versi Properti:

  • Versi Hooks adalah kode pendukung yang lebih banyak
  • Versi Properti lebih banyak kode boilerplate
  • Versi Hooks memiliki masalah dalam metode build di mana jika Anda memanggil hook dengan urutan yang salah, semuanya menjadi buruk dan tidak ada cara untuk segera melihatnya dari kode.

Apakah kode boilerplate benar-benar masalah besar? Maksud saya, Anda dapat dengan mudah menggunakan kembali Properti sekarang, kodenya ada di satu tempat. Jadi itu benar-benar _only_ argumen verbositas sekarang.

Saya pikir solusi yang baik seharusnya tidak bergantung pada paket lain yang mengetahuinya. Seharusnya tidak masalah apakah itu dalam kerangka atau tidak. Orang yang tidak menggunakannya seharusnya tidak menjadi masalah. Jika orang tidak menggunakannya adalah masalah maka itu, IMHO, adalah bendera merah untuk API.

Maksud saya, Anda dapat dengan mudah menggunakan kembali Properti sekarang, kodenya ada di satu tempat.

Kode berada di satu tempat tidak berarti dapat digunakan kembali.
Maukah Anda membuat widget sekunder yang menggunakan kembali kode yang saat ini berada di dalam _ExampleState di widget yang berbeda?
Dengan twist: widget baru itu harus mengelola ID penggunanya secara internal di dalam Statusnya, sehingga kita memiliki:

class _WhateverState extends State with PropertyManager {
  // may change over time after some setState calls
  int userId;
}

Jika orang tidak menggunakannya adalah masalah maka itu, IMHO, adalah bendera merah untuk API.

Orang tidak menggunakan sesuatu karena tidak resmi tidak berarti bahwa API itu buruk.

Benar-benar sah untuk tidak ingin menambahkan dependensi tambahan karena ini adalah pekerjaan ekstra yang harus dipertahankan (karena versi, lisensi, depresiasi, dan hal-hal lain).
Dari apa yang saya ingat, Flutter memiliki persyaratan untuk memiliki ketergantungan sesedikit mungkin.

Bahkan dengan Penyedia itu sendiri, yang sekarang diterima secara luas dan hampir resmi, saya telah melihat orang-orang berkata "Saya lebih suka menggunakan InheritedWidgets bawaan untuk menghindari penambahan ketergantungan".

Maukah Anda membuat widget sekunder yang menggunakan kembali kode yang saat ini berada di dalam _ExampleState di widget yang berbeda?

Kode yang dimaksud adalah tentang mendapatkan userId dari widget dan meneruskannya ke metode fetchUser. Kode untuk mengelola userId yang berubah secara lokal di objek yang sama akan berbeda. Itu sepertinya baik-baik saja? Saya tidak begitu yakin masalah apa yang Anda coba selesaikan di sini.

Sebagai catatan, saya tidak akan menggunakan Properti untuk melakukan apa yang Anda gambarkan, itu hanya akan terlihat seperti:

class Example extends StatefulWidget {
  Example({ Key key }): super(key: key);

  <strong i="10">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with PropertyManager {
  int _userId;
  Future<User> _future;

  void _setUserId(int newId) {
    if (newId == _userId)
      return;
    setState(() {
      _future = fetchUser(_userId);
    });
  }

  // ...code that uses _setUserId...

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: _future.value,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

Orang tidak menggunakan sesuatu karena tidak resmi tidak berarti bahwa API itu buruk.

Sepakat.

Fakta bahwa orang tidak menggunakan sesuatu itu sendiri menjadi buruk adalah yang berarti API itu buruk. Ketika Anda mengatakan "Penulis paket cenderung tidak menerbitkan paket yang mengandalkan kait untuk menyelesaikan masalah", itu menunjukkan bahwa kait bergantung pada orang lain yang menggunakannya untuk berguna bagi Anda. API yang baik, IMHO, tidak menjadi buruk jika tidak ada orang lain yang mengadopsinya; itu harus bertahan bahkan jika tidak ada orang lain yang mengetahuinya. Misalnya, contoh Property atas tidak bergantung pada paket lain yang menggunakannya agar berguna.

Bahkan dengan Penyedia itu sendiri, yang sekarang diterima secara luas dan hampir resmi, saya telah melihat orang-orang berkata "Saya lebih suka menggunakan InheritedWidgets bawaan untuk menghindari penambahan ketergantungan".

Apa yang salah dengan orang yang lebih suka menggunakan InheritedWidget? Saya tidak ingin memaksakan solusi pada orang. Mereka harus menggunakan apa yang ingin mereka gunakan. Anda benar-benar menggambarkan non-masalah. Solusi untuk orang yang lebih suka menggunakan InheritedWidget adalah menyingkir dan membiarkan mereka menggunakan InheritedWidget.

. API yang baik, IMHO, tidak menjadi buruk jika tidak ada orang lain yang mengadopsinya; itu harus bertahan bahkan jika tidak ada orang lain yang mengetahuinya. Misalnya, contoh Properti di atas tidak bergantung pada paket lain yang menggunakannya agar bermanfaat.

Ada kesalahpahaman.

Masalahnya bukan tentang orang-orang yang tidak menggunakan kail pada umumnya.
Ini tentang Penyedia yang tidak dapat menggunakan kait untuk memperbaiki masalah karena kait tidak resmi sedangkan Penyedia.


Kode untuk mengelola userId yang berubah secara lokal di objek yang sama akan berbeda. Itu sepertinya baik-baik saja? Saya tidak begitu yakin masalah apa yang Anda coba selesaikan di sini.

Sebagai catatan, saya tidak akan menggunakan Properti untuk melakukan apa yang Anda gambarkan, itu hanya akan terlihat seperti:

Ini tidak menjawab pertanyaan. Saya menanyakan ini secara khusus untuk membandingkan penggunaan kembali kode antara kait vs Properti.

Dengan kait, kita dapat menggunakan kembali FetchUser :

class _WhateverState extends State with PropertyManager {
  // may change over time after some setState calls
  int userId;

  Widget build(context) {
    final user = use(FetchUser(userId));
  }
}

Dengan kait, kita dapat menggunakan kembali FetchUser :

Saya tidak mengerti mengapa ini diinginkan. FetchUser tidak memiliki kode yang menarik, itu hanya sebuah adaptor dari Hooks ke fungsi fetchUser . Mengapa tidak langsung menelepon fetchUser ? Kode yang Anda gunakan kembali bukanlah kode yang menarik.

Ini tentang Penyedia yang tidak dapat menggunakan kait untuk memperbaiki masalah karena kait tidak resmi sedangkan Penyedia.

IMHO solusi yang baik untuk masalah penggunaan kembali kode tidak perlu diadopsi oleh Penyedia sama sekali. Mereka akan menjadi konsep yang sepenuhnya ortogonal. Ini adalah sesuatu yang dibicarakan oleh panduan gaya Flutter di bawah judul "hindari melengkapi".

Saya tidak mengerti mengapa ini diinginkan. FetchUser tidak memiliki kode yang menarik, itu hanya adaptor dari Hooks ke fungsi fetchUser. Mengapa tidak langsung memanggil fetchUser? Kode yang Anda gunakan kembali bukanlah kode yang menarik.

Tidak masalah. Kami mencoba untuk mendemonstrasikan dapat digunakan kembali kode. fetchUser bisa apa saja – termasuk ChangeNotifier.addListener misalnya.

Kami dapat memiliki implementasi alternatif yang tidak bergantung pada fetchUser , dan cukup menyediakan API untuk melakukan pengambilan data implisit:

int userId;

Widget build(context) {
  AsyncSnapshot<User> user = use(ImplicitFetcher<User>(userId, fetch: () => fetchUser(userId));
}

IMHO solusi yang baik untuk masalah penggunaan kembali kode tidak perlu diadopsi oleh Penyedia sama sekali. Mereka akan menjadi konsep yang sepenuhnya ortogonal. Ini adalah sesuatu yang dibicarakan oleh panduan gaya Flutter di bawah judul "hindari melengkapi".

Itu sebabnya saya menyebutkan bahwa kait adalah primitif

Sebagai metafora:
package:animations tergantung pada Animation . Tapi itu tidak masalah, karena ini adalah inti primitif.
Lain halnya jika package:animations menggunakan garpu Animation dikelola oleh komunitas

@escamoteur Jadi saya mengerti, apakah Anda menyarankan agar kami mengubah cara kerja widget secara mendasar? Atau apakah Anda menyarankan bahwa harus ada beberapa kemampuan baru yang spesifik? Mengingat bagaimana hal-hal seperti Hooks dan proposal Properti di atas dimungkinkan tanpa perubahan apa pun pada kerangka inti, tidak jelas bagi saya apa yang sebenarnya ingin Anda ubah.

@Hixie tidak, maksud saya adalah bahwa jika kait menjadi lebih populer, kita harus memikirkan untuk memasukkannya ke dalam kerangka kerja dan mengajarkannya kepada semua orang sehingga kita tetap memiliki pemahaman yang sama tentang bagaimana kode Flutter terlihat dan berperilaku.
Saya sangat berbagi kekhawatiran Anda tetapi di sisi lain Widget dengan kait terlihat sangat elegan.
Itu tidak akan melarang untuk melakukan hal-hal seperti sebelumnya.

Itu tidak akan melarang untuk melakukan hal-hal seperti sebelumnya.

Saya pikir itu akan, saya tidak berpikir itu akan menjadi ide yang baik bagi tim Flutter untuk mengatakan "hei, kami sekarang merekomendasikan kait bergetar tetapi Anda masih dapat melakukan hal-hal seperti sebelumnya" orang akan bingung tentang ini. Juga jika tim Flutter merekomendasikan hook di masa mendatang, maka mereka juga harus berhenti menerbitkan kode flutter yang sebenarnya sebagai contoh.

Orang selalu mengikuti "cara resmi" dalam melakukan sesuatu dan menurut saya seharusnya tidak ada dua cara resmi dalam menggunakan Flutter.

Tidak masalah. Kami mencoba untuk mendemonstrasikan dapat digunakan kembali kode. fetchUser bisa apa saja – termasuk ChangeNotifier.addListener misalnya.

Tentu. Itulah fungsi yang baik untuk: mengabstraksi kode. Tapi kita sudah memiliki fungsi. Kode Properti di atas, dan kode _setUserId di atas, menunjukkan bahwa Anda dapat membawa semua kode yang memanggil fungsi-fungsi tersebut ke satu tempat tanpa memerlukan bantuan khusus dari kerangka kerja. Mengapa kita membutuhkan Hooks untuk membungkus panggilan ke fungsi-fungsi itu?

IMHO solusi yang baik untuk masalah penggunaan kembali kode tidak perlu diadopsi oleh Penyedia sama sekali. Mereka akan menjadi konsep yang sepenuhnya ortogonal. Ini adalah sesuatu yang dibicarakan oleh panduan gaya Flutter di bawah judul "hindari melengkapi".

Itu sebabnya saya menyebutkan bahwa kait adalah primitif

Mereka adalah kenyamanan, mereka bukan primitif. Jika mereka primitif, pertanyaan "apa masalahnya" akan lebih mudah dijawab. Anda akan mengatakan "ini adalah hal yang ingin saya lakukan dan saya tidak bisa melakukannya".

Sebagai metafora:
package:animations tergantung pada Animation . Tapi itu tidak masalah, karena ini adalah inti primitif.
Lain halnya jika package:animations menggunakan garpu Animation dikelola oleh komunitas

Hirarki kelas Animasi melakukan sesuatu yang mendasar: ia memperkenalkan ticker dan cara untuk mengontrolnya dan berlangganan ke mereka. Tanpa hierarki kelas Animasi, Anda harus menemukan sesuatu seperti hierarki kelas Animasi untuk melakukan animasi. (Idealnya sesuatu yang lebih baik. Ini bukan pekerjaan terbaik kami.) Hooks tidak memperkenalkan fitur fundamental baru. Itu hanya menyediakan cara untuk menulis kode yang sama secara berbeda. Mungkin kode itu lebih sederhana, atau diperhitungkan secara berbeda dari yang seharusnya, tetapi itu bukan primitif. Anda tidak memerlukan kerangka kerja seperti Hooks untuk menulis kode yang melakukan hal yang sama dengan kode yang menggunakan Hooks.


Pada dasarnya, saya tidak berpikir masalah yang dijelaskan dalam masalah ini adalah sesuatu yang perlu diperbaiki oleh kerangka kerja. Orang yang berbeda akan memiliki kebutuhan yang sangat berbeda tentang cara mengatasinya. Ada banyak cara untuk memperbaikinya, kami telah membahas beberapa di bug ini; beberapa cara sangat sederhana dan dapat ditulis dalam beberapa menit, jadi ini bukanlah masalah yang begitu sulit untuk dipecahkan sehingga memberikan nilai bagi kita untuk memiliki dan memelihara solusinya. Masing-masing proposal memiliki kekuatan dan kelemahan; kelemahannya adalah dalam setiap hal hal-hal yang akan menjadi penghalang bagi seseorang untuk menggunakannya. Bahkan tidak terlalu jelas bahwa semua orang setuju bahwa masalahnya perlu diperbaiki sama sekali.

Kait _are_ primitif
Berikut utas dari Dan: https://twitter.com/dan_abramov/status/1093698629708251136 menjelaskan ini. Beberapa susunan kata berbeda, tetapi logikanya sebagian besar berlaku untuk Flutter karena kesamaan antara React class Components dan Flutter StatefulWidgets

Lebih khusus lagi, Anda dapat menganggap flutter_hooks sebagai mixin State dinamis.

Jika mereka primitif, pertanyaan "apa masalahnya" akan lebih mudah dijawab. Anda akan mengatakan "ini adalah hal yang ingin saya lakukan dan saya tidak bisa melakukannya".

Itu ada di OPnya:

Sulit untuk menggunakan kembali logika Negara. Kami berakhir dengan metode build yang kompleks dan sangat bersarang atau harus menyalin-menempelkan logika di beberapa widget.
Tidak mungkin untuk menggunakan kembali logika seperti itu melalui mixin maupun fungsi.

Mungkin kode itu lebih sederhana, atau diperhitungkan secara berbeda dari yang seharusnya, tetapi itu bukan primitif. Anda tidak memerlukan kerangka kerja seperti Hooks untuk menulis kode yang melakukan hal yang sama dengan kode yang menggunakan Hooks.

Anda tidak perlu kelas untuk menulis program. Tetapi kelas memungkinkan Anda untuk menyusun kode Anda dan memfaktorkannya dengan cara yang berarti.
Dan kelas adalah primitif.

Hal yang sama dengan mixin, yang juga primitif

Kait adalah hal yang sama.

Mengapa kita membutuhkan Hooks untuk membungkus panggilan ke fungsi-fungsi itu?

Karena ketika kita perlu memanggil logika ini bukan di _one_ place tapi di _two_ places.

Tidak mungkin untuk menggunakan kembali logika seperti itu melalui mixin maupun fungsi.

Tolong beri saya contoh konkret di mana hal ini terjadi. Sejauh ini semua contoh yang telah kita pelajari sederhana tanpa kait.

Sejauh ini di utas ini saya belum melihat solusi lain selain kait @rrousselGit yang memecahkan dan membuatnya mudah untuk digunakan kembali dan menyusun logika status.

Memang saya belum melakukan banyak panah dan kepakan akhir-akhir ini jadi saya mungkin kehilangan hal-hal dalam contoh kode solusi properti di atas tetapi, apakah ada solusi? Apa saja opsi hari ini yang tidak memerlukan salin tempel alih-alih digunakan kembali?
Apa jawaban untuk pertanyaan @rrousselGit :

Maukah Anda membuat widget sekunder yang menggunakan kembali kode yang saat ini berada di dalam _ExampleState di widget yang berbeda?
Dengan twist: widget baru itu harus mengelola ID penggunanya secara internal di dalam Statusnya

Jika tidak mungkin untuk menggunakan kembali logika keadaan yang begitu mudah dengan solusi properti di atas, apa saja opsi lainnya?
Apakah jawabannya sederhana karena seharusnya tidak mudah digunakan kembali dalam flutter? Yang benar-benar baik-baik saja tapi IMHO agak sedih.

BTW, Apakah SwiftUI melakukannya dengan cara baru/menginspirasi lainnya? Atau apakah mereka juga tidak memiliki kemampuan penggunaan kembali logika negara yang sama? Saya sendiri belum pernah menggunakan swiftui. Mungkin terlalu berbeda?

Semua Pembangun, pada dasarnya. Builder adalah satu-satunya cara untuk menggunakan kembali status saat ini.
Kait membuat Builder lebih mudah dibaca dan lebih mudah dibuat


Berikut adalah kumpulan kait khusus yang saya atau beberapa klien buat bulan lalu untuk berbagai proyek:

  • useQuery – yang setara dengan ImplicitFetcher hook yang saya berikan sebelumnya tetapi malah membuat kueri GraphQL.
  • useOnResume yang memberikan panggilan balik untuk melakukan tindakan kustom pada AppLifecycleState.resumed tanpa harus
    bersusah payah membuat WidgetsBindingObserver
  • useDebouncedListener yang mendengarkan pendengar (biasanya TextField atau ScrollController), tetapi dengan debounce pada pendengar
  • useAppLinkService yang memungkinkan widget untuk melakukan beberapa logika pada acara khusus yang mirip dengan AppLifecycleState.resumed tetapi dengan aturan bisnis
  • useShrinkUIForKeyboard untuk menangani tampilan keyboard dengan lancar. Ini mengembalikan boolean yang menunjukkan apakah UI harus beradaptasi dengan padding bawah atau tidak (yang didasarkan pada mendengarkan focusNode)
  • useFilter , yang menggabungkan useDebouncedListener dan useState (kait primitif yang mendeklarasikan satu properti) untuk mengekspos filter untuk bilah pencarian.
  • useImplicitlyAnimated<Int/Double/Color/...> – setara dengan TweenAnimationBuilder sebagai pengait

Aplikasi juga menggunakan banyak kait tingkat rendah untuk logika yang berbeda.

Misalnya, alih-alih:

Whatever whatever;

initState() {
  whatever = doSomething(widget.id);
}

didUpdateWidget(oldWidget) {
  if (oldWidget.id != widget.id)
    whatever = doSomething(widget.id);
}

Mereka melakukan:

Widget build(context) {
  final whatever = useUnaryEvent<Whatever, int>(widget.id, (int id) => doSomething(id));
}

Ini menghindari duplikat antara initState / didUpdateWidget / didChangeDependencies .

Mereka juga menggunakan banyak useProvider , dari Riverpod yang seharusnya StreamBuilder / ValueListenableBuilder


Bagian yang penting adalah, widget jarang menggunakan "hanya satu kait".
Misalnya, widget dapat melakukan

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    final filter = useFilter(debounceDuration: const Duration(seconds: 2));
    final userId = useProvider(authProvider).userId;
    final chatId = useProvider(selectedChatProvider);
    final chat = useQuery(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

Ini ringkas dan sangat mudah dibaca (tentu saja dengan asumsi Anda memiliki pengetahuan dasar tentang API).
Semua logika dapat dibaca dari atas ke bawah – tidak ada lompatan dari antara metode untuk memahami kode.
Dan semua kait yang digunakan di sini digunakan kembali di banyak tempat di basis kode

Jika kita melakukan hal yang sama persis tanpa kait, kita akan memiliki:

class ChatScreen extends StatefulWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="20">@override</strong>
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  String filter;
  Timer timer;

  <strong i="21">@override</strong>
  void dispose() {
    timer?.cancel();
    super.dispose();
  }

  <strong i="22">@override</strong>
  Widget build(BuildContext context) {
    return Consumer<Auth>(
      provider: authProvider,
      builder: (context, auth, child) {
        return Consumer<int>(
          provider: selectedChatProvider,
          builder: (context, chatId, child) {
            return GraphQLBuilder<Chat>(
              query: ChatQuery(
                userId: auth.userId,
                chatId: chatId,
                filter: filter.value,
              ),
              builder: (context, chat, child) {
                return Column(
                  children: [
                    Searchbar(
                      onChanged: (value) {
                        timer?.cancel();
                        timer = Timer(const Duration(seconds: 2), () {
                          filter = value;
                        });
                      },
                    ),
                    Expanded(
                      child: ChatList(chat: chat),
                    ),
                  ],
                );
              },
            );
          },
        );
      },
    );
  }
}

Ini secara signifikan kurang dapat dibaca.

  • Kami memiliki 10 level lekukan – 12 jika kami membuat FilterBuilder untuk menggunakan kembali logika filter
  • Logika filter tidak dapat digunakan kembali sebagaimana adanya.

    • kita mungkin lupa untuk membatalkan timer karena kesalahan

  • setengah dari metode build tidak berguna bagi pembaca. Para Pembangun mengalihkan perhatian kita dari hal-hal yang penting
  • Saya kehilangan 5 menit mencoba memahami mengapa kode tidak dapat dikompilasi karena tanda kurung yang hilang

Sebagai pengguna flutter_hooks sendiri, saya akan menyumbangkan pendapat saya. Sebelum menggunakan hook, saya senang dengan Flutter. Saya tidak melihat perlunya sesuatu seperti itu. Setelah membacanya dan menonton video youtube tentangnya, saya masih tidak yakin, itu terlihat keren, tetapi saya membutuhkan beberapa latihan atau contoh untuk benar-benar memotivasi. Tapi kemudian aku menyadari sesuatu. Saya menghindari widget stateful dengan cara apa pun, hanya ada banyak boilerplate yang terlibat, dan melompat-lompat di kelas mencoba menemukan sesuatu. Karena itu saya telah memindahkan sebagian besar status fana saya ke solusi manajemen status bersama dengan status aplikasi lainnya, dan hanya menggunakan widget stateless. Namun, ini menyebabkan logika bisnis bergantung pada Flutter dengan cepat karena ketergantungan untuk mendapatkan Navigator , atau BuildContext untuk mengakses InheritedWidget s / Providers lebih tinggi di pohon. Tidak mengatakan itu adalah pendekatan manajemen negara yang baik, saya tahu itu tidak. Tapi saya melakukan apa saja yang bisa saya lakukan untuk tidak perlu khawatir tentang manajemen negara di UI.

Setelah menggunakan kait sebentar, saya mendapati diri saya jauh lebih produktif, jauh lebih senang menggunakan Flutter, menempatkan status sementara di tempat yang tepat (bersama dengan UI) daripada dengan status aplikasi.

Bagi saya, ini seperti pengumpul sampah untuk status/pengendali fana. Saya tidak harus ingat untuk membuang semua langganan di UI, meskipun saya masih sangat sadar akan fakta bahwa inilah yang dilakukan flutter_hooks untuk saya. Itu juga membuatnya lebih mudah untuk memelihara & memperbaiki kode saya. Berbicara dari menulis ~ 10 aplikasi dalam satu tahun terakhir untuk penelitian dan kesenangan pascasarjana saya.

Seperti yang lain, saya tidak tahu persis apa motivasi utama untuk memasukkannya ke dalam Flutter SDK itu sendiri. Namun, berikut adalah dua pemikiran tentang hal itu.

  1. Kadang-kadang saya akan membuat pengait untuk memudahkan menggunakan paket yang memiliki pengontrol yang perlu diinisialisasi/dibuang. (Misalnya golden_layout , atau zefyr ). Saya percaya bahwa pengguna lain yang menggunakan flutter_hooks akan mendapat manfaat dari paket seperti itu. Namun, sepertinya saya tidak dapat membenarkan penerbitan paket yang secara harfiah berisi 1-3 fungsi. Alternatifnya adalah membuat paket wastafel dapur yang berisi banyak kait untuk berbagai paket yang saya gunakan, saya kemudian dapat menggunakan ketergantungan git, tetapi siapa pun yang menggunakan paket lain + flutter_hooks harus bergantung pada git saya di untuk mendapatkan manfaat (yang kurang dapat ditemukan, dan kemungkinan berisi dependensi pada paket yang tidak mereka pedulikan), atau pada paket yang berisi 3 fungsi atau saya menerbitkan paket taman-sink ke pub.dev. Semua ide tampak konyol, dan tidak terlalu mudah ditemukan. Pengguna lain dari flutter_hooks dapat dengan mudah menyalin dan menempelkan fungsi-fungsi tersebut ke dalam kode mereka atau mencoba mencari tahu sendiri logikanya, tetapi hal itu sama sekali meleset dari tujuan berbagi kode/paket. Fungsi akan jauh lebih baik masuk ke paket asli, dan bukan di beberapa 'paket ekstensi'. Jika flutter_hooks adalah bagian dari kerangka kerja, atau bahkan hanya sebuah paket yang digunakan atau diekspor dari kerangka kerja seperti characters , maka pembuat paket asli kemungkinan besar akan menerima permintaan tarik untuk kait sederhana fungsi, dan kami tidak akan mengacaukan 1-3 paket fungsi.
    Jika flutter_hooks tidak diadopsi oleh Flutter, saya memperkirakan akan ada 1-3 paket fungsi yang mengacaukan hasil pencarian pub.dev. Fakta bahwa paket-paket ini akan sangat kecil membuat saya sangat setuju dengan @rrousselGit bahwa ini adalah primitif. Jika 1228 bintang pada repositori flutter_hooks bukan merupakan indikasi untuk menyelesaikan masalah yang disebutkan oleh @rrousselGit, saya tidak tahu apa itu.

  2. Saya menonton video youtube tentang berkontribusi pada repo Flutter karena saya tertarik melihat apa yang bisa saya lakukan untuk membantu. Saat saya menonton, orang yang membuat video menambahkan properti baru dengan cukup mudah tetapi hampir lupa untuk mengurus pembaruan dispose , didUpdateWidget , dan debugFillProperties . Melihat semua kerumitan widget stateful lagi, dan betapa mudahnya melewatkan sesuatu membuat saya tidak mempercayainya lagi, dan membuat saya tidak begitu bersemangat untuk berkontribusi ke repositori Flutter utama. Tidak mengatakan bahwa itu benar-benar menghalangi saya, saya masih tertarik untuk berkontribusi, tetapi rasanya seperti saya akan membuat kode boilerplate yang sulit dipertahankan & ditinjau. Ini bukan tentang kerumitan penulisan kode, tetapi kerumitan membaca kode dan memverifikasi bahwa Anda telah membuang dan menjaga status fana dengan benar.

Maaf atas tanggapan yang bertele-tele, namun, saya telah melihat masalah ini dari waktu ke waktu, dan agak bingung dengan tanggapan dari tim Flutter. Sepertinya Anda belum meluangkan waktu untuk mencoba aplikasi dua arah, dan lihat sendiri perbedaannya. Saya memahami keinginan untuk tidak mempertahankan ketergantungan tambahan atau mengintegrasikannya terlalu banyak ke dalam kerangka kerja. Namun, bagian inti dari kerangka kerja flutter_hook hanyalah 500 baris kode yang terdokumentasi dengan baik. Sekali lagi, maaf jika ini menyinggung percakapan & saya harap saya tidak menyinggung siapa pun karena memberikan 2 sen saya dan berbicara. Saya tidak berbicara lebih awal karena saya merasa @rrousselGit membuat poin yang sangat bagus dan jelas.

Maaf atas tanggapan yang bertele-tele, namun, saya telah melihat masalah ini dari waktu ke waktu, dan agak bingung dengan tanggapan dari tim Flutter. Sepertinya Anda belum meluangkan waktu untuk mencoba aplikasi dua arah, dan lihat sendiri perbedaannya.

Agar adil, ini adalah utas yang sangat panjang dan pendiri kerangka kerja telah secara aktif berkontribusi beberapa kali setiap hari, dengan beberapa solusi, meminta umpan balik tentang mereka, dan terlibat dengan mereka serta bekerja untuk memahami apa yang diminta. Sejujurnya saya berjuang untuk memikirkan contoh yang lebih jelas tentang seorang pengelola yang membantu.

Saya berharap ini sedikit lebih sabar dengan masalah ini - saya tidak mengerti kait lebih dalam setelah membaca utas ini, selain itu cara lain untuk mengikat Disposables seumur hidup ke suatu Negara. Saya tidak suka pendekatan itu secara gaya, dan saya merasa ada sesuatu yang secara fundamental cacat jika posisinya adalah 'luangkan waktu untuk menulis aplikasi yang sama sekali baru dalam paradigma, maka Anda akan mengerti mengapa itu perlu dimasukkan ke dalam kerangka kerja! ' - seperti yang dicatat oleh insinyur Bereaksi di utas ini, itu benar-benar tidak disarankan untuk Flutter, dan manfaat yang dijelaskan di utas ini kecil dibandingkan dengan biaya jenis pengkabelan ulang yang berarti Anda memerlukan basis kode yang sama sekali baru untuk melihat manfaatnya.

Sejujurnya saya berjuang untuk memikirkan contoh yang lebih jelas tentang seorang pengelola yang membantu.

Sepakat. Saya berterima kasih kepada Hixie untuk meluangkan waktu untuk berpartisipasi dalam diskusi ini.

saya tidak mengerti hook lebih dalam setelah membaca utas ini

Agar adil, masalah ini secara eksplisit berusaha menghindari pembicaraan tentang kait secara khusus.
Ini lebih tentang mencoba menjelaskan masalahnya daripada solusinya

Apakah Anda merasa gagal melakukannya?

Saya dapat merasakan kedua sisi ( @rrousselGit dan @Hixie) di sini dan ingin memberikan umpan balik dari sisi penggunaan/perspektif (saya) dari framework Flutter.

Pendekatan flutter_hooks memang mengurangi boilerplate cukup banyak (hanya dari contoh yang ditunjukkan di sini karena kita dapat menggunakan kembali konfigurasi status tersebut) dan mengurangi kerumitan dengan tidak harus secara aktif memikirkan tentang inisialisasi/pembuangan sumber daya. Secara umum itu melakukan pekerjaan yang baik untuk meningkatkan dan mendukung aliran / kecepatan pengembangan ... meskipun itu tidak cocok dengan "inti" Flutter itu sendiri (secara subjektif).

Seperti setidaknya> 95% dari kode yang saya tulis, hasil dalam metode build hanya bersifat deklaratif, tidak ada variabel lokal atau panggilan di luar subpohon widget yang dikembalikan, semua bagian logika ada di dalam fungsi status tersebut untuk menginisialisasi, menetapkan, dan membuang sumber daya dan menambahkan pendengar (dalam kasus saya reaksi MobX) dan hal-hal logis semacam itu. Karena ini juga merupakan pendekatan untuk sebagian besar di Flutter itu sendiri, rasanya sangat asli. Melakukannya juga memberi Anda kesempatan kepada pengembang untuk selalu eksplisit dan terbuka tentang apa yang Anda lakukan - itu memaksa saya untuk selalu mengonversi widget tersebut menjadi StatefulWidget dan menulis kode serupa di initState/dispos, tetapi juga selalu menghasilkan tulisan persis apa yang ingin Anda lakukan secara langsung di Widget yang sedang digunakan. Bagi saya pribadi, seperti @Hixie sudah menyebutkan dirinya sendiri, itu tidak mengganggu saya dengan cara apa pun menulis kode boilerplate semacam ini dan memungkinkan saya sebagai pengembang untuk memutuskan bagaimana menanganinya dengan benar alih-alih mengandalkan sesuatu seperti flutter_hooks untuk melakukannya untuk saya dan mengakibatkan tidak mengerti mengapa sesuatu mungkin berperilaku seperti itu. Mengekstrak widget dalam bit kecil juga memastikan bahwa jenis boilerplate tersebut tepat pada kasus penggunaan yang digunakannya. Dengan flutter_hooks saya masih perlu memikirkan status seperti apa yang layak ditulis untuk menjadi sebuah kait dan oleh karena itu digunakan kembali - rasa yang berbeda mungkin menghasilkan berbagai kait penggunaan "tunggal" atau tidak ada kait sama sekali karena saya mungkin tidak terlalu sering menggunakan kembali konfigurasi tetapi cenderung menulis lebih banyak yang khusus.

Jangan salah paham, pendekatan dalam kait seperti itu tampaknya sangat bagus dan bermanfaat, tetapi bagi saya itu terasa seperti konsep yang sangat mendasar yang mengubah konsep inti tentang cara menangani ini. Rasanya sangat bagus sebagai paket itu sendiri untuk memberi para pengembang kesempatan untuk menggunakan pendekatan semacam ini jika mereka tidak senang dengan cara melakukannya "asli", tetapi menjadikannya bagian dari kerangka kerja Flutter itu sendiri akan, setidaknya menjadi bersih / terpadu, hasilkan penulisan ulang sebagian besar Flutter untuk memanfaatkan konsep ini (banyak pekerjaan) atau menggunakannya untuk hal-hal yang akan datang/dipilih (yang mungkin membingungkan karena memiliki pendekatan campuran seperti itu).

Jika itu akan diintegrasikan ke dalam kerangka Flutter itu sendiri dan didukung/digunakan secara aktif, saya jelas akan masuk ke ini. Karena saya memahami dan bahkan menyukai pendekatan saat ini dan melihat (kemungkinan) tindakan yang diperlukan untuk mengimplementasikan ini secara asli, saya dapat memahami keraguan dan/atau mengapa itu tidak boleh dilakukan dan lebih baik menyimpannya sebagai satu paket.

Perbaiki saya jika saya salah tetapi utas ini adalah tentang masalah menggunakan kembali logika keadaan di banyak widget dengan cara yang dapat dibaca dan disusun. Bukan kait secara khusus. Saya percaya utas ini dibuka karena keinginan untuk berdiskusi seputar masalah dengan pendekatan terbuka untuk solusi apa yang seharusnya.

Kait disebutkan karena mereka adalah salah satu solusi dan saya percaya @rrousselGit telah menggunakannya di sini untuk mencoba dan menjelaskan masalah/masalah yang mereka selesaikan (karena itu adalah solusi) sehingga solusi lain yang mungkin lebih asli dari flutter dapat muncul dengan dan disajikan. Sejauh pengetahuan saya, belum ada solusi lain yang disajikan di utas ini yang menyelesaikan masalah penggunaan kembali?

Dengan itu, saya tidak tahu ke mana arah utas saat ini.
Saya pikir masalah itu benar-benar ada. Atau kita sedang memperdebatkan ini?
Jika kita semua setuju bahwa sulit untuk menggunakan kembali logika keadaan dengan cara yang dapat disusun di banyak widget dengan inti flutter hari ini, solusi apa yang bisa menjadi solusi inti? karena pembangun benar-benar (mengutip)

secara signifikan kurang dapat dibaca

Solusi properti tampaknya tidak mudah digunakan kembali atau apakah itu kesimpulan yang salah yang saya buat(?) karena tidak ada jawaban tentang cara menggunakannya untuk:

membuat widget sekunder yang menggunakan kembali kode yang saat ini berada di dalam _ExampleState di widget yang berbeda?
Dengan twist: widget baru itu harus mengelola ID penggunanya secara internal di dalam Statusnya

Saya bersedia membantu dengan dokumen desain seperti yang disarankan @timsneath . Saya pikir ini mungkin format yang lebih baik untuk menjelaskan masalah dengan beberapa contoh studi kasus, serta menyebutkan solusi yang berbeda dan mengeksplorasi apakah kita dapat menemukan solusi yang cocok dengan flutter dan di mana letaknya. Saya setuju bahwa diskusi dalam masalah ini sedikit hilang.

Saya cukup skeptis tentang ide membuat dokumen desain saat ini.
Jelas bahwa untuk saat ini, @Hixie menentang pemecahan masalah ini di dalam Flutter secara langsung.

Bagi saya, sepertinya kita tidak sepakat tentang pentingnya masalah dan peran Google dalam memecahkan masalah itu.
Jika kedua belah pihak tidak setuju dalam hal ini, saya tidak melihat bagaimana kita dapat melakukan diskusi yang produktif tentang bagaimana mengatasi masalah ini – apapun solusinya.

Utas edisi ini adalah bacaan yang sangat menarik dan saya senang melihat bahwa pertukaran sudut pandang tetap beradab. Namun saya sedikit terkejut dengan kebuntuan saat ini.

Ketika berbicara tentang kait, pandangan saya adalah bahwa sementara Flutter tidak selalu membutuhkan solusi kait khusus yang disajikan oleh @rrousselGit , dia juga tidak mengatakan itu. Flutter memang membutuhkan solusi yang memberikan manfaat yang sama seperti hook, untuk semua alasan yang disebutkan Remi dan pendukung lainnya. @emanuel-lundman merangkum argumen di atas dan saya setuju dengan pandangannya.

Karena tidak ada proposal layak lainnya yang menawarkan kemampuan yang sama dan mengingat fakta bahwa kait memiliki rekam jejak yang terbukti baik di React, dan bahwa ada solusi yang ada yang dapat dijadikan dasar untuk Flutter, saya rasa itu tidak akan terjadi. pilihan yang buruk untuk melakukannya. Saya tidak berpikir konsep kait, sebagai primitif yang juga termasuk dalam Flutter SDK (atau bahkan lebih rendah), akan mengambil apa pun dari Flutter. Menurut pendapat saya itu hanya akan memperkaya dan membuatnya lebih mudah untuk mengembangkan aplikasi yang dapat dipelihara dan menyenangkan dengan Flutter.

Sementara argumen bahwa kait tersedia sebagai paket bagi mereka yang ingin menuai manfaatnya, adalah poin yang valid, saya merasa itu tidak optimal untuk kait seperti primitif. Inilah alasannya.

Sangat sering ketika membuat bahkan paket yang dapat digunakan kembali secara internal, kami berdebat apakah paket tersebut perlu "murni", dalam arti bahwa itu mungkin hanya bergantung pada Dart+Flutter SDK, atau jika kami mengizinkan beberapa paket lain di dalamnya dan jika demikian, yang yang. Bahkan Penyedia keluar untuk paket "murni", tetapi sering diizinkan masuk untuk paket tingkat yang lebih tinggi. Untuk sebuah aplikasi juga selalu ada perdebatan yang sama, paket mana yang OK dan mana yang tidak. Penyedia berwarna hijau, tetapi sesuatu seperti Hooks masih merupakan tanda tanya sebagai satu paket.

Jika solusi seperti kait akan menjadi bagian dari SDK, itu akan menjadi pilihan yang jelas untuk menggunakan kemampuan yang ditawarkannya. Sementara saya ingin menggunakan Hooks dan mengizinkannya sekarang sebagai sebuah paket, saya juga khawatir bahwa itu menciptakan gaya kode Flutter dan memperkenalkan konsep yang mungkin tidak familiar bagi pengembang Flutter yang tidak menggunakannya. Rasanya seperti bercabang jika kita melewati jalan ini tanpa dukungan di SDK. Untuk proyek pribadi yang lebih kecil, ini adalah pilihan yang mudah untuk menggunakan Hooks. Saya sarankan mencobanya bersama dengan Riverpod.

(Konservatisme paket kami berasal dari dibakar oleh paket dan kekacauan ketergantungan pada manajer paket lain di masa lalu, mungkin tidak unik.)

Saya tidak mengatakan bahwa kait akan menjadi satu-satunya cara untuk menyelesaikan masalah saat ini, bahkan jika itu adalah satu-satunya solusi yang ditunjukkan sejauh ini. Ini tentu bisa menjadi pendekatan yang menarik dan valid untuk menyelidiki opsi pada tingkat yang lebih umum sebelum berkomitmen pada solusi. Agar hal itu terjadi, perlu ada pengakuan bahwa Flutter SDK _saat ini memiliki kelemahan dalam hal logika keadaan yang dapat digunakan kembali dengan mudah_, yang tampaknya tidak ada meskipun penjelasan rumit saat ini.

Bagi saya ada dua alasan utama untuk tidak hanya memasukkan Hooks ke dalam kerangka inti. Yang pertama adalah bahwa API memiliki jebakan berbahaya di dalamnya. Terutama, jika Anda akhirnya memanggil kait dengan urutan yang salah maka semuanya akan rusak. Ini sepertinya masalah yang fatal bagi saya. Saya mengerti bahwa dengan disiplin dan mengikuti dokumentasi Anda dapat menghindarinya tetapi IMHO solusi yang baik untuk masalah penggunaan kembali kode ini tidak akan memiliki kekurangan itu.

Yang kedua adalah bahwa seharusnya tidak ada alasan orang tidak bisa begitu saja menggunakan Hooks (atau perpustakaan lain) untuk menyelesaikan masalah ini. Sekarang, dengan Hooks secara khusus itu tidak berfungsi, seperti yang telah dibahas orang, karena menulis hook cukup memberatkan sehingga orang berharap perpustakaan yang tidak terkait akan mendukung hook. Tapi saya pikir solusi yang baik untuk masalah ini tidak membutuhkan itu. Solusi yang baik akan berdiri sendiri dan tidak perlu setiap perpustakaan lain mengetahuinya.

Kami baru saja menambahkan RestorableProperties ke framework. Akan menarik untuk melihat apakah mereka dapat dimanfaatkan di sini entah bagaimana...

Saya setuju @Hixie di API memiliki masalah tersembunyi yang memerlukan penganalisis atau linter untuk dipecahkan. Saya pikir kita, sebagai siapa pun yang ingin berpartisipasi, harus mencari berbagai solusi, mungkin melalui saran dokumen desain, atau sebaliknya, pada masalah manajemen siklus hidup yang dapat digunakan kembali. Idealnya ini akan lebih spesifik Flutter dan memanfaatkan Flutter API sambil juga memecahkan masalah yang dilakukan oleh hook API. Saya pikir versi Vue adalah model yang baik untuk memulai, seperti yang saya sebutkan sebelumnya, karena tidak bergantung pada urutan panggilan kait. Apakah ada orang lain yang tertarik untuk menyelidiki dengan saya?

@Hixie tetapi Anda setuju dengan masalah ini bahwa tidak ada cara yang baik untuk menggunakan kembali logika status dengan cara yang dapat disusun di antara widget? Itu sebabnya Anda mulai berpikir untuk memanfaatkan ResuableProperties entah bagaimana?

Bagi saya ada dua alasan utama untuk tidak hanya memasukkan Hooks ke dalam kerangka inti. Yang pertama adalah bahwa API memiliki jebakan berbahaya di dalamnya. Terutama, jika Anda akhirnya memanggil kait dengan urutan yang salah maka semuanya akan rusak. Ini sepertinya masalah yang fatal bagi saya. Saya mengerti bahwa dengan disiplin dan mengikuti dokumentasi Anda dapat menghindarinya tetapi IMHO solusi yang baik untuk masalah penggunaan kembali kode ini tidak akan memiliki kekurangan itu.

Dari bekerja dengan kait dan bekerja dengan orang lain yang menggunakan kait, ini sebenarnya bukan masalah besar IMHO. Dan sama sekali tidak dibandingkan dengan semua keuntungan besar (keuntungan besar dalam kecepatan dev, penggunaan kembali, komposisi dan kode yang mudah dibaca) yang mereka bawa ke meja.
Kait adalah kait, seperti kelas adalah kelas, bukan hanya fungsi, dan Anda tidak dapat menggunakannya secara kondisional. Anda belajar secepat itu. Dan editor Anda dapat membantu dengan masalah ini juga.

Yang kedua adalah bahwa seharusnya tidak ada alasan orang tidak bisa begitu saja menggunakan Hooks (atau perpustakaan lain) untuk menyelesaikan masalah ini. Sekarang, dengan Hooks secara khusus itu tidak berfungsi, seperti yang telah dibahas orang, karena menulis hook cukup memberatkan sehingga orang berharap perpustakaan yang tidak terkait akan mendukung hook. Tapi saya pikir solusi yang baik untuk masalah ini tidak membutuhkan itu. Solusi yang baik akan berdiri sendiri dan tidak perlu setiap perpustakaan lain mengetahuinya.

Menulis kait tidak memberatkan.
Ini masih lebih mudah daripada solusi yang tersedia sekarang IMHO (untuk menggunakan frasa itu lagi ).
Mungkin saya salah mengartikan apa yang Anda tulis. Tapi saya tidak berpikir ada yang mengatakan itu?
Saya membacanya seperti orang-orang sangat menghargai semua keuntungan yang dibawa solusi kait ke meja dan berharap mereka bisa menggunakannya di mana-mana. Untuk menuai semua manfaat. Karena sebuah kait dapat digunakan kembali, akan sangat bagus jika pengembang pihak ketiga dapat merasa percaya diri untuk membuat kode dan mengirimkan kait mereka sendiri tanpa mengharuskan setiap orang untuk menulis pembungkus mereka sendiri. Raih manfaat dari penggunaan kembali logika negara.
Saya pikir @rrousselGit dan @gaearon sudah menjelaskan hal primitif. Jadi saya tidak akan masuk ke dalamnya.
Mungkin saya tidak mendapatkan pernyataan ini karena saya tidak dapat melihat bahwa itu adalah ringkasan yang baik dari apa yang telah ditulis orang di utas ini. Maafkan saya.

Berharap ada jalan ke depan. Tapi saya pikir sudah waktunya untuk setidaknya menyetujui ini adalah masalah dan maju dengan solusi alternatif yang lebih baik karena kait tampaknya tidak ada di atas meja.
Atau putuskan saja untuk melewati memperbaiki masalah di inti bergetar.

Siapa yang memutuskan jalan ke depan?
Apa langkah selanjutnya?

Ini sepertinya masalah yang fatal bagi saya. Saya mengerti bahwa dengan disiplin dan mengikuti dokumentasi Anda dapat menghindarinya tetapi IMHO solusi yang baik untuk masalah penggunaan kembali kode ini tidak akan memiliki kekurangan itu.

Di React, kami menyelesaikan ini dengan linter — analisis statis. Dalam pengalaman kami, cacat ini tidak terlalu penting bahkan dalam basis kode yang besar. Ada masalah lain yang mungkin kita anggap sebagai kekurangan, tetapi saya hanya ingin menunjukkan bahwa sementara ketergantungan pada urutan panggilan yang terus-menerus adalah apa yang orang secara intuitif berpikir akan menjadi masalah, keseimbangan akhirnya sangat berbeda dalam praktiknya.

Alasan sebenarnya saya menulis komentar ini adalah karena Flutter menggunakan bahasa yang dikompilasi. "Linting" tidak opsional. Jadi, jika ada keselarasan antara bahasa host dan kerangka kerja UI, sangat mungkin untuk menegakkan bahwa masalah "bersyarat" tidak pernah muncul secara statis. Tapi itu hanya berfungsi ketika kerangka UI dapat memotivasi perubahan bahasa (mis. Tulis + Kotlin).

@Hixie tetapi Anda setuju dengan masalah ini bahwa tidak ada cara yang baik untuk menggunakan kembali logika status dengan cara yang dapat disusun di antara widget? Itu sebabnya Anda mulai berpikir untuk memanfaatkan ResuableProperties entah bagaimana?

Itu pasti sesuatu yang dibesarkan orang. Itu bukan sesuatu yang saya punya pengalaman mendalam. Itu bukan sesuatu yang saya rasakan sebagai masalah saat menulis aplikasi saya sendiri dengan Flutter. Itu tidak berarti bahwa itu bukan masalah nyata bagi sebagian orang.

Karena sebuah kait dapat digunakan kembali, akan sangat bagus jika pengembang pihak ketiga dapat merasa percaya diri untuk membuat kode dan mengirimkan kait mereka sendiri tanpa mengharuskan semua orang untuk menulis pembungkus mereka sendiri.

Maksud saya adalah bahwa solusi yang baik di sini tidak mengharuskan siapa pun untuk menulis pembungkus.

Apa langkah selanjutnya?

Ada banyak langkah selanjutnya, misalnya:

  • Jika ada masalah khusus dengan Flutter yang belum kita bicarakan di sini, ajukan masalah dan jelaskan masalahnya.
  • Jika Anda memiliki ide bagus tentang cara mengatasi masalah ini dengan cara yang lebih baik daripada Hooks, buat paket yang melakukannya.
  • Jika ada hal-hal yang dapat dilakukan untuk meningkatkan Hooks, lakukanlah.
  • Jika ada masalah dengan Flutter yang mencegah Hooks mencapai potensi penuhnya, ajukan masalah tersebut sebagai masalah baru.
    dll.

Utas edisi ini adalah bacaan yang sangat menarik dan saya senang melihat bahwa pertukaran sudut pandang tetap beradab.

Saya tidak suka melihat seperti apa utas yang tidak beradab itu. Ada sangat sedikit empati di utas ini sehingga sulit untuk dibaca dan diikuti dari samping

Maksud saya adalah bahwa solusi yang baik di sini tidak mengharuskan siapa pun untuk menulis pembungkus.

Anda tidak harus menulis pembungkus sekalipun. Tetapi Anda mungkin ingin menuai manfaat dan hal-hal yang dapat digunakan kembali dalam kode Anda sendiri yang telah Anda terbiasa. Anda yakin masih bisa menggunakan perpustakaan apa adanya. Jika Anda menulis hal-hal yang membungkus kait (jika mungkin) itu mungkin bukan karena Anda pikir itu beban tetapi itu lebih baik daripada alternatifnya.

Itu sebenarnya alasan yang bagus dan alasan yang disebutkan mengapa solusi untuk masalah di utas ini akan menjadi inti yang bagus. Solusi logika keadaan yang dapat dikomposisi dapat digunakan kembali dalam inti berarti bahwa orang tidak perlu menulis pembungkus karena logika yang dapat digunakan kembali seperti itu dengan aman dapat dikirimkan dalam semua paket tanpa menambahkan dependensi.

Solusi logika keadaan yang dapat dikomposisi dapat digunakan kembali dalam inti berarti bahwa orang tidak perlu menulis pembungkus karena logika yang dapat digunakan kembali seperti itu dengan aman dapat dikirimkan dalam semua paket tanpa menambahkan dependensi.

Poin yang saya coba sampaikan adalah bahwa IMHO solusi yang baik tidak memerlukan _anyone_ untuk menulis logika itu. Tidak akan ada logika yang berlebihan untuk digunakan kembali. Misalnya, melihat contoh "fetchUser" dari sebelumnya, tidak ada yang harus menulis hook, atau yang setara, untuk memanggil fungsi "fetchUser", Anda cukup memanggil fungsi "fetchUser" secara langsung. Demikian pula "fetchUser" tidak perlu tahu apa-apa tentang hook (atau apa pun yang kita gunakan) dan hook (atau apa pun yang kita gunakan) tidak perlu tahu apa-apa tentang "fetchUser". Semua sambil menjaga logika yang Anda tulis sepele, seperti halnya dengan kait.

Pembatasan saat ini disebabkan oleh fakta bahwa kait adalah tambalan di atas batasan bahasa.

Dalam beberapa bahasa, kait adalah konstruksi bahasa, seperti:

state count = 0;

return RaisedButton(
  onPressed: () => count++,
  child: Text('clicked $count times'),
)

Ini akan menjadi varian dari fungsi async /sync , yang dapat mempertahankan beberapa status di seluruh panggilan.

Itu tidak memerlukan penggunaan non-kondisional lagi karena, sebagai bagian dari bahasa, kita dapat membedakan setiap variabel dengan nomor barisnya daripada jenisnya.

Saya akan menambahkan bahwa batasan kait mirip dengan batasan --track-widget-creation.

Bendera ini merusak kanonalisasi konstruktor const untuk widget. Tapi itu tidak masalah karena widget bersifat deklaratif.

Dalam pengertian itu, kait adalah sama. Batasan tidak terlalu penting, karena dimanipulasi secara deklaratif.
Kami tidak akan mendapatkan satu hook yang sangat spesifik tanpa membaca yang lain.

Mungkin contoh fetchuser bukan yang ideal.
Tetapi useStream, useAnimstion atau useStreamCintroller membuat Pohon Widget jauh lebih bersih dan mencegah Anda lupa membuang atau mengurus dudChangeDependencues.
Oleh karena itu cara saat ini memiliki jebakan yang bisa membuat Anda terjebak. Jadi saya kira masalah potensial dengan urutan panggilan tidak lebih besar dari itu.
Saya tidak yakin apakah saya akan mulai menulis hook saya sendiri, tetapi memiliki koleksi yang sering dibutuhkan dan siap digunakan di dalam framework akan menyenangkan.
Itu hanya akan menjadi cara alternatif untuk menghadapi mereka.

@Hixie , benar-benar minta maaf karena tidak dapat memahami apa yang Anda coba gambarkan, saya menyalahkan bahwa ini sudah larut malam di sini, tetapi mungkin hanya saya .. Tetapi dalam solusi bagus yang Anda gambarkan, di mana nilai-nilai negara, logika bisnis negara dan logika peristiwa seumur hidup bahwa solusi untuk masalah membungkus/meringkas agar mudah dikomposisi dan dibagikan di antara widget berada? Bisakah Anda menguraikan sedikit tentang apa yang dilakukan solusi yang baik dan bagaimana Anda melihatnya akan bekerja secara ideal?

Hanya menyela di sini sedikit, melihat bahwa ada yang menyebutkan tentang kesopanan dari diskusi ini. Saya pribadi tidak merasa ada orang di sini yang tidak beradab.

Yang mengatakan, saya pikir perlu dicatat bahwa ini adalah topik yang sangat dipedulikan orang, di semua sisi.

  • @rrousselGit telah menjawab pertanyaan pemula tentang manajemen status di StackOverflow dan pada pelacak masalah package:provider selama bertahun-tahun sekarang. Saya hanya mengikuti yang terakhir dari selang pemadam kebakaran ini, dan saya menghormati ketekunan dan kesabaran Remi.
  • @Hixie dan anggota tim Flutter lainnya sangat peduli dengan API Flutter, stabilitasnya, permukaannya, kemampuan perawatannya, dan keterbacaannya. Berkat pengalaman pengembang Flutter seperti sekarang ini.
  • Pengembang Flutter sangat peduli dengan manajemen keadaan, karena itulah yang mereka lakukan dalam sebagian besar waktu pengembangan mereka.

Jelas bahwa semua pihak dalam diskusi ini memiliki alasan yang baik untuk memperdebatkan apa yang mereka lakukan. Dapat juga dimengerti bahwa perlu waktu untuk menyampaikan pesan.

Jadi, saya akan senang jika diskusi berlanjut, baik di sini atau dalam bentuk lain. Jika saya dapat membantu dengan cara apa pun, seperti dengan dokumen resmi, beri tahu saya.

Sebaliknya, jika orang berpikir bahwa diskusi di sini sudah tidak terkendali, maka mari kita berhenti sejenak dan melihat apakah ada cara yang lebih baik untuk berkomunikasi.

(Secara terpisah, saya ingin memberi teriakan kepada @gaearon untuk bergabung dalam diskusi ini. Pengalaman React dalam hal ini sangat berharga.)

@emanuel-lundman

Tetapi dalam solusi bagus yang Anda gambarkan, di mana nilai-nilai status, logika bisnis status, dan logika peristiwa seumur hidup yang solusi untuk masalah bungkus/enkapsulasi agar mudah dikomposisi dan dibagikan di antara widget berada? Bisakah Anda menguraikan sedikit tentang apa yang dilakukan solusi yang baik dan bagaimana Anda melihatnya akan bekerja secara ideal?

Sayangnya saya tidak bisa menjelaskannya karena saya tidak tahu. :-)

@escamoteur

Mungkin contoh fetchuser bukan yang ideal.
Tetapi useStream, useAnimstion atau useStreamCintroller membuat Pohon Widget jauh lebih bersih dan mencegah Anda lupa membuang atau mengurus dudChangeDependencues.

Salah satu kesulitan dalam masalah ini adalah "memindahkan tiang gawang" di mana suatu masalah dijelaskan, kemudian ketika dianalisis, masalah tersebut dianggap bukan masalah sebenarnya dan dijelaskan masalah baru, dan seterusnya. Apa yang mungkin benar-benar berguna adalah membuat beberapa contoh kanonik, misalnya aplikasi Flutter demo yang memiliki contoh nyata dari masalah, yang tidak terlalu disederhanakan demi eksposisi. Kemudian kita dapat mengimplementasikannya kembali dengan menggunakan Hooks, dan proposal lainnya, dan benar-benar mengevaluasinya satu sama lain secara konkret. (Saya akan melakukan ini sendiri kecuali saya tidak benar-benar mengerti apa masalahnya, jadi mungkin lebih baik jika seseorang yang menganjurkan Hooks akan melakukannya.)

Apa yang mungkin benar-benar berguna adalah membuat beberapa contoh kanonik, misalnya aplikasi demo Flutter yang memiliki contoh nyata dari masalah, yang tidak terlalu disederhanakan demi eksposisi

Apa pendapat Anda tentang contoh yang saya berikan di sini? https://github.com/flutter/flutter/issues/51752#issuecomment -669626522

Ini adalah cuplikan kode dunia nyata.

Saya pikir itu akan menjadi awal yang bagus. Bisakah kita membuatnya menjadi aplikasi yang berjalan sebagai aplikasi yang berdiri sendiri, dengan versi yang tidak menggunakan kait dan versi yang melakukannya?

Maaf, maksud saya sebagai cuplikan kode, bukan sebagai aplikasi.

Saya pikir salah satu masalah dengan ide "demo aplikasi Flutter" adalah, contoh yang dibuat di utas ini sangat nyata.
Mereka tidak terlalu disederhanakan.
Kasus penggunaan utama dari hook jika untuk memfaktorkan status mikro, seperti debounce, event handler, langganan, atau efek samping implisit – yang digabungkan bersama untuk mencapai logika yang lebih berguna.

Saya punya beberapa contoh di Riverpod, seperti https://marvel.riverpod.dev/#/ di mana kode sumbernya ada di sini: https://github.com/rrousselGit/river_pod/tree/master/examples/marvel/lib
Tapi itu tidak akan jauh berbeda dari apa yang disebutkan sampai sekarang.

@Hixie

Saya benar-benar kesulitan memahami mengapa ini menjadi masalah. Saya telah menulis banyak aplikasi Flutter tetapi sepertinya tidak terlalu menjadi masalah? Bahkan dalam kasus terburuk, ada empat baris untuk mendeklarasikan properti, menginisialisasi, membuangnya, dan melaporkannya ke data debug (dan sebenarnya biasanya lebih sedikit, karena Anda biasanya dapat mendeklarasikannya pada baris yang sama saat Anda menginisialisasinya, aplikasi umumnya tidak perlu khawatir tentang menambahkan status ke properti debug, dan banyak dari objek ini tidak memiliki status yang perlu dibuang).

Saya berada di kapal yang sama.
Saya akui, saya juga tidak begitu mengerti masalah yang dijelaskan di sini. Saya bahkan tidak mengerti apa yang dimaksud dengan "logika negara", yang harus dapat digunakan kembali.

Saya memiliki banyak widget formulir stateful, beberapa yang memiliki puluhan bidang formulir, dan saya harus mengelola sendiri pengontrol teks dan node fokus. Saya membuat dan membuangnya dalam metode siklus hidup statelesswidget. Meskipun cukup membosankan, saya tidak memiliki widget yang menggunakan jumlah pengontrol/focusNodes yang sama atau untuk kasus penggunaan yang sama. Satu-satunya hal yang umum di antara mereka adalah konsep umum menjadi stateful dan menjadi bentuk. Hanya karena itu sebuah pola, itu tidak berarti kode itu diulang.
Maksud saya, di banyak bagian kode saya, saya harus mengulang array, saya tidak akan memanggil melakukan "untuk (hal-hal var dalam hal-hal)" di seluruh kode pengulangan aplikasi saya.

Saya menyukai kekuatan StatefulWidget yang berasal dari kesederhanaan api siklus hidupnya. Ini memungkinkan saya untuk menulis StatefulWidgets yang melakukan satu hal dan melakukannya secara terpisah dari aplikasi lainnya. "Keadaan" widget saya selalu bersifat pribadi, jadi penggunaan kembali widget saya tidak menjadi masalah, begitu juga penggunaan kembali kode.

Saya memiliki beberapa masalah dengan contoh yang diangkat di sini, yang agak sejalan dengan poin Anda:

  • membuat beberapa widget stateful dengan "logika status" yang sama persis tampaknya salah dan bertentangan dengan gagasan memiliki widget mandiri. Tetapi sekali lagi, saya bingung apa yang orang maksud dengan "logika negara" yang umum.
  • kait sepertinya tidak melakukan apa pun yang belum bisa saya lakukan menggunakan panah biasa dan konsep pemrograman dasar (seperti fungsi)
  • masalah tampaknya terkait atau disebabkan oleh gaya pemrograman tertentu, gaya yang tampaknya mendukung "keadaan global yang dapat digunakan kembali".
  • mengabstraksi beberapa baris kode berbau "optimasi kode prematur" dan menambahkan kompleksitas untuk memecahkan masalah yang tidak ada hubungannya dengan kerangka kerja dan segala sesuatu yang berkaitan dengan bagaimana orang menggunakannya.

Ini secara signifikan kurang dapat dibaca.

  • Kami memiliki 10 level lekukan – 12 jika kami membuat FilterBuilder untuk menggunakan kembali logika filter
  • Logika filter tidak dapat digunakan kembali sebagaimana adanya.

    • kita mungkin lupa untuk membatalkan timer karena kesalahan
  • setengah dari metode build tidak berguna bagi pembaca. Para Pembangun mengalihkan perhatian kita dari hal-hal yang penting
  • Saya kehilangan 5 menit mencoba memahami mengapa kode tidak dapat dikompilasi karena tanda kurung yang hilang

Contoh Anda lebih merupakan pertunjukan tentang bagaimana Penyedia bertele-tele dan mengapa menyalahgunakan InheritedWidgets untuk semuanya adalah hal yang buruk, daripada masalah nyata apa pun dengan StatelessWidget dan api siklus hidup Negara Flutter.

@rrousselGit Maaf jika saya tidak jelas. Saran yang saya buat di atas adalah khusus untuk membuat aplikasi Vanilla Flutter (menggunakan StatefulWidget dll) yang realistis dan menunjukkan masalah yang Anda gambarkan, sehingga kami kemudian dapat membuat proposal berdasarkan aplikasi lengkap yang sebenarnya. Contoh konkret yang telah kita diskusikan di sini, seperti contoh "fetchUser", selalu berakhir dengan diskusi seperti "Anda bisa menangani kasus seperti ini dan itu akan sederhana dan tidak perlu Hooks" diikuti dengan "yah, itu terlalu disederhanakan, di dunia nyata Anda membutuhkan Hooks". Jadi maksud saya adalah, mari kita buat contoh dunia nyata yang benar-benar _memerlukan Hooks, yang tidak terlalu disederhanakan, yang menunjukkan kesulitan dalam menggunakan kembali kode, sehingga kita dapat melihat apakah mungkin untuk menghindari masalah ini tanpa menggunakan kode baru. , atau jika kita memang membutuhkan kode baru, dan dalam kasus terakhir, apakah itu harus berbentuk seperti Hooks atau jika kita dapat menemukan solusi yang lebih baik lagi.

Saya bahkan tidak mengerti apa yang dimaksud dengan "logika negara", yang harus dapat digunakan kembali.

Cukup adil
Sejajar dengan logika keadaan adalah logika UI, dan apa yang dibawa Widget ke tabel.

Kami secara teknis dapat menghapus lapisan Widget. Dalam situasi itu, yang tersisa adalah RenderObjects.

Misalnya, kita bisa memiliki penghitung minimalis:

var counter = 0;

final counterLabel = RenderParagraph(
  TextSpan(text: '$counter'),
  textDirection: TextDirection.ltr,
);

final button = RenderPointerListener(
  onPointerUp: (_) {
    counter++;
    counterLabel.text = TextSpan(text: '$counter');
  },
  child: counterLabel,
);

Itu belum tentu rumit. Tapi itu rawan kesalahan. Kami memiliki duplikat pada rendering counterLabel

Dengan widget, kami memiliki:

class _CounterState exends State {
  int counter = 0;

  Widget build(context ) {
    return GestureDetector(
      onTap: () => setState(() => counter++);
      child: Text('$counter'),
    );
  }
}

Satu-satunya hal yang dilakukan adalah memfaktorkan logika Text , dengan membuatnya deklaratif.
Ini adalah perubahan minimalis. Tetapi pada basis kode yang besar, itu adalah penyederhanaan yang signifikan .

Kait melakukan hal yang sama persis.
Tetapi alih-alih Text , Anda mendapatkan kait khusus untuk logika status. Yang termasuk pendengar, debouncing, membuat permintaan HTTP, ...


Contoh Anda lebih merupakan pertunjukan tentang bagaimana Penyedia bertele-tele dan mengapa menyalahgunakan InheritedWidgets untuk semuanya adalah hal yang buruk, daripada masalah nyata apa pun dengan StatelessWidget dan api siklus hidup Negara Flutter.

Ini tidak terkait dengan penyedia (kode ini sama sekali tidak menggunakan penyedia).
Jika ada, penyedia memilikinya lebih baik karena memiliki context.watch daripada Consumer .

Solusi standar adalah mengganti Consumer dengan ValueListenableBuilder – yang mengarah ke masalah yang sama persis.

Saya setuju @Hixie , saya pikir kita perlu dua perbandingan berdampingan untuk menilai keefektifan hanya Flutter versus dengan kait. Ini juga akan membantu meyakinkan orang lain apakah kait lebih baik atau tidak, atau mungkin solusi lain bahkan lebih baik jika aplikasi vanilla dibuat dengan solusi ketiga ini. Konsep aplikasi vanilla ini telah ada selama beberapa waktu, dengan hal-hal seperti TodoMVC yang menunjukkan perbedaan antara berbagai kerangka kerja front-end, jadi itu tidak selalu baru. Saya dapat membantu dengan membuat aplikasi contoh ini.

@satvikpendem
Saya akan bersedia membantu.
Saya pikir contoh aplikasi di repo flutter_hooks mungkin menampilkan beberapa kait berbeda dan apa yang mereka buat lebih mudah/masalah yang mereka pecahkan, dan akan menjadi tempat awal yang baik.

Saya juga berpikir bahwa kita juga dapat menggunakan beberapa contoh dan pendekatan yang disajikan dalam edisi ini.

Pembaruan: Kode ada di sini, https://github.com/TimWhiting/local_widget_state_approaches
Saya tidak yakin nama yang tepat untuk repositori, jadi jangan berasumsi bahwa itu adalah masalah yang kami coba selesaikan. Saya telah melakukan aplikasi penghitung dasar di stateful dan hooks. Saya tidak punya banyak waktu nanti malam, tetapi saya akan mencoba menambahkan lebih banyak kasus penggunaan yang lebih menggambarkan apa yang mungkin menjadi masalah. Siapa pun yang ingin berkontribusi, silakan minta akses.

Contoh konkret yang telah kita diskusikan di sini, seperti contoh "fetchUser", selalu berakhir dengan diskusi seperti "Anda bisa menangani kasus seperti ini dan itu akan sederhana dan tidak perlu Hooks" diikuti dengan "yah, itu terlalu disederhanakan, di dunia nyata Anda membutuhkan Hooks".

saya tidak setuju. Saya tidak berpikir saya telah melihat "Anda bisa menangani kasus itu seperti ini" dan setuju bahwa kode yang dihasilkan lebih baik daripada varian kait.

Maksud saya selama ini adalah bahwa meskipun kita dapat melakukan sesuatu secara berbeda, kode yang dihasilkan rawan kesalahan dan/atau kurang dapat dibaca.
Ini berlaku untuk fetchUser juga

Kait melakukan hal yang sama persis.
Tetapi alih-alih Text , Anda mendapatkan kait khusus untuk logika status. Yang termasuk pendengar, debouncing, membuat permintaan HTTP, ...

Tidak, saya masih tidak mengerti apa yang seharusnya menjadi logika keadaan umum ini. Maksud saya, saya memiliki banyak widget yang membaca dari database dalam metode "initState/didUpdateDependency", tetapi saya tidak dapat menemukan dua widget yang membuat kueri yang sama persis sehingga "logika" mereka tidak sama.

Menggunakan contoh membuat permintaan HTTP. Dengan asumsi saya memiliki "makeHTTPRequest(url, paramters)" di suatu tempat di kelas layanan saya yang perlu digunakan oleh beberapa widget saya, mengapa saya menggunakan pengait alih-alih hanya memanggil metode secara langsung kapan pun saya membutuhkannya? Bagaimana menggunakan kait berbeda dari panggilan metode biasa dalam kasus ini?

Pendengar. Saya tidak memiliki widget yang mendengarkan hal yang sama. Setiap widget saya bertanggung jawab untuk berlangganan apa pun yang mereka butuhkan dan memastikan mereka berhenti berlangganan. Kait mungkin merupakan gula sintaksis untuk sebagian besar hal, tetapi karena widget saya tidak akan mendengarkan kombinasi objek yang sama, kait harus "diparametrikan" entah bagaimana. Sekali lagi, bagaimana kait berbeda dari fungsi lama biasa?


Ini tidak terkait dengan penyedia (kode ini sama sekali tidak menggunakan penyedia).
Jika ada, penyedia memilikinya lebih baik karena memiliki context.watch daripada Consumer .

Hah? Contoh tandingan Anda dengan apa yang seharusnya diselesaikan oleh HookWidget "ChatScreen" Anda, adalah ini:

<strong i="19">@override</strong>
  Widget build(BuildContext context) {
    return Consumer<Auth>(
      provider: authProvider,
      builder: (context, auth, child) {
        return Consumer<int>(
          provider: selectedChatProvider,
          builder: (context, chatId, child) {

Bagaimana ini tidak terkait dengan penyedia? Saya bingung. Saya bukan ahli dalam Penyedia, tetapi ini jelas terlihat seperti kode yang menggunakan Penyedia.

Saya ingin menegaskan fakta bahwa masalah ini bukan tentang keadaan kompleks.
Ini tentang peningkatan kecil yang dapat diterapkan ke seluruh basis kode.

Jika kita tidak setuju dengan nilai contoh yang diberikan di sini, aplikasi tidak akan membawa apa pun ke percakapan – karena tidak ada yang bisa kita lakukan dengan kait yang tidak bisa kita lakukan dengan StatefulWidget.

Rekomendasi saya adalah membandingkan cuplikan mikro berdampingan seperti ImplicitFetcher , dan _objectively_ menentukan kode mana yang lebih baik menggunakan metrik terukur, dan melakukannya untuk berbagai cuplikan kecil.


Bagaimana ini tidak terkait dengan penyedia? Saya bingung. Saya bukan ahli dalam Penyedia, tetapi ini jelas terlihat seperti kode yang menggunakan Penyedia.

Kode ini bukan dari Penyedia tetapi dari proyek berbeda yang tidak menggunakan InheritedWidgets.
Consumer penyedia tidak memiliki parameter provider .

Dan seperti yang saya sebutkan, Anda dapat mengganti Consumer -> ValueListenableBuilder/StreamBuilder/BlocBuilder/Observer/...

dalam metode "initState/didUpdateDependency", tetapi saya tidak dapat menemukan dua widget yang membuat kueri yang sama persis sehingga "logika" mereka tidak sama.

Logika keadaan yang ingin kita gunakan kembali bukanlah "membuat kueri" tetapi "melakukan sesuatu ketika x berubah". "Lakukan sesuatu" dapat berubah, tetapi "ketika x berubah" adalah umum

Contoh konkret:
Kami mungkin ingin widget membuat permintaan HTTP setiap kali ID yang diterimanya berubah.
Kami juga ingin membatalkan permintaan yang tertunda menggunakan CancelableOperation package:async .

Sekarang, kami memiliki dua widget yang ingin melakukan hal yang persis sama, tetapi dengan permintaan HTTP yang berbeda.
Pada akhirnya, kami memiliki:

CancelableOperation<User> pendingUserRequest;

initState() {
  pendingUserRequest = fetchUser(widget.userId);
}

didUpdateWidget(oldWidget) {
  if (widget.userId != oldWidget.userId) {
      pendingUserRequest.cancel();
      pendingUserRequest = fetchUser(widget.userId);
  }
}

dispose() {
  pendingUserRequest.cancel();
}

VS:

CancelableOperation<Message> pendingMessageRequest;

initState() {
  pendingMessageRequest = fetchMessage(widget.messageId);
}

didUpdateWidget(oldWidget) {
  if (widget.userId != oldWidget.messageId) {
      pendingMessageRequest.cancel();
      pendingMessageRequest = fetchMessage(widget.messageId);
      message = pendingMessageRequest.value;
  }
}

dispose() {
  pendingMessageRequest.cancel();
}

Satu-satunya perbedaan adalah kita mengubah fetchUser dengan fetchMessage . Logikanya sebaliknya 100% sama. Tetapi kami tidak dapat menggunakannya kembali, yang rawan kesalahan.

Dengan hook, kita dapat memfaktorkan ini menjadi hook useUnaryCancelableOperation .

Yang berarti bahwa dengan dua widget yang sama akan melakukan:

Widget build(context) {
  Future<User> user = useUnaryCancelableOperation(userId, fetchUser);
}

VS

Widget build(context) {
  Future<Message> message = useUnaryCancelableOperation(messageId, fetchMessage);
}

Dalam skenario ini, semua logika yang terkait dengan membuat permintaan dan membatalkannya dibagikan. Hanya tersisa perbedaan yang berarti, yaitu fetchUser vs fetchMessage .
Kami bahkan dapat membuat paket dari useUnaryCancelableOperation , dan sekarang semua orang dapat menggunakannya kembali di aplikasi mereka.

Jika kita tidak setuju dengan nilai contoh yang diberikan di sini, aplikasi tidak akan membawa apa pun ke percakapan – karena tidak ada yang bisa kita lakukan dengan kait yang tidak bisa kita lakukan dengan StatefulWidget.

Jika itu benar-benar terjadi maka saya kira kita harus menutup bug ini, karena kita sudah membahas contoh yang diberikan di sini dan mereka belum menarik. Saya benar-benar ingin memahami situasinya dengan lebih baik, dan itu terdengar dari komentar sebelumnya di bug ini seperti manfaatnya ada di level aplikasi, maka saran saya agar kami mempelajari contoh aplikasi.

Satu-satunya perbedaan adalah bahwa kami mengubah fetchUser dengan fetchMessage . Logikanya sebaliknya 100% sama. Tetapi kami tidak dapat menggunakannya kembali, yang rawan kesalahan.

Apa yang rawan kesalahan dan apa yang bisa digunakan kembali? Menerapkan lapisan abstraksi dan hierarki kelas yang sama sekali baru agar kita tidak perlu mengimplementasikan tiga metode di kelas dan terlalu berlebihan.

Sekali lagi, hanya karena sesuatu adalah pola umum, bukan berarti Anda perlu membuat fitur baru untuk itu. Selain itu, jika Anda ingin mengurangi kode berulang dalam kasus ini, Anda dapat memperluas kelas StatefulWidget* dan mengganti metode initstate/didUpdateWidget dengan bit umum.

Dengan kait, kita dapat memfaktorkan ini menjadi kait useUnaryCancelableOperation .

Yang berarti bahwa dengan dua widget yang sama akan melakukan:

Widget build(context) {
  Future<User> user = useUnaryCancelableOperation(userId, fetchUser);
}

VS

Widget build(context) {
  Future<Message> message = useUnaryCancelableOperation(messageId, fetchMessage);
}

Dalam skenario ini, semua logika yang terkait dengan membuat permintaan dan membatalkannya dibagikan. Hanya tersisa perbedaan yang berarti, yaitu fetchUser vs fetchMessage .
Kami bahkan dapat membuat paket dari useUnaryCancelableOperation , dan sekarang semua orang dapat menggunakannya kembali di aplikasi mereka.

Maaf tapi itu pasti tidak dari saya. Selain dari fakta bahwa ia hanya menyimpan sejumlah kecil redundansi kode, "memfaktorkan" kode yang secara konseptual milik metode siklus hidup "initState" dan "memperbarui", ke dalam metode build adalah hal yang tidak penting.

Saya berharap metode build saya hanya membangun tata letak dan tidak ada yang lain. Menyiapkan dan meruntuhkan dependensi jelas tidak termasuk dalam metode build, dan saya cukup senang harus secara eksplisit menulis ulang jenis kode yang sama untuk membuatnya eksplisit untuk diri saya di masa depan dan orang lain tentang apa yang dilakukan widget saya. Dan jangan menempelkan semuanya dalam metode build.

Jika itu benar-benar terjadi maka saya kira kita harus menutup bug ini

@Hixie Tolong jangan. Orang-orang peduli dengan masalah ini. Saya telah berbicara dengan Anda di reddit tentang hal yang sama , tetapi dalam konteks SwiftUI: https://github.com/szotp/SwiftUI_vs_Flutter

Ini bukan tentang kait, ini tentang entah bagaimana meningkatkan widget stateful. Orang hanya benci menulis boilerplate. Untuk pengembang yang menulis kode SwiftUI, yang terbiasa dengan RAII dan menyalin semantik Tampilan, mengelola sekali pakai secara manual tampaknya tidak tepat.

Jadi saya mendorong tim flutter untuk setidaknya melihat ini sebagai masalah dan memikirkan solusi/perbaikan alternatif.

Saya berharap metode build saya hanya membangun tata letak dan tidak ada yang lain. Menyiapkan dan menghancurkan dependensi jelas tidak termasuk dalam metode build,
Itu poin penting. Metode build harus murni. Tetap saja saya berharap kita bisa mendapatkan keuntungan tanpa kesulitan

Saya benar-benar tidak mengerti dorongan untuk lebih banyak contoh di sini. Terlihat jelas di wajahnya.

Masalah yang diselesaikan dengan kait sederhana dan jelas, itu membuat kode KERING. Manfaat dari ini adalah yang jelas, lebih sedikit kode == lebih sedikit bug, perawatan lebih mudah, lebih sedikit tempat untuk menyembunyikan bug, jumlah baris yang lebih rendah secara keseluruhan meningkatkan keterbacaan, programmer junior lebih terisolasi, dll.

Jika Anda berbicara tentang kasus penggunaan dunia nyata, ini adalah aplikasi tempat Anda menyiapkan dan menghancurkan 12 pengontrol animator dalam 12 tampilan berbeda, setiap saat, membiarkan pintu terbuka untuk melewatkan panggilan buang(), atau menimpa beberapa metode siklus hidup lainnya. kemudian terapkan itu ke lusinan instance stateful lainnya, dan Anda dengan mudah melihat ratusan atau ribuan baris kode yang tidak berguna.

Flutter penuh dengan kasus-kasus di mana kita perlu terus-menerus mengulangi diri kita sendiri, menyiapkan, dan merobohkan keadaan objek kecil, yang menciptakan semua jenis peluang untuk bug yang tidak perlu ada, tetapi ada, karena saat ini tidak ada pendekatan elegan untuk berbagi logika setup/teardown/sinkronisasi hafalan ini.

Anda dapat melihat masalah ini dalam status _any_ yang memiliki fase penyiapan dan pembongkaran, atau memiliki beberapa kait siklus hidup yang selalu perlu dikaitkan.

Saya sendiri merasa menggunakan widget adalah pendekatan terbaik, saya jarang menggunakan AnimatorController misalnya karena pengaturan/teardown sangat mengganggu, verbose dan rawan bug, alih-alih menggunakan TweenAnimationBuilder di mana pun saya bisa. Tetapi pendekatan ini memiliki keterbatasan saat Anda mendapatkan jumlah objek stateful yang lebih tinggi dalam tampilan tertentu, memaksa bersarang dan bertele-tele di mana sebenarnya tidak ada yang diperlukan.

@szotp Saya belum... Saya lebih suka kita membuat satu atau lebih aplikasi dasar yang menunjukkan masalah sehingga kita dapat mengevaluasi solusinya. Saya akan melakukannya sendiri tetapi saya tidak mengerti persis apa yang kami coba selesaikan jadi saya orang yang salah untuk melakukannya.

Aplikasi Baseline

@esDotDev Kami telah membahas kasus seperti ini di bug ini sejauh ini, tetapi setiap kali kami memilikinya, solusi selain Hooks diabaikan karena mereka tidak menyelesaikan beberapa masalah yang tidak termasuk dalam contoh yang ditangani oleh solusi. Oleh karena itu, deskripsi sederhana dari masalah tampaknya tidak cukup untuk menangkap sepenuhnya. Misalnya, kasus "12 pengontrol animator" mungkin dapat diselesaikan dengan serangkaian pengontrol animasi dan fitur fungsional di Dart. TweenAnimationBuilder mungkin menjadi solusi lain. Tak satu pun dari itu melibatkan Hooks. Tetapi saya yakin jika saya menyarankan itu, seseorang akan menunjukkan sesuatu yang saya lewatkan dan mengatakan "itu tidak berhasil karena ..." dan memunculkan masalah (baru, dalam konteks contoh). Oleh karena itu, kebutuhan akan aplikasi dasar yang kita semua setujui mencakup seluruh penyebaran masalah.

Jika ada yang ingin memajukan ini, saya benar-benar berpikir cara terbaik untuk melakukannya adalah apa yang saya jelaskan di atas (https://github.com/flutter/flutter/issues/51752#issuecomment-670249755 dan https://github.com /flutter/flutter/issues/51752#issuecomment-670232842). Itu akan memberi kita titik awal yang kita semua sepakati mewakili sejauh mana masalah yang kita coba selesaikan; kemudian, kami dapat merancang solusi yang mengatasi masalah tersebut dengan cara yang memenuhi semua keinginan (misalnya kebutuhan @rrousselGit untuk penggunaan kembali, kebutuhan @Rudiksz untuk metode pembangunan bersih, dll), dan yang paling penting kami dapat mengevaluasi solusi tersebut di konteks aplikasi dasar.

Saya pikir kita semua bisa dengan mudah menyepakati masalah dia:
_Tidak ada cara yang elegan untuk berbagi tugas penyiapan/penghancuran yang terkait dengan hal-hal seperti Streams, AnimatorControllers, dll. Hal ini menyebabkan verbositas yang tidak perlu, celah untuk bug, dan keterbacaan yang berkurang._

Apakah ada yang tidak setuju dengan itu? Tidak bisakah kita mulai dari sana dan bergerak maju untuk mencari solusi? Kita harus setuju bahwa itu adalah masalah inti terlebih dahulu, yang sepertinya masih belum kita lakukan.

Ketika saya menulis itu, saya menyadari persis sama dengan nama masalah, yang terbuka dan menyisakan ruang untuk diskusi:
" Menggunakan kembali logika keadaan terlalu bertele-tele atau terlalu sulit "

Bagi saya itu adalah masalah yang sangat jelas, dan kita harus segera bergerak melewati tahap debat dan bertukar pikiran tentang apa yang akan berhasil, jika tidak kait lalu apa. Kami membutuhkan kondisi mikro yang dapat digunakan kembali... Saya yakin kami dapat menemukan sesuatu. Itu benar-benar akan membersihkan banyak tampilan Flutter di penghujung hari dan membuatnya lebih kuat.

@Hixie Tolong jangan. Orang-orang peduli dengan masalah ini. Saya telah berbicara dengan Anda di reddit tentang hal yang sama , tetapi dalam konteks SwiftUI: https://github.com/szotp/SwiftUI_vs_Flutter

Contoh SwiftUI Anda dapat direplikasi dalam dart dalam beberapa baris kode, hanya dengan memperluas kelas StatefulWidget.

Saya memiliki StatefulWidgets yang tidak berlangganan pemberitahuan apa pun dan/atau melakukan panggilan eksternal apa pun, dan kenyataannya sebagian besar seperti itu. Saya memiliki sekitar 100 widget khusus (walaupun tidak semuanya Stateful), dan mungkin 15 di antaranya memiliki "logika keadaan umum" apa pun seperti yang dijelaskan oleh contoh di sini.

Dalam jangka panjang, menulis beberapa baris kode (alias boilerplate) adalah tradeoff kecil untuk menghindari overhead yang tidak perlu. Dan lagi, masalah harus mengimplementasikan metode initState/didUpdate tampaknya terlalu berlebihan. Ketika saya membuat widget yang menggunakan salah satu pola yang dijelaskan di sini, saya mungkin menghabiskan 5-10 menit pertama untuk "menerapkan" metode siklus hidup dan kemudian beberapa hari benar-benar menulis dan memoles widget itu sendiri tanpa pernah menyentuh metode siklus hidup tersebut. Jumlah waktu yang saya habiskan untuk menulis apa yang disebut kode setup/teardown boilerplate sangat kecil dibandingkan dengan kode aplikasi saya.

Seperti yang saya katakan, fakta bahwa StatefulWidgets membuat begitu sedikit asumsi tentang kegunaannya adalah apa yang membuat mereka begitu kuat dan efisien.

Menambahkan jenis widget baru ke Flutter yang mensubklasifikasikan StatefulWidget (atau tidak) untuk kasus penggunaan khusus ini akan baik-baik saja, tetapi jangan memasukkannya ke dalam StatefulWidget itu sendiri. Saya memiliki banyak widget yang tidak memerlukan overhead yang akan datang dengan sistem "kait" atau keadaan mikro.

@esDotDev Saya setuju bahwa itu adalah masalah yang dihadapi beberapa orang; Saya bahkan mengusulkan beberapa solusi sebelumnya dalam masalah ini (cari berbagai versi saya dari kelas Property , mungkin terkubur sekarang karena GitHub tidak suka menampilkan semua komentar). Kesulitannya adalah proposal tersebut ditolak karena tidak menyelesaikan masalah tertentu (misalnya dalam satu kasus, tidak menangani hot reload, dalam kasus lain, tidak menangani didUpdateWidget). Jadi kemudian saya membuat lebih banyak proposal, tetapi kemudian ditolak lagi karena tidak menangani hal lain (saya lupa apa). Inilah mengapa penting untuk memiliki semacam garis dasar yang kami setujui mewakili _keseluruhan_ masalah sehingga kami dapat menemukan solusi untuk masalah itu.

Tujuannya tidak pernah berubah. Kritik yang dilontarkan adalah solusi yang diajukan kurang fleksibel. Tak satu pun dari mereka terus bekerja di luar cuplikan yang mereka terapkan.

Inilah sebabnya mengapa judul dalam edisi ini menyebutkan "Sulit": Karena saat ini tidak ada fleksibilitas dalam cara kami memecahkan masalah saat ini.


Cara lain untuk melihatnya adalah:

Masalah ini pada dasarnya berargumen bahwa kita perlu menerapkan lapisan Widget, untuk logika Negara.
Solusi yang disarankan adalah "Tetapi Anda dapat melakukannya dengan RenderObjects".

_Dalam jangka panjang, menulis beberapa baris kode (alias boilerplate) adalah tradeoff kecil untuk menghindari overhead yang tidak perlu._

Pasangkan nits dengan pernyataan ini:

  1. Ini tidak benar-benar beberapa baris, jika Anda mengambil tanda kurung, spasi baris @overides , dll ke acct, Anda dapat melihat 10-15+ baris untuk pengontrol animator sederhana. Itu non-sepele dalam pikiran saya ... seperti cara di luar sepele. 3 baris untuk melakukan ini mengganggu saya (di Unity itu Thing.DOTween() ). 15 konyol.
  1. Ini bukan tentang mengetik, meskipun itu menyakitkan. Ini tentang kekonyolan memiliki kelas 50 baris, di mana 30 barisnya adalah boilerplate hafalan. Kebingungannya. Ini tentang fakta bahwa jika Anda _tidak_ menulis boilerplate, tidak ada peringatan atau apa pun, Anda hanya menambahkan bug.
  2. Saya tidak melihat overhead yang layak didiskusikan dengan sesuatu seperti Hooks. Kita berbicara tentang array objek, dengan beberapa fxn di masing-masingnya. Di Dart, yang sangat cepat. Ini adalah ikan haring merah imo.

@esDotDev

Bagi saya itu adalah masalah yang sangat jelas, dan kita harus segera bergerak melewati tahap debat dan bertukar pikiran tentang apa yang akan berhasil, jika tidak kait lalu apa.

Memperluas widget. Seperti cara ValueNotifier memperluas ChangeNotifier untuk menyederhanakan pola penggunaan umum, semua orang dapat menulis cita rasa StatelessWidgets mereka sendiri untuk kasus penggunaan khusus mereka.

Ya saya setuju itu adalah pendekatan yang efektif, tetapi itu meninggalkan sesuatu yang diinginkan. Jika saya memiliki 1 animator, maka saya bisa menggunakan TweenAnimationBuilder. Keren, masih seperti 5 baris kode, tapi terserah. bekerja ... tidak TERLALU buruk. Tetapi jika saya memiliki 2 atau 3? Sekarang saya berada di neraka bersarang, jika saya memiliki cpl objek stateful lainnya untuk beberapa alasan, yah itu semua agak menjadi kekacauan lekukan, atau saya membuat beberapa widget yang sangat spesifik yang merangkum kumpulan pengaturan, pembaruan, dan pembongkaran acak logika.

Memperluas widget. Seperti cara ValueNotifier memperluas ChangeNotifier untuk menyederhanakan pola penggunaan umum, semua orang dapat menulis cita rasa StatelessWidgets mereka sendiri untuk kasus penggunaan khusus mereka.

Anda hanya dapat memperluas satu kelas dasar dalam satu waktu. Itu tidak berskala

Mixin adalah upaya logis berikutnya. Tetapi seperti yang disebutkan OP, mereka juga tidak menskala.

@esDotDev

atau saya membuat beberapa widget yang sangat spesifik yang merangkum kumpulan acak dari logika penyiapan, pembaruan, dan pembongkaran.

Semacam widget yang harus menyiapkan 3-4 jenis AnimationControllers memang terdengar seperti kasus penggunaan yang sangat spesifik dan mendukung kumpulan acak logika setup/teardown pasti tidak boleh menjadi bagian dari kerangka kerja. Sebenarnya itu sebabnya metode initState/didUpdateWidget diekspos di tempat pertama, sehingga Anda dapat melakukan kumpulan pengaturan acak sesuai keinginan hati Anda.

Metode initState terpanjang saya adalah 5 baris kode, widget saya tidak mengalami nesting yang berlebihan, jadi kami pasti memiliki kebutuhan dan kasus penggunaan yang berbeda. Atau gaya pengembangan yang berbeda.

@esDotDev

3. Saya tidak melihat overhead yang layak didiskusikan dengan sesuatu seperti Hooks. Kita berbicara tentang array objek, dengan beberapa fxn di masing-masingnya. Di Dart, yang sangat cepat. Ini adalah ikan haring merah imo.

Jika solusi yang diusulkan seperti paket flutter_hooks itu sepenuhnya tidak benar. Ya, secara konseptual ini adalah array dengan fungsi di dalamnya, tetapi implementasinya sama sekali tidak sepele atau efisien.

Maksud saya, saya mungkin salah, tetapi sepertinya HookElement memeriksa apakah ia harus membangun kembali dirinya sendiri dalam metode pembuatannya sendiri?!
Juga memeriksa apakah kait harus diinisialisasi, diinisialisasi ulang, atau dibuang pada setiap pembuatan widget tampaknya seperti overhead yang signifikan. Rasanya tidak benar, jadi saya harap saya salah.

Apakah masuk akal untuk mengambil salah satu contoh arsitektur @brianegan sebagai aplikasi dasar untuk perbandingan?

Jika saya boleh menyela di sini, tidak yakin apakah ini sudah dikatakan. Tetapi di React kami tidak terlalu memikirkan siklus hidup dengan kait, dan itu mungkin terdengar menakutkan jika Anda terbiasa membangun Komponen/Widget, tetapi inilah mengapa siklus hidup tidak terlalu penting.

Sering kali, ketika Anda sedang membangun Komponen/Widget dengan status atau tindakan yang harus diambil berdasarkan alat peraga, Anda ingin sesuatu terjadi ketika keadaan/alat peraga itu berubah (misalnya, seperti yang saya lihat disebutkan di utas ini, Anda pasti ingin kembali -mengambil detail pengguna ketika prop userId telah berubah). Biasanya jauh lebih alami untuk menganggapnya sebagai "efek" dari perubahan ID pengguna, daripada sesuatu yang terjadi ketika semua properti Widget berubah.

Hal yang sama untuk pembersihan, biasanya jauh lebih alami untuk berpikir "Saya perlu membersihkan status/pendengar/pengontrol ini ketika prop/status ini berubah" daripada "Saya harus ingat untuk membersihkan X ketika semua properti/status berubah atau ketika seluruh komponen dihancurkan".

Saya sudah lama tidak menulis Flutter, jadi saya tidak mencoba terdengar seolah-olah saya tahu iklim atau batasan saat ini yang akan dimiliki pendekatan ini pada pola pikir Flutter saat ini, saya terbuka untuk pendapat yang berbeda. Saya hanya berpikir bahwa banyak orang yang tidak akrab dengan React hooks mengalami kebingungan yang sama seperti yang saya alami ketika saya diperkenalkan kepada mereka karena pola pikir saya begitu mendarah daging dalam paradigma siklus hidup.

@escamoteur @Rudiksz @Hixie telah ada proyek GitHub yang dibuat oleh @TimWhiting yang saya undang ke tempat kami mulai membuat contoh-contoh ini. Setiap orang/kelompok dapat membuat bagaimana mereka akan memecahkan masalah yang telah ditentukan. Itu bukan aplikasi penuh, lebih seperti halaman, tetapi kami juga dapat menambahkan aplikasi, jika mereka berfungsi untuk menunjukkan contoh yang lebih kompleks. Kemudian kita bisa mendiskusikan masalah dan membuat API yang lebih baik. @TimWhiting dapat mengundang siapa saja yang tertarik saya kira.

https://github.com/TimWhiting/local_widget_state_approaches

Komposisi Jetpack juga mirip dengan kait, yang dibandingkan dengan reaksi di sini .

@satvikpendem @TimWhiting Bagus sekali! Terima kasih.

@esDotDev
kasus penggunaan yang sangat spesifik dan mendukung kumpulan acak dari logika penyiapan/penghancuran jelas tidak boleh menjadi bagian dari kerangka kerja.

Ini adalah paku yang kaitnya mengenai kepala. Setiap jenis objek bertanggung jawab atas pengaturan dan pembongkarannya sendiri. Animator tahu cara membuat, memperbarui, dan menghancurkan diri mereka sendiri, seperti halnya streaming, dan sebagainya. Hooks secara khusus memecahkan masalah 'koleksi acak' dari perancah negara yang tersebar di seluruh tampilan Anda. Memungkinkan sebagian besar kode tampilan untuk fokus pada logika bisnis dan format tata letak, yang merupakan kemenangan. Itu tidak memaksa Anda untuk membuat widget khusus, hanya untuk menyembunyikan beberapa boilerplate generik yang sama di setiap proyek.

Metode initState terpanjang saya adalah 5 baris kode, widget saya tidak mengalami nesting yang berlebihan, jadi kami pasti memiliki kebutuhan dan kasus penggunaan yang berbeda. Atau gaya pengembangan yang berbeda.

Milikku juga. Tapi initState() + buang() + didUpdateDependancies(), dan melewatkan salah satu dari 2 yang terakhir dapat menyebabkan bug.

Saya pikir contoh kanonik akan seperti: Tulis tampilan yang menggunakan 1 streamcontroller dan 2 animator controller.

Anda memiliki 3 opsi sejauh yang saya bisa lihat:

  1. Tambahkan 30 baris atau lebih boilerplate ke kelas Anda, dan beberapa mixin. Yang tidak hanya bertele-tele, tetapi cukup sulit untuk diikuti pada awalnya.
  2. Gunakan 2 TweenAnimationBuilders dan StreamBuilder, untuk sekitar 15 level indentasi, bahkan sebelum Anda mendapatkan kode tampilan, dan Anda masih memiliki banyak boilerplate untuk Stream.
  3. Tambahkan seperti 6 baris kode non-indentasi di bagian atas build(), untuk mendapatkan 3 sub-objek stateful Anda, dan tentukan kode init/hancurkan kustom apa pun

Mungkin ada opsi ke-4 yang merupakan SingleStreamBuilderDoubleAnimationWidget, tetapi ini hanya pekerjaan yang dibuat-buat untuk pengembang dan cukup mengganggu secara umum.

Juga perlu dicatat bahwa beban kognitif 3 secara signifikan lebih rendah daripada 2 lainnya untuk pengembang baru. Sebagian besar pengembang baru bahkan tidak tahu bahwa TweenAnimationBuilder ada, dan hanya mempelajari konsep SingleTickerProvider adalah tugas tersendiri. Hanya mengatakan, "Tolong beri saya animator", adalah pendekatan yang lebih mudah dan lebih kuat.

Saya akan mencoba dan mengkode sesuatu hari ini.

2. Gunakan 2 TweenAnimationBuilders dan StreamBuilder, untuk sekitar 15 level indentasi, bahkan sebelum Anda mendapatkan kode tampilan, dan Anda masih memiliki banyak boilerplate untuk Stream.

Benar. Tunjukkan pada kami contoh kode dunia nyata yang menggunakan 15 level lekukan.

Bagaimana mengganti 30 baris kode dengan 6 baris + ratusan baris di perpustakaan mengurangi beban kognitif? Ya, saya bisa mengabaikan "keajaiban" yang dilakukan perpustakaan, tetapi bukan aturannya. Misalnya paket hooks memberi tahu Anda dengan tegas bahwa hook harus digunakan hanya dalam metode build. Sekarang Anda memiliki kendala ekstra untuk dikhawatirkan.

Saya mungkin memiliki kurang dari 200 baris kode yang melibatkan focusnodes, textcontrollers, singletickerproviders atau berbagai metode siklus hidup widget stateful saya, dalam sebuah proyek dengan 15k baris kode. Apa kelebihan kognitif yang Anda bicarakan?

@Rudiksz tolong berhenti bersikap pasif agresif.
Kita bisa berbeda pendapat tanpa bertengkar.


Kendala kait adalah yang paling tidak saya khawatirkan.

Kami tidak berbicara tentang kait secara khusus, tetapi tentang masalahnya.
Jika perlu, kami dapat memberikan solusi yang berbeda.

Yang penting adalah masalah yang ingin kita selesaikan.

Selanjutnya, Widget hanya dapat digunakan di dalam build dan tanpa syarat (atau kami mengubah kedalaman pohon, yang tidak boleh dilakukan)

Itu identik dengan batasan kait, tetapi saya tidak berpikir orang-orang mengeluhkannya.

Selanjutnya, Widget hanya dapat digunakan di dalam build dan tanpa syarat (atau kami mengubah kedalaman pohon, yang tidak boleh dilakukan)

Itu identik dengan batasan kait, tetapi saya tidak berpikir orang-orang mengeluhkannya.

Tidak, itu tidak identik. Masalah yang disajikan di sini tampaknya terkait dengan kode yang _mempersiapkan_ widget _sebelum_ dibangun (kembali). Mempersiapkan status, dependensi, aliran, pengontrol, yang lainnya, dan menangani perubahan dalam struktur pohon. Tak satu pun dari ini harus ada dalam metode build, meskipun tersembunyi di balik satu panggilan fungsi.
Titik masuk untuk logika itu tidak boleh ada dalam metode build.

Memaksa saya untuk memasukkan logika inisialisasi dalam bentuk apa pun ke dalam metode build sama sekali tidak sama dengan "memaksa" saya untuk membuat pohon widget dalam metode build. Seluruh alasan untuk metode build adalah untuk mengambil status yang ada (set variabel) dan menghasilkan pohon widget yang kemudian dicat.

Sebaliknya, saya juga menentang memaksa saya untuk menambahkan kode yang membangun widget di dalam metode initState/didUpdateWidget.

Seperti sekarang, metode siklus hidup statefulwidget memiliki peran yang sangat jelas dan membuatnya sangat mudah dan lurus untuk memisahkan kode dengan masalah yang sama sekali berbeda.

Secara konseptual saya mulai memahami masalah yang sedang dijelaskan di sini, tetapi saya masih gagal untuk melihatnya sebagai masalah yang sebenarnya. Mungkin beberapa contoh nyata (yang bukan aplikasi penghitung) dapat membantu saya berubah pikiran.

Sebagai catatan tambahan, Riverpod , eksperimen terbaru saya, memiliki beberapa ide yang sangat mirip, tanpa kendala.

Misalnya, ini memecahkan:

Consumer(
  provider: provider,
  builder: (context, value) {
    return Consumer(
      provider: provider2,
      builder: (context, value2) {
        return Text('$value $value2');
      },
    );
  },
)

dengan memiliki:

Consumer(
  builder (context, watch) {
    final value = watch(provider);
    final value2 = watch(provider2);
  },
)

Di mana watch dapat dipanggil secara kondisional:

Consumer(
  builder: (context, watch) {
    final value = watch(provider);
    if (something) {
      final value2 = watch(provider2);
    }
  },
)

Kita bahkan dapat menyingkirkan Consumer seluruhnya dengan memiliki kelas dasar StatelessWidget / StatefulWidget kustom:

class Example extends ConsumerStatelessWidget {
  <strong i="21">@override</strong>
  Widget build(ConsumerBuildContext context) {
    final value = context.watch(provider);
    final value2 = context.watch(provider2);
  }
}

Masalah utamanya adalah, ini khusus untuk satu jenis objek, dan ini bekerja dengan mengandalkan fakta bahwa instance objek memiliki kode hash yang konsisten di seluruh pembangunan kembali.

Jadi kami masih jauh dari fleksibilitas kait

@rrousselGit Saya pikir tanpa memperluas kelas StatelessWidget / StatefulWidget dan membuat sesuatu seperti ConsumerStatelessWidget, dimungkinkan untuk memiliki sesuatu seperti context.watch dengan menggunakan metode ekstensi pada BuildContext class dan meminta penyedia menyediakan fungsi arloji dengan InheritedWidgets.

Itu topik yang berbeda. Tapi tl; dr, kami tidak dapat mengandalkan InheritedWidgets sebagai solusi untuk masalah ini: https://github.com/flutter/flutter/issues/30062

Untuk mengatasi masalah itu, menggunakan InheritedWidgets akan memblokir kami karena https://github.com/flutter/flutter/issues/12992 dan https://github.com/flutter/flutter/pull/33213

Secara konseptual saya mulai memahami masalah yang sedang dijelaskan di sini, tetapi saya masih gagal untuk melihatnya sebagai masalah yang sebenarnya.

Membandingkan Flutter dengan SwiftUI, bagi saya jelas bahwa ada masalah aktual, atau lebih tepatnya - hal-hal tidak sehebat yang seharusnya.

Mungkin sulit untuk dilihat, karena Flutter & lainnya bekerja keras untuk mengatasinya: kami memiliki pembungkus untuk setiap kasus tertentu: AnimatedBuilder, StreamBuilder, Konsumen, AnimatedOpacity, dll. StatefulWidget berfungsi dengan baik untuk mengimplementasikan utilitas kecil yang dapat digunakan kembali ini, tetapi levelnya terlalu rendah untuk komponen khusus domain yang tidak dapat digunakan kembali, di mana Anda mungkin memiliki banyak pengontrol teks, animasi, atau apa pun yang dibutuhkan logika bisnis. Solusi yang biasa adalah dengan menggigit peluru dan menulis semua boilerplate itu, atau membuat pohon penyedia dan pendengar yang dibangun dengan hati-hati. Tidak ada pendekatan yang memuaskan.

Seperti yang dikatakan @rrousselGit , di masa lalu (UIKit) kami dipaksa untuk mengelola UIViews kami secara manual (setara dengan RenderObjects), dan ingat untuk menyalin nilai dari model ke tampilan dan kembali, menghapus tampilan yang tidak digunakan, mendaur ulang, dan sebagainya. Ini bukan ilmu roket, dan banyak orang tidak melihat masalah lama ini, tapi saya pikir semua orang di sini akan setuju bahwa Flutter jelas memperbaiki situasi.
Dengan statefulness, masalahnya sangat mirip: itu membosankan, pekerjaan rawan kesalahan yang bisa diotomatisasi.

Dan, omong-omong, menurut saya hook tidak menyelesaikan masalah ini sama sekali. Hanya saja kait adalah satu-satunya pendekatan yang mungkin dilakukan tanpa mengubah bagian dalam flutter.

StatefulWidget bekerja sangat baik untuk mengimplementasikan utilitas kecil yang dapat digunakan kembali ini, tetapi levelnya terlalu rendah untuk komponen khusus domain yang tidak dapat digunakan kembali, di mana Anda mungkin memiliki banyak pengontrol teks, animasi, atau logika bisnis apa pun yang dibutuhkan.

Saya bingung ketika Anda mengatakan bahwa membangun komponen khusus domain yang tidak dapat digunakan kembali, Anda memerlukan widget tingkat tinggi. Biasanya justru sebaliknya.

AnimatedBuilder, StreamBuilder, Consumer, AnimatedOpacity adalah semua widget yang mengimplementasikan use case tertentu. Ketika saya membutuhkan widget yang memiliki logika yang sangat spesifik sehingga saya tidak dapat menggunakan widget level yang lebih tinggi ini, saat itulah saya turun ke api level yang lebih rendah sehingga saya dapat menulis kasus penggunaan spesifik saya sendiri. Yang disebut boilerplate menerapkan bagaimana widget unik saya mengelola kombinasi unik dari aliran, panggilan jaringan, pengontrol, dan yang lainnya.

Mengadvokasi kait, perilaku seperti kait atau bahkan hanya "otomatisasi" seperti mengatakan bahwa kita memerlukan widget tingkat rendah yang dapat menangani logika tingkat tinggi yang tidak dapat digunakan kembali yang ingin dimiliki siapa pun tanpa harus menulis apa yang disebut kode boilerplate.

Dengan statefulness, masalahnya sangat mirip: itu membosankan, pekerjaan rawan kesalahan yang bisa diotomatisasi.

Lagi. Anda ingin mengotomatiskan __"komponen khusus domain yang tidak dapat digunakan kembali, di mana Anda mungkin memiliki banyak pengontrol teks, animasi, atau apa pun yang dibutuhkan logika bisnis"__?!

Seperti yang dikatakan @rrousselGit , di masa lalu (UIKit) kami dipaksa untuk mengelola UIViews kami secara manual (setara dengan RenderObjects), dan ingat untuk menyalin nilai dari model ke tampilan dan kembali, menghapus tampilan yang tidak digunakan, mendaur ulang, dan sebagainya. Ini bukan ilmu roket, dan banyak orang tidak melihat masalah lama ini, tapi saya pikir semua orang di sini akan setuju bahwa Flutter jelas memperbaiki situasi.

Saya melakukan pengembangan ios dan Android 6-7 tahun yang lalu (sekitar waktu Android beralih ke desain material mereka) dan saya tidak ingat salah satu dari tampilan pengelolaan dan daur ulang ini menjadi masalah dan Flutter tampaknya tidak lebih baik atau lebih buruk. Saya tidak dapat berbicara tentang urusan saat ini, saya berhenti ketika Swift dan Kotlin diluncurkan.

Boilerplate yang terpaksa saya tulis di StatefulWidgets saya adalah sekitar 1% dari basis kode saya. Apakah rawan kesalahan? Setiap baris kode adalah sumber bug yang potensial, jadi tentu saja. Apakah itu rumit? 200 baris kode dari 15000? Saya benar-benar tidak berpikir begitu, tapi itu hanya pendapat saya. Pengontrol teks/animasi Flutter, node fokus semuanya memiliki masalah yang dapat diperbaiki, tetapi menjadi verbose bukanlah satu.

Saya sangat ingin tahu apa yang sedang dikembangkan orang sehingga mereka membutuhkan begitu banyak boilerplate.

Mendengarkan beberapa komentar di sini terdengar seperti mengelola 5 baris kode alih-alih 1 seperti 5 kali lebih sulit. Ini bukan.

Tidakkah Anda setuju bahwa alih-alih mengatur initState dan membuang untuk setiap AnimationController misalnya bisa lebih rawan kesalahan daripada hanya melakukannya sekali dan menggunakan kembali logika itu? Prinsip yang sama seperti menggunakan fungsi, dapat digunakan kembali. Saya setuju bahwa itu bermasalah untuk menempatkan kait di fungsi build, pasti ada cara yang lebih baik.

Benar-benar terasa seperti perbedaan antara mereka yang melihat dan tidak melihat masalah di sini adalah bahwa yang pertama telah menggunakan konstruksi seperti kait sebelumnya, seperti di React, Swift, dan Kotlin, dan yang terakhir belum, seperti bekerja di Java murni atau Android. Saya pikir satu-satunya cara untuk diyakinkan, menurut pengalaman saya, adalah mencoba kait dan melihat apakah Anda dapat kembali ke cara standar. Seringkali, banyak orang tidak bisa, sekali lagi, menurut pengalaman saya. Anda tahu itu ketika Anda menggunakannya.

Untuk itu, saya mendorong orang-orang yang skeptis untuk menggunakan flutter_hooks untuk proyek kecil dan melihat bagaimana tarifnya, lalu ulangi dengan cara default. Tidaklah cukup bahwa kita hanya membuat versi aplikasi untuk dibaca seperti dalam saran @Hixie (walaupun kita akan melakukannya juga tentu saja), saya percaya setiap orang harus menggunakan kait sendiri untuk melihat perbedaannya.

Tidaklah cukup bahwa kita hanya membuat versi aplikasi untuk dibaca seperti dalam saran @Hixie (walaupun kita akan melakukannya juga tentu saja), saya percaya setiap orang harus menggunakan kait sendiri untuk melihat perbedaannya.

Saya menyia-nyiakan hari untuk mencoba penyedia, bahkan lebih banyak hari mencoba blok, saya tidak menemukan salah satu dari mereka adalah solusi yang baik. Jika itu berhasil untuk Anda, bagus.

Agar saya bahkan mencoba solusi yang Anda usulkan untuk masalah yang Anda alami, Anda perlu menunjukkan kelebihannya. Saya melihat contoh dengan kait bergetar dan saya melihat implementasinya. Hanya tidak.

Kode pengurang boilerplate apa pun yang ditambahkan ke kerangka kerja, saya harap Stateful/StatelessWidgets dibiarkan tidak berubah. Tidak banyak lagi yang bisa saya tambahkan ke percakapan ini.

Mari kita mulai lagi, di dunia hipotetis di mana kita dapat mengubah Dart, tanpa berbicara tentang kait.

Masalah yang diperdebatkan adalah:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

Kode ini tidak dapat dibaca.

Kami dapat memperbaiki masalah keterbacaan dengan memperkenalkan kata kunci baru yang mengubah sintaks menjadi:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

Kode ini secara signifikan lebih mudah dibaca, tidak terkait dengan kait, dan tidak mengalami keterbatasan.
Keuntungan keterbacaan tidak banyak tentang jumlah baris, tetapi pemformatan dan lekukan.


Tapi bagaimana bila Builder bukan widget root?

Contoh:

Widget build(context) {
  return Scaffold(
    body: StreamBuilder(
      stream: stream,
      builder: (context, value) {
        return Consumer<Value2>(
          builder: (context, value2, child) {
            return Text('${value.data} $value2');
          },
        );
      },
    ),
  );
}

Kita bisa memiliki:

Widget build(context) {
  return Scaffold(
    body: {
      final value = keyword StreamBuilder(stream: stream);
      final value2 = keyword Consumer<Value2>();
      return Text('${value.data} $value2');
    }
  );
}

Tapi bagaimana ini berhubungan dengan masalah penggunaan kembali?

Alasan mengapa ini terkait adalah, Pembangun secara teknis adalah cara untuk menggunakan kembali logika keadaan. Tetapi masalah mereka adalah, mereka membuat kode tidak terlalu mudah dibaca jika kami berencana menggunakan _many_ builder, seperti dalam komentar ini https://github.com/flutter/flutter/issues/51752#issuecomment -669626522

Dengan sintaks baru ini, kami memperbaiki masalah keterbacaan. Dengan demikian, kita dapat mengekstrak lebih banyak hal ke dalam Builder.

Jadi misalnya, useFilter disebutkan dalam komentar ini dapat berupa:

FilterBuilder(
  debounceDuration: const Duration(seconds: 2),
  builder: (context, filter) {
    return TextField(onChange: (value) => filter.value = value);
  }
)

Yang kemudian dapat kita gunakan dengan kata kunci baru:

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    final filter = keyword FilterBuilder(debounceDuration: const Duration(seconds: 2));
    final userId = keyword Consumer(authProvider).userId;
    final chatId = keyword Consumer(selectedChatProvider);
    final chat = keyword QueryBuilder(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

Bagaimana dengan "ekstrak sebagai fungsi" yang kita bicarakan dengan kait, untuk membuat kait/Pembuat khusus?

Kita dapat melakukan hal yang sama dengan kata kunci tersebut, dengan mengekstrak kombinasi Builder dalam suatu fungsi:

Builder<Chat> ChatBuilder()  {
  final filter = keyword FilterBuilder(debounceDuration: const Duration(seconds: 2));
  final userId = keyword Consumer(authProvider).userId;
  final chatId = keyword Consumer(selectedChatProvider);
  final chat = keyword QueryBuilder(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

  return Builder(chat);
}

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="21">@override</strong>
  Widget build(BuildContext context) {
    final chat = keyword ChatBuilder();

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

Jelas, tidak banyak pemikiran yang diberikan dalam semua implikasi dari sintaks tersebut. Tapi itulah ide dasarnya.


Kait adalah fitur ini.
Keterbatasan kait ada karena diimplementasikan sebagai paket daripada fitur bahasa.

Dan kata kuncinya adalah use , sehingga keyword StreamBuilder menjadi use StreamBuilder , yang pada akhirnya diimplementasikan sebagai useStream

Kode ini secara signifikan lebih mudah dibaca

Saya pikir ini masalah pendapat. Saya setuju bahwa beberapa orang berpikir bahwa versi yang Anda gambarkan lebih mudah dibaca lebih baik; secara pribadi saya lebih suka versi tanpa sihir yang jauh lebih eksplisit. Tapi saya tidak keberatan untuk membuat gaya kedua menjadi mungkin.

Yang mengatakan, langkah selanjutnya adalah mengerjakan aplikasi @TimWhiting (https://github.com/TimWhiting/local_widget_state_approaches/blob/master/lib/stateful/counter.dart) untuk menjadikannya sesuatu yang memiliki semua masalah yang ingin kita selesaikan.

Untuk apa nilainya, https://github.com/flutter/flutter/issues/51752#issuecomment -670959424 cukup sejajar dengan inspirasi untuk Hooks in React. Pola Builder tampaknya identik dengan pola Render Props yang dulu lazim di React (tetapi mengarah ke pohon yang sama dalam). Kemudian @trueadm menyarankan gula sintaks untuk Render Props, dan kemudian mengarah ke Hooks (untuk menghapus overhead runtime yang tidak perlu).

`Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}`

Jika keterbacaan dan lekukan adalah masalahnya, ini dapat ditulis ulang sebagai

  <strong i="8">@override</strong>
  Widget build(context) {
    return ValueListenableBuilder<String>(
      valueListenable: someValueListenable,
      builder: (context, value, _) => buildStreamBuilder(value),
    );
  }

  StreamBuilder<int> buildStreamBuilder(String value) => StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) => buildTweenAnimationBuilder(value, value2),
      );

  Widget buildTweenAnimationBuilder(String value, AsyncSnapshot<int> value2) =>
      TweenAnimationBuilder<double>(
        duration: Duration(milliseconds: 500),
        tween: Tween(),
        builder: (context, value3, _) => Text('$value $value2 $value3'),
      );

Jika fungsi bukan milik Anda, atau Anda perlu dapat digunakan kembali, ekstraklah sebagai widget

class NewWidget extends StatelessWidget {
  var someValueListenable;

  var someStream;

  <strong i="12">@override</strong>
  Widget build(context) {
    return ValueListenableBuilder<String>(
      valueListenable: someValueListenable,
      builder: (context, value, _) {
        return MyStreamedWidget(value, someStream);
      },
    );
  }
}

class MyStreamedWidget extends StatelessWidget {
  const MyStreamedWidget(
    this.value,
    this.someStream, {
    Key key,
  }) : super(key: key);

  final String value;
  final Stream someStream;

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: someStream,
      builder: (context, value2) => MyAnimatedWidget(value, value2),
    );
  }
}

class MyAnimatedWidget extends StatelessWidget {
  final String value;
  final AsyncSnapshot<int> value2;

  const MyAnimatedWidget(
    this.value,
    this.value2, {
    Key key,
  }) : super(key: key);

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
      tween: Tween(),
      builder: (context, value3, _) {
        return Text('$value $value2 $value3');
      },
    );
  }
}

Tidak ada dalam contoh Anda yang menjamin kata kunci atau fitur baru.

Aku tahu apa yang akan kamu katakan. Variabel 'nilai' harus melewati semua widget/panggilan fungsi, tapi itu hanya hasil dari bagaimana Anda merancang aplikasi Anda. Saya memecah kode saya menggunakan keduanya dengan metode "membangun" dan widget khusus, tergantung pada kasus penggunaan, dan tidak pernah harus meneruskan variabel yang sama ke rantai tiga panggilan.

Kode yang dapat digunakan kembali dapat digunakan kembali jika bergantung sesedikit mungkin pada efek samping eksternal (seperti InheritedWidgets, atau (semi)status global).

@Rudiksz Saya tidak berpikir Anda menambahkan apa pun ke diskusi di sini. Kami menyadari strategi untuk mengurangi masalah ini karena kami melakukannya sepanjang hari. Jika Anda tidak merasa itu masalah, maka Anda dapat terus menggunakan hal-hal sebagaimana adanya dan ini tidak mempengaruhi Anda sama sekali.

Jelas ada banyak orang yang melihat ini sebagai titik sakit mendasar, dan hanya bersepeda bolak-balik tidak ada gunanya. Anda tidak akan, melalui berbagai argumen, meyakinkan orang bahwa mereka tidak menginginkan apa yang mereka inginkan atau mengubah pikiran siapa pun di sini. Setiap orang dalam diskusi ini jelas memiliki ratusan atau ribuan jam di Flutter, dan tidak diharapkan bahwa kita semua harus menyepakati banyak hal.

Saya pikir ini masalah pendapat. Saya setuju bahwa beberapa orang berpikir bahwa versi yang Anda gambarkan lebih mudah dibaca lebih baik; secara pribadi saya lebih suka versi tanpa sihir yang jauh lebih eksplisit. Tapi saya tidak keberatan untuk membuat gaya kedua menjadi mungkin.

Jika itu masalah pendapat, saya kira itu cukup miring ke satu arah.

  1. Keduanya memiliki sihir. Saya tidak selalu tahu apa yang dilakukan oleh pembangun ini secara internal. Versi non-ajaib menulis boilerplate yang sebenarnya di dalam builder ini. Menggunakan mixin SingleAnimationTIckerProvider juga ajaib untuk 95% pengembang Flutter.
  2. Satu mengaburkan nama variabel yang sangat penting yang akan digunakan nanti di pohon, yaitu value1 dan value2 , yang lain memilikinya di depan dan di tengah di atas build. Ini adalah kemenangan parsing/pemeliharaan yang jelas.
  3. Satu memiliki 6 tingkat lekukan bahkan sebelum pohon widget dimulai, yang lain memiliki 0
  4. Salah satunya adalah 5 garis vertikal, yang lain adalah 15
  5. Satu menunjukkan bagian konten yang sebenarnya dengan jelas (Teks ()) yang lain menyembunyikannya, bersarang, jauh ke bawah ke dalam pohon. Kemenangan parsing jelas lainnya.

Secara umum saya bisa melihat ini mungkin masalah selera. Tetapi Flutter secara khusus memiliki masalah dengan jumlah baris, lekukan, dan rasio signal:nois secara umum. Sementara saya benar-benar _suka_ kemampuan untuk secara deklaratif membentuk pohon dalam kode Dart, itu dapat menyebabkan beberapa kode yang sangat tidak dapat dibaca/verbose, terutama ketika Anda mengandalkan beberapa lapisan pembangun yang dibungkus. Jadi dalam konteks Flutter itu sendiri, di mana kita terus-menerus bertarung dalam pertempuran ini, pengoptimalan semacam ini adalah fitur yang mematikan, karena ini memberi kita alat yang sangat bagus untuk memerangi masalah verbositas umum yang agak meluas ini.

TL;DR - Apa pun yang secara signifikan mengurangi lekukan dan jumlah garis di Flutter, bernilai ganda, karena jumlah garis dan lekukan yang melekat pada Flutter umumnya tinggi.

@Rudiksz Saya tidak berpikir Anda menambahkan apa pun ke diskusi di sini. Kami menyadari strategi untuk mengurangi masalah ini karena kami melakukannya sepanjang hari. Jika Anda tidak merasa itu masalah, maka Anda dapat terus menggunakan hal-hal sebagaimana adanya dan ini tidak mempengaruhi Anda sama sekali.

Kecuali jika itu adalah perubahan dalam kerangka inti maka itu mempengaruhi saya, bukan?

Jelas ada banyak orang yang melihat ini sebagai titik sakit mendasar, dan hanya bersepeda bolak-balik tidak ada gunanya. Anda tidak akan, melalui berbagai argumen, meyakinkan orang bahwa mereka tidak menginginkan apa yang mereka inginkan atau mengubah pikiran siapa pun di sini. Setiap orang dalam diskusi ini jelas memiliki ratusan atau ribuan jam di Flutter, dan tidak diharapkan bahwa kita semua harus menyepakati banyak hal.

Benar, kuda ini sudah dipukuli sampai mati berkali-kali, jadi aku tidak akan terjebak untuk menjawab komentar lagi.

Builder secara teknis adalah cara untuk menggunakan kembali logika status. Tetapi masalah mereka adalah, mereka membuat kode tidak terlalu mudah dibaca.

Ini menyatakannya dengan sempurna. Untuk memikirkannya dalam istilah Flutter, kita membutuhkan pembuat satu baris.

Builder yang tidak memerlukan lusinan tab dan garis, tetapi masih mengizinkan beberapa boilerplate khusus untuk dihubungkan ke siklus hidup widget.

Mantra "semuanya adalah widget" secara khusus bukanlah hal yang baik di sini. Kode yang relevan dalam builder biasanya hanya props pengaturannya, dan stateful yang dikembalikannya yang dibutuhkan build fxn. Setiap jeda baris dan tab pada dasarnya tidak ada gunanya.

Kecuali jika itu adalah perubahan dalam kerangka inti maka itu mempengaruhi saya, bukan?

@Rudiksz Saya rasa tidak ada yang mengusulkan agar kami mengubah widget Stateful. Anda selalu dapat menggunakannya dalam bentuk saat ini jika Anda mau. Solusi apa pun yang mungkin kami temukan akan menggunakan widget Stateful tanpa perubahan, atau jenis widget lain sepenuhnya. Kami tidak mengatakan bahwa widget Stateful buruk, hanya saja kami menginginkan jenis Widget lain yang memungkinkan status widget yang lebih dapat dikomposisi. Anggap saja sebagai widget Stateful yang alih-alih satu objek status yang terkait dengannya berisi beberapa objek status, dan satu fungsi build yang terpisah tetapi memiliki akses ke objek status tersebut. Dengan cara ini, Anda dapat menggunakan kembali bit status umum (bersama dengan logika status yang terkait dengan status tersebut) dengan initState dan dispose sudah diterapkan untuk Anda. Pada dasarnya keadaan lebih modular, yang dapat disusun dengan cara yang berbeda dalam situasi yang berbeda. Sekali lagi, ini bukan proposal yang konkret, tapi mungkin cara lain untuk memikirkannya. Mungkin itu bisa berubah menjadi solusi yang lebih seperti flutter , tapi saya tidak tahu.

Yang mengatakan, langkah selanjutnya adalah mengerjakan aplikasi @TimWhiting (https://github.com/TimWhiting/local_widget_state_approaches/blob/master/lib/stateful/counter.dart) untuk menjadikannya sesuatu yang memiliki semua masalah yang ingin kita selesaikan.

Ini rumit karena masalah ini pada dasarnya adalah salah satu kematian dengan seribu luka. Itu hanya menambah mengasapi dan mengurangi keterbacaan di seluruh basis kode. Dampaknya terasa paling buruk di widget kecil, di mana seluruh kelas <100 baris, dan setengahnya dihabiskan untuk mengelola pengontrol animator. Jadi saya tidak tahu apa yang akan ditunjukkan oleh 30 contoh ini, sedangkan 1 tidak.

Ini benar-benar perbedaan antara ini:

<strong i="10">@override</strong>
  Widget build(BuildContext context) {
    final controller = get AnimationController(vsync: this, duration: widget.duration);
    //do stuff
  }

Dan ini:

  AnimationController _controller;

  <strong i="14">@override</strong>
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
  }

  <strong i="15">@override</strong>
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.duration != oldWidget.duration) {
      _controller.duration = widget.duration;
    }
  }

  <strong i="16">@override</strong>
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  <strong i="17">@override</strong>
  Widget build(BuildContext context) {
    //Do Stuff
  }

Tidak ada cara yang lebih baik untuk menggambarkan dari ini. Anda dapat memperluas kasus penggunaan ini ke objek tipe pengontrol apa pun. AnimatorController, FocusController, dan TextEditingController mungkin adalah yang paling umum dan menjengkelkan untuk dikelola dalam penggunaan sehari-hari. Sekarang bayangkan 50, atau 100 dari ini tersebar di seluruh basis kode saya.

  • Anda memiliki sekitar 1000-2000 baris yang bisa hilang begitu saja.
  • Anda mungkin memiliki lusinan bug dan RTE (di berbagai titik pengembangan) yang tidak perlu ada karena beberapa penggantian atau lainnya hilang.
  • Anda memiliki widget, yang ketika dilihat dengan mata dingin, jauh lebih sulit untuk dilihat sekilas. Saya perlu membaca masing-masing penggantian ini, saya tidak bisa hanya berasumsi bahwa itu adalah boilerplate.

Dan Anda tentu saja dapat memperluas ini ke pengontrol khusus. Seluruh konsep menggunakan pengontrol kurang menarik di Flutter karena Anda tahu Anda harus mem-bootstrap, mengelola, dan menghancurkannya seperti ini, yang mengganggu dan rawan bug. Ini mengarahkan Anda untuk menghindari membuat sendiri dan bukannya membuat StatefulWidgets/Builders kustom. Akan lebih baik jika objek tipe pengontrol lebih mudah digunakan dan lebih kuat, karena pembangun memiliki masalah keterbacaan (atau setidaknya, secara signifikan lebih bertele-tele dan sarat spasi).

Ini rumit

Yup, desain API itu rumit. Selamat Datang di hidupku.

Jadi saya tidak tahu apa yang akan ditunjukkan oleh 30 contoh ini, sedangkan 1 tidak.

Bukan 30 contoh yang akan membantu, tetapi 1 contoh yang cukup rumit sehingga tidak dapat disederhanakan dengan cara yang kemudian Anda katakan "baik, tentu, untuk contoh ini berhasil, tetapi tidak untuk contoh _real_".

Bukan 30 contoh yang akan membantu, itu 1 contoh yang cukup rumit sehingga tidak dapat disederhanakan dengan cara yang kemudian Anda katakan "baik, tentu, untuk contoh ini berhasil, tetapi tidak untuk contoh nyata".

Saya sudah mengatakannya beberapa kali, tetapi cara menilai hook seperti itu tidak adil.
Kait bukan tentang membuat sesuatu yang sebelumnya tidak mungkin menjadi mungkin. Ini tentang menyediakan API yang konsisten untuk memecahkan masalah semacam ini.

Meminta aplikasi yang menunjukkan sesuatu yang tidak dapat disederhanakan secara berbeda adalah menilai seekor ikan dari kemampuannya memanjat pohon.

Kami tidak hanya mencoba menilai kait, kami mencoba mengevaluasi berbagai solusi untuk melihat apakah ada solusi yang memenuhi kebutuhan semua orang.

(Saya ingin tahu dengan cara apa Anda akan mengevaluasi proposal yang berbeda di sini, jika tidak dengan benar-benar menulis aplikasi di setiap proposal dan membandingkannya. Metrik evaluasi apa yang akan Anda usulkan sebagai gantinya?)

Cara yang tepat untuk menilai solusi untuk masalah ini bukanlah aplikasi (karena setiap penggunaan API secara individu akan diabaikan seperti contoh di sini).

Apa yang harus kita nilai dari solusi yang diusulkan adalah:

  • Apakah kode yang dihasilkan secara objektif lebih baik daripada sintaks default?

    • Apakah itu menghindari kesalahan?

    • Apakah lebih mudah dibaca?

    • Apakah lebih mudah untuk menulis?

  • Seberapa dapat digunakan kembali kode yang dihasilkan?
  • Berapa banyak masalah yang dapat diselesaikan dengan API ini?

    • Apakah kita kehilangan beberapa manfaat untuk beberapa masalah tertentu?

Saat dievaluasi pada kisi ini, proposal Property / addDispose mungkin memiliki "apakah kode yang dihasilkan lebih baik?" skor, tetapi mengevaluasi buruk terhadap penggunaan kembali dan fleksibilitas.

Saya tidak tahu bagaimana menjawab pertanyaan-pertanyaan itu tanpa benar-benar melihat setiap proposal dalam penggunaan nyata.

Mengapa?

Saya tidak perlu membuat aplikasi menggunakan Property untuk mengetahui bahwa proposal ini akan mengalami kesulitan dalam menghasilkan kode yang benar-benar dapat digunakan kembali dan memecahkan banyak masalah.

Kami dapat mengambil * Builder yang ada dan mencoba mengimplementasikannya kembali menggunakan solusi yang diusulkan.
Kami juga dapat mencoba dan mengimplementasikan kembali hook yang terdaftar di thread ini, atau beberapa hook yang dibuat di komunitas React (ada banyak kompilasi hook yang tersedia secara online).

Saya tidak perlu membuat aplikasi menggunakan Property untuk mengetahui bahwa proposal ini akan mengalami kesulitan dalam menghasilkan kode yang benar-benar dapat digunakan kembali dan memecahkan banyak masalah.

Sayangnya, saya tidak membagikan naluri Anda di sini (seperti yang ditunjukkan oleh fakta bahwa menurut saya Properti (https://github.com/flutter/flutter/issues/51752#issuecomment-667737471) berfungsi dengan baik sampai Anda mengatakan bahwa itu harus menangani nilai baik dari widget dan status lokal dengan API yang sama, yang saya tidak sadari merupakan kendala sampai Anda memunculkannya). Jika saya memberikan versi Properti yang menyelesaikan masalah itu juga, apakah itu pasti akan baik-baik saja, atau akan ada masalah baru yang tidak tercakup? Tanpa target yang kita semua sepakati adalah targetnya, saya tidak tahu untuk apa kita merancang solusi.

Kami dapat mengambil * Builder yang ada dan mencoba mengimplementasikannya kembali menggunakan solusi yang diusulkan.

Jelas bukan _any_. Misalnya, Anda memberikan satu di OP, dan ketika saya membuat proposal Properti pertama saya (https://github.com/flutter/flutter/issues/51752#issuecomment-664787791) Anda menunjukkan masalah dengannya yang tidak diilustrasikan oleh Pembangun asli.

Kami juga dapat mencoba dan mengimplementasikan kembali hook yang terdaftar di thread ini, atau beberapa hook yang dibuat di komunitas React (ada banyak kompilasi hook yang tersedia secara online).

Saya tidak keberatan di mana kita mulai. Jika Anda memiliki contoh yang sangat bagus yang menurut Anda menggambarkan masalah dan menggunakan kait, maka bagus, mari tambahkan itu ke repositori @TimWhiting . Intinya adalah menerapkan hal yang sama dalam berbagai cara berbeda, saya tidak keberatan dari mana ide itu berasal.

Bukan 30 contoh yang akan membantu, tetapi 1 contoh yang cukup rumit sehingga tidak dapat disederhanakan dengan cara yang kemudian Anda katakan "baik, tentu, untuk contoh ini berhasil, tetapi tidak untuk contoh _real_".

Tidak akan pernah ada yang lebih rumit daripada keinginan sederhana untuk menggunakan AnimatorController (atau komponen stateful lain yang dapat digunakan kembali yang dapat Anda pikirkan) tanpa pembangun yang tidak dapat dibaca atau sekelompok boilerplate siklus hidup yang rawan bug.

Belum ada solusi yang diusulkan yang membahas manfaat keterbacaan dan ketahanan yang diminta, dengan cara tujuan umum.

Saya bersikeras pada fakta bahwa _any_ builder akan melakukannya, karena masalah ini dapat diubah namanya menjadi "Kami membutuhkan gula sintaksis untuk Pembangun" dan mengarah ke diskusi yang sama.

Semua argumen lain yang dibuat (seperti membuat dan membuang AnimationController ) dibuat atas dasar bahwa argumen tersebut juga dapat diekstraksi menjadi pembangun:

Widget build(context) {
  return AnimationControllerBuilder(
    duration: Duration(seconds: 2),
    builder: (context, animationController) {

    }
  );
}

Pada akhirnya, saya pikir contoh sempurna adalah mencoba mengimplementasikan kembali StreamBuilder secara keseluruhan, dan mengujinya dalam skenario yang berbeda:

  • alirannya berasal dari Widget
  • // dari Widget yang Diwarisi
  • dari negara bagian setempat

dan uji setiap kasus individual terhadap "aliran dapat berubah seiring waktu", jadi:

  • didUpdateWidget dengan aliran baru
  • Widget yang Diwarisi diperbarui
  • kami memanggil setState

Ini saat ini tidak dapat diselesaikan dengan Property atau onDispose

@esDotDev Bisakah Anda menghitung "manfaat keterbacaan dan ketahanan yang diminta"? Jika seseorang membuat proposal yang menangani AnimationController dengan manfaat keterbacaan dan ketahanan itu, maka kita sudah selesai di sini?

@rrousselGit Saya tidak menganjurkan Properti, seperti yang Anda katakan sebelumnya bahwa itu tidak menyelesaikan masalah Anda. Tetapi jika seseorang membuat solusi yang melakukan semua yang dilakukan StreamBuilder, tetapi tanpa lekukan, apakah itu? Anda akan senang?

Tetapi jika seseorang membuat solusi yang melakukan semua yang dilakukan StreamBuilder, tetapi tanpa lekukan, apakah itu? Anda akan senang?

Kemungkinan besar, ya

Tentu saja kita perlu membandingkan solusi ini dengan solusi lain. Tapi itu akan mencapai tingkat yang dapat diterima.

@esDotDev Bisakah Anda menghitung "manfaat keterbacaan dan ketahanan yang diminta"?

Kekokohan dalam hal itu dapat sepenuhnya merangkum boilerplate di sekitar dependensi dan siklus hidup. yaitu. Saya tidak perlu memberi tahu fetchUser setiap saat, bahwa itu mungkin harus dibangun kembali ketika id berubah, ia tahu untuk melakukannya secara internal. Seharusnya tidak memberi tahu Animasi untuk membuang dirinya sendiri setiap kali Widget induknya dibuang, dll. (Saya tidak sepenuhnya mengerti apakah Properti dapat melakukan ini). Ini menghentikan pengembang dari membuat kesalahan untuk tugas-tugas hafalan, di seluruh basis kode (salah satu manfaat utama menggunakan Pembangun saat ini imo)

Keterbacaan adalah bahwa kita bisa mendapatkan hal stateful dengan satu baris kode non-indentasi, dan variabel untuk hal tersebut diangkat dan terlihat jelas.

@esDotDev Jika seseorang membuat proposal yang menangani AnimationController dengan manfaat keterbacaan dan ketahanan itu, maka kita sudah selesai di sini?

Jika maksud Anda secara khusus AnimationController no. Jika maksud Anda objek seperti AnimationController/FocusController/TextEditingController, maka ya.

Memiliki API seperti fungsi yang mengembalikan nilai yang memiliki waktu hidup yang ditentukan yang tidak jelas adalah

Saya pikir ini adalah kesalahpahaman kunci. Masa pakai hook jelas, karena menurut definisi mereka adalah sub-status. Mereka _selalu_ ada untuk seumur hidup Negara yang "menggunakan" mereka. Itu tidak bisa lebih jelas. Sintaksnya mungkin aneh dan asing, tapi jelas tidak kurang jelas.

Mirip dengan bagaimana masa pakai TweenAnimationBuilder() juga jelas. Itu akan hilang ketika induknya pergi. Seperti widget anak, ini adalah status anak. Mereka adalah "komponen" negara yang sepenuhnya independen, kami dapat merakit dan menggunakan kembali dengan mudah dan kami secara eksplisit tidak mengatur masa pakainya karena kami ingin itu secara alami terikat ke keadaan di mana ia dideklarasikan, sebuah fitur bukan bug.

@esDotDev

dll

Bisakah Anda lebih spesifik? (Inilah sebabnya saya menyarankan untuk membuat aplikasi demo yang mencakup semua basis. Saya terus berpikir itulah cara terbaik untuk melakukan ini.) Apakah ada fitur yang penting selain hanya memanggil penginisialisasi kecuali konfigurasi telah berubah dan secara otomatis membuang sumber daya yang dialokasikan ketika elemen host dibuang?

Objek seperti TextEditingController

Bisakah Anda lebih spesifik? Apakah TextEditingController lebih rumit daripada AnimationController dalam beberapa hal?


@rrousselGit

Tetapi jika seseorang membuat solusi yang melakukan semua yang dilakukan StreamBuilder, tetapi tanpa lekukan, apakah itu? Anda akan senang?

Kemungkinan besar, ya

Berikut adalah solusi yang melakukan semua yang dilakukan StreamBuilder, tanpa indentasi apa pun:

Widget build(context) {
  var result = Text('result:');
  var builder1 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Row(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream1, builder: builder1);
  var builder2 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Column(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream2, builder: builder2);
}

Saya kira ini melanggar beberapa batasan lain. Itulah sebabnya saya lebih suka memiliki sesuatu yang kita semua bisa setujui adalah deskripsi lengkap dari masalah sebelum kita mencoba menyelesaikannya.

Ini hanyalah kendala yang sama yang dimiliki pembangun @Hixie tidak ada yang meminta lebih dari itu. Builder dapat mengikat ke widget.whatever, builder dapat sepenuhnya mengelola status internal apa pun yang diperlukan dalam konteks pohon widget. Hanya itu yang bisa dilakukan oleh hook, dan semua yang diminta oleh siapa pun untuk keadaan mikro atau apa pun yang Anda ingin menyebutnya.

Bisakah Anda lebih spesifik? Apakah TextEditingController lebih rumit daripada AnimationController dalam beberapa hal?

Tidak, tetapi mungkin melakukan hal yang berbeda dalam init/buang, atau itu akan mengikat ke properti yang berbeda, dan saya ingin merangkum boilerplate spesifik itu.

@esDotDev Jadi Anda menginginkan hal yang sama sebagai pembangun, tetapi tanpa indentasi, dan pada satu baris (dikurangi panggilan balik pembangun itu sendiri, mungkin)? Contoh yang baru saja saya posting ( https://github.com/flutter/flutter/issues/51752#issuecomment-671004483 ) apakah itu dengan pembangun hari ini, jadi mungkin ada kendala tambahan di luar itu?

(FWIW, saya tidak berpikir pembangun, atau sesuatu seperti pembangun tetapi pada satu baris, adalah solusi yang baik, karena mereka mengharuskan setiap tipe data memiliki pembuatnya sendiri yang dibuat untuk itu; tidak ada cara yang baik untuk hanya membangun satu dengan cepat .)

(FWIW, saya tidak berpikir pembangun, atau sesuatu seperti pembangun tetapi pada satu baris, adalah solusi yang baik, karena mereka mengharuskan setiap tipe data memiliki pembuatnya sendiri yang dibuat untuk itu; tidak ada cara yang baik untuk hanya membangun satu dengan cepat .)

Saya tidak mengerti apa artinya ini. Bisakah Anda mengulanginya? 🙏

Anda harus membuat AnimationBuilder untuk Animations dan StreamBuilder untuk Streams dan seterusnya. Alih-alih hanya memiliki satu Builder dan mengatakan "inilah cara Anda mendapatkan yang baru, inilah cara Anda membuangnya, inilah cara Anda mengeluarkan data" dll saat Anda membuat StatefulWidget.

Saya kira ini melanggar beberapa batasan lain. Itulah sebabnya saya lebih suka memiliki sesuatu yang kita semua bisa setujui adalah deskripsi lengkap dari masalah sebelum kita mencoba menyelesaikannya.

Saya pikir itu cukup jelas melanggar setiap permintaan untuk kode yang dapat dibaca, yang pada akhirnya menjadi tujuan di sini, jika tidak, kita semua hanya akan menggunakan satu juta pembangun yang diketik secara khusus, membuat sarang selamanya, dan menyebutnya sehari.

Saya pikir apa yang diminta adalah sesuatu seperti (bersabarlah, saya tidak, banyak menggunakan Streams):

Widget build(context) {
   var snapshot1 = get AsyncSnapshot<int>(stream1);
   var snapshot2 = get AsyncSnapshot<int>(stream2);
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

Ini semua kodenya. Tidak akan ada lagi, karena Stream dibuat untuk kita, Stream dibuang untuk kita, kita tidak bisa menembak diri kita sendiri DAN kodenya jauh lebih mudah dibaca.

Anda harus membuat AnimationBuilder untuk Animations dan StreamBuilder untuk Streams dan seterusnya.

Saya tidak melihat itu sebagai masalah. Kami sudah memiliki RestorableInt vs RestorableString vs RestorableDouble

Dan obat generik dapat menyelesaikannya:

GenericBuilder<Stream<int>>(
  create: (ref) {
    var controller = StreamController<int>();
    ref.onDispose(controller.close);
    return controller.stream;
  }
  builder: (context, Stream<int> stream) {

  }
)

Demikian pula, Flutter atau Dart dapat menyertakan antarmuka Disposable jika itu benar-benar menjadi masalah.

@esDotDev

Saya pikir apa yang diminta adalah beberapa hal seperti:

Itu akan melanggar beberapa batasan yang cukup masuk akal yang telah dicantumkan orang lain (misalnya @Rudiksz), yaitu menjamin bahwa tidak ada kode inisialisasi yang pernah terjadi selama panggilan ke metode build.

@rrousselGit

Saya tidak melihat itu sebagai masalah. Kami sudah memiliki RestorableInt vs RestorableString vs RestorableDouble

Dan kami memiliki AnimationBuilder dan StreamBuilder dan sebagainya, ya. Dalam kedua kasus itu sangat disayangkan.

Pembuat Generik

Itu mirip dengan apa yang saya usulkan untuk Properti, tetapi jika saya memahami kekhawatiran Anda di sana, Anda percaya itu terlalu bertele-tele.

Sebelumnya, Anda mengatakan bahwa jika seseorang membuat solusi yang melakukan semua yang dilakukan StreamBuilder, tetapi tanpa lekukan, Anda mungkin akan senang. Anda belum mengomentari upaya saya melakukan itu (https://github.com/flutter/flutter/issues/51752#issuecomment-671004483). Apakah Anda senang dengan solusi itu?

@esDotDev

Saya pikir apa yang diminta adalah beberapa hal seperti:

Itu akan melanggar beberapa batasan yang cukup masuk akal yang telah dicantumkan orang lain (misalnya @Rudiksz), yaitu menjamin bahwa tidak ada kode inisialisasi yang pernah terjadi selama panggilan ke metode build.

Kode ini tidak penting dalam build. Bagian yang penting adalah

  1. Saya tidak dipaksa untuk membuat indentasi pohon saya, atau menambahkan satu ton baris tambahan.
  2. Kode siklus hidup khusus untuk hal ini dienkapsulasi.

Ini akan luar biasa:

AsyncSnapshot<int> snapshot1 = createLifecycleState(widget.stream1);
AsyncSnapshot<int> snapshot2 = createLifecycleState(widget.stream2);
AniamtorController animator = createLifecycleState(duration: Duration(seconds: 1), (a)=>a.forward());

Widget build(context) {
   return Opacity(opacity: animator.value, child: Column(children: [Text(snapshot1.data), Text(snapshot2.data)]));
}

Atau, tidak sesingkat itu, tetapi masih jauh lebih mudah dibaca daripada menggunakan pembangun, dan lebih sedikit verbose & rawan kesalahan daripada melakukannya secara langsung:

AsyncSnapshot<int> stream1;
AsyncSnapshot<int> stream2;
<strong i="18">@override</strong> 
void initState(){
    snapshot1 = createLifecycleState(widget.stream1);
    snapshot2 = createLifecycleState(widget.stream2);
   super.initState();
}

Widget build(context) {
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

Saya tidak mengerti mengapa kita terus kembali ke verbositas.
Saya secara eksplisit mengatakan berkali-kali bahwa itu bukan masalah dan masalahnya adalah kegunaan kembali vs keterbacaan vs fleksibilitas.

Saya bahkan membuat kisi untuk mengevaluasi solusi https://github.com/flutter/flutter/issues/51752#issuecomment -671000137 dan kasus uji https://github.com/flutter/flutter/issues/51752#issuecomment - 671002248


Sebelumnya, Anda mengatakan bahwa jika seseorang membuat solusi yang melakukan semua yang dilakukan StreamBuilder, tetapi tanpa lekukan, Anda mungkin akan senang. Anda belum mengomentari upaya saya melakukan itu (#51752 (komentar)). Apakah Anda senang dengan solusi itu?

Itu mencapai tingkat fleksibilitas minimum yang dapat diterima.

Mengevaluasinya sesuai https://github.com/flutter/flutter/issues/51752#issuecomment -671000137 memberikan:

  • Apakah kode yang dihasilkan secara objektif lebih baik daripada sintaks default?

    • Apakah itu menghindari kesalahan?

      _Sintaks default (StreamBuilder tanpa peretasan) kurang rawan kesalahan. Solusi ini tidak menghindari kesalahan, itu menciptakan beberapa_

    • Apakah lebih mudah dibaca?

      _Jelas tidak lebih mudah dibaca_

    • Apakah lebih mudah untuk menulis?

      _Tidak mudah untuk menulis_

  • Seberapa dapat digunakan kembali kode yang dihasilkan?
    _StreamBuilder tidak terikat dengan Widget/Negara/siklus hidup, jadi pass ini_
  • Berapa banyak masalah yang dapat diselesaikan dengan API ini?
    _Kita bisa membuat custom builder, dan menggunakan pola ini. Jadi ini lulus_. ✅
  • Apakah kita kehilangan beberapa manfaat untuk beberapa masalah tertentu?
    _Tidak, sintaksnya relatif konsisten_. ✅
  1. Fitur IMO ini dapat diperluas ke semua widget builder, termasuk LayoutBuilder misalnya.
  2. Perlu ada cara untuk menonaktifkan mendengarkan, sehingga Anda dapat membuat pengontrol 10x dan meneruskannya ke daun untuk membangun kembali, atau flutter perlu mengetahui bagian mana dari pohon yang menggunakan nilai yang diperoleh oleh pembuatnya.
  3. Menggunakan ini seharusnya tidak lebih bertele-tele yang mengaitkan.
  4. Kompiler harus diperluas untuk menangani ini dengan benar.
  5. Pembantu debug diperlukan. Katakanlah Anda meletakkan breakpoint di salah satu widget Anda yang menggunakan ini. Saat mencapai breakpoint di dalam metode build, karena salah satu builder terpicu, IDE dapat menampilkan info tambahan untuk setiap builder yang digunakan:
Widget build(context) {
   // this builder is not highlighted, but you can hover over it to see how often it rebuilds, how heavy were those rebuilds, and when was the last rebuild
   var snapshot1 = keyword StreamBuilder(stream1);
   // this builder will be highlighted because it triggered the rebuild
   var constraints = keyword LayoutBuilder(); 

   // <-- I had a breakpoint here and parent constraint changed, breakpoints got reached.
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

Juga, @Hixie

Itu akan melanggar beberapa batasan yang cukup masuk akal yang telah dicantumkan orang lain (misalnya @Rudiksz), yaitu menjamin bahwa tidak ada kode inisialisasi yang pernah terjadi selama panggilan ke metode build.

Kami sudah secara implisit melakukan itu dengan menggunakan *Builders. Kami hanya membutuhkan gula sintaks untuk mendeindentasi mereka. Ini sangat mirip dengan async/menunggu dan masa depan, saya pikir.

@esDotDev Apa yang Anda gambarkan terdengar sangat mirip dengan apa yang saya usulkan sebelumnya dengan Properti (lihat misalnya https://github.com/flutter/flutter/issues/51752#issuecomment-664787791 atau https://github.com/flutter/flutter/ issue/51752#issuecomment-667737471). Apakah ada sesuatu yang mencegah solusi semacam ini dibuat sebagai sebuah paket? Artinya, perubahan apa yang perlu dilakukan oleh kerangka kerja inti agar Anda dapat menggunakan fitur semacam ini?

@rrousselGit Seperti Shawn, saya akan menanyakan hal yang sama kepada Anda. Jika satu-satunya perbedaan antara fitur StreamBuilder saat ini dan yang akan memenuhi kebutuhan Anda adalah sintaks yang berbeda, apa yang Anda perlukan dari sintaks inti untuk memungkinkan Anda menggunakan fitur seperti itu? Apakah tidak cukup hanya membuat sintaks yang Anda inginkan dan menggunakannya?

Masalah yang saya miliki dengan kisi Anda adalah jika saya menerapkannya pada solusi sejauh ini saya akan mendapatkan ini, yang saya anggap sangat berbeda dari apa yang akan Anda dapatkan:

StatefulWidget

  • Apakah kode yang dihasilkan secara objektif lebih baik daripada sintaks default?

    • Apakah itu menghindari kesalahan?

      _Ini sama dengan sintaks default, yang tidak terlalu rawan kesalahan._

    • Apakah lebih mudah dibaca?

      _Sama sama, jadi sama-sama enak dibaca, yang cukup enak dibaca._

    • Apakah lebih mudah untuk menulis?

      _Sama sama, jadi sama-sama gampang nulisnya, yang lumayan gampang._

  • Seberapa dapat digunakan kembali kode yang dihasilkan?
    _Ada sangat sedikit kode untuk digunakan kembali._
  • Berapa banyak masalah yang dapat diselesaikan dengan API ini?
    _Ini adalah dasarnya._
  • Apakah kita kehilangan beberapa manfaat untuk beberapa masalah tertentu?
    _Sepertinya tidak begitu._

Variasi pada Properti

  • Apakah kode yang dihasilkan secara objektif lebih baik daripada sintaks default?

    • Apakah itu menghindari kesalahan?

      _Ini memindahkan kode ke tempat yang berbeda, tetapi tidak secara khusus mengurangi jumlah kesalahan._

    • Apakah lebih mudah dibaca?

      _Ini menempatkan kode inisialisasi dan kode pembersihan dan kode siklus hidup lainnya di tempat yang sama, jadi kurang jelas._

    • Apakah lebih mudah untuk menulis?

      _Ini mencampur kode inisialisasi dan kode pembersihan dan kode siklus hidup lainnya, jadi lebih sulit untuk ditulis._

  • Seberapa dapat digunakan kembali kode yang dihasilkan?
    _Persis dapat digunakan kembali seperti StatefulWidget, hanya di tempat yang berbeda._
  • Berapa banyak masalah yang dapat diselesaikan dengan API ini?
    _Ini adalah gula sintaksis untuk StatefulWidget, jadi tidak ada bedanya._
  • Apakah kita kehilangan beberapa manfaat untuk beberapa masalah tertentu?
    _Kinerja dan penggunaan memori akan sedikit berkurang._

Variasi pada Builder

  • Apakah kode yang dihasilkan secara objektif lebih baik daripada sintaks default?

    • Apakah itu menghindari kesalahan?

      _Ini pada dasarnya adalah solusi StatefulWidget tetapi diperhitungkan; kesalahan harus setara._

    • Apakah lebih mudah dibaca?

      _Metode build lebih kompleks, logika lainnya berpindah ke widget yang berbeda, jadi hampir sama._

    • Apakah lebih mudah untuk menulis?

      _Lebih sulit untuk menulis pertama kali (membuat widget builder), sedikit lebih mudah setelahnya, jadi kurang lebih sama._

  • Seberapa dapat digunakan kembali kode yang dihasilkan?
    _Persis dapat digunakan kembali seperti StatefulWidget, hanya di tempat yang berbeda._
  • Berapa banyak masalah yang dapat diselesaikan dengan API ini?
    _Ini adalah gula sintaksis untuk StatefulWidget, jadi sebagian besar tidak ada perbedaan. Dalam beberapa aspek sebenarnya lebih baik, misalnya, mengurangi jumlah kode yang harus dijalankan saat menangani perubahan ketergantungan._
  • Apakah kita kehilangan beberapa manfaat untuk beberapa masalah tertentu?
    _Sepertinya tidak begitu._

Solusi seperti kait

  • Apakah kode yang dihasilkan secara objektif lebih baik daripada sintaks default?

    • Apakah itu menghindari kesalahan?

      _Mendorong pola buruk (misalnya konstruksi dalam metode build), berisiko bug jika tidak sengaja digunakan dengan persyaratan._

    • Apakah lebih mudah dibaca?

      _Meningkatkan jumlah konsep yang harus diketahui untuk memahami metode build._

    • Apakah lebih mudah untuk menulis?

      _Pengembang harus belajar menulis hook, yang merupakan konsep baru, jadi lebih sulit._

  • Seberapa dapat digunakan kembali kode yang dihasilkan?
    _Persis dapat digunakan kembali seperti StatefulWidget, hanya di tempat yang berbeda._
  • Berapa banyak masalah yang dapat diselesaikan dengan API ini?
    _Ini adalah gula sintaksis untuk StatefulWidget, jadi tidak ada bedanya._
  • Apakah kita kehilangan beberapa manfaat untuk beberapa masalah tertentu?
    _Kinerja dan penggunaan memori menurun._

Saya tidak mengerti mengapa kita terus kembali ke verbositas.
Saya secara eksplisit mengatakan berkali-kali bahwa itu bukan masalah dan masalahnya adalah kegunaan kembali vs keterbacaan vs fleksibilitas.

Maaf, saya salah ingat siapa yang mengatakan bahwa Properti terlalu bertele-tele. Anda benar, kekhawatiran Anda hanyalah bahwa ada kasus penggunaan baru yang belum terdaftar sebelumnya yang tidak ditangani, meskipun saya pikir akan sepele untuk memperluas Properti untuk menangani kasus penggunaan itu juga (saya belum 't mencoba, tampaknya lebih baik menunggu sampai kami memiliki aplikasi demo yang jelas sehingga kami dapat menyelesaikan semuanya sekali dan untuk semua daripada harus mengulangi berulang kali karena persyaratannya disesuaikan).

@szotp

  1. Fitur IMO ini dapat diperluas ke semua widget builder, termasuk LayoutBuilder misalnya.

LayoutBuilder adalah widget yang sangat berbeda dari kebanyakan builder, FWIW. Tak satu pun dari proposal yang telah dibahas sejauh ini akan bekerja untuk masalah seperti LayoutBuilder, dan tidak ada persyaratan yang dijelaskan sebelum komentar Anda termasuk LayoutBuilder. Jika kita juga harus menggunakan fitur baru ini untuk menangani LayoutBuilder, itu penting untuk diketahui; Saya sarankan bekerja dengan @TimWhiting untuk memastikan contoh aplikasi yang akan kami jadikan dasar proposal menyertakan pembuat tata letak sebagai contoh.

  1. Perlu ada cara untuk menonaktifkan mendengarkan, sehingga Anda dapat membuat pengontrol 10x dan meneruskannya ke daun untuk membangun kembali, atau flutter perlu mengetahui bagian mana dari pohon yang menggunakan nilai yang diperoleh oleh pembuatnya.

Saya tidak yakin apa artinya ini. Sejauh yang saya tahu, Anda dapat melakukannya hari ini dengan pendengar dan pembangun (misalnya saya menggunakan ValueListenableBuilder dalam aplikasi yang saya kutip sebelumnya untuk melakukan hal ini).

Itu akan melanggar beberapa batasan yang cukup masuk akal yang telah dicantumkan orang lain (misalnya @Rudiksz), yaitu menjamin bahwa tidak ada kode inisialisasi yang pernah terjadi selama panggilan ke metode build.

Kami sudah secara implisit melakukan itu dengan menggunakan *Builders.

Saya tidak berpikir itu akurat. Itu tergantung pada pembuatnya, tetapi beberapa bekerja sangat keras untuk memisahkan initState, didChangeDependencies, didUpdateWidget, dan logika build, sehingga hanya jumlah minimum kode yang diperlukan untuk menjalankan setiap build berdasarkan apa yang telah berubah. Misalnya, ValueListenableBuilder hanya mendaftarkan listener saat pertama kali dibuat, builder-nya dapat dijalankan kembali tanpa menjalankan ulang induk atau initState dari builder. Ini bukan sesuatu yang bisa dilakukan Hooks.

@esDotDev Apa yang Anda gambarkan terdengar sangat mirip dengan apa yang saya usulkan sebelumnya dengan Properti (lihat misalnya #51752 (komentar) atau #51752 (komentar) ).

Jika saya mengerti benar, kita bisa membuat UserProperty yang secara otomatis menangani DidDependancyChange for UserId, atau AnimationProperty , atau properti lain yang kita perlukan untuk menangani init/update/dispose untuk tipe itu? Kemudian ini tampak bagus untuk saya. Kasus penggunaan yang paling umum dapat dengan cepat dibuat.

Satu-satunya hal yang membuat saya kecewa adalah pembangun masa depan di sini. Tapi saya pikir ini hanya karena contoh yang Anda pilih?

Misalnya, bisakah saya membuat ini?

class _ExampleState extends State<Example> with PropertyManager {
  AnimationProperty animProp1;
  AnimationProperty animProp2;

  <strong i="15">@override</strong>
  void initProperties() {
    super.initProperties();
    anim1= register(anim1, AnimationProperty (
      AnimationController(duration: Duration(seconds: 1)),
      initState: (animator) => animator.forward()
      // Not dealing with updates or dispose here, cause AnimationProperty handles it
    ));
   anim2 = register(anim2, AnimationProperty(
       AnimationController(duration: Duration(seconds: 2))..forward(),
   ));
  }

  <strong i="16">@override</strong>
  Widget build(BuildContext context) {
    return Column(children: [
       FadeTransition(opacity: anim1, child: ...),
       FadeTransition(opacity: anim2, child: ...),
   ])
  }
}

Jika demikian, ini benar-benar LGTM! Dalam hal menambah kerangka kerja, ini adalah kasus apakah ini harus dipromosikan ke pendekatan sintaksis kelas satu (yang berarti itu menjadi praktik umum dalam satu tahun atau lebih), atau apakah itu ada sebagai plugin yang persentase pengembang satu digit menggunakan.

Ini masalah apakah Anda ingin dapat memperbarui contoh verbose dan (sedikit?) rawan kesalahan dengan sintaks yang lebih baik dan lebih ringkas. Harus menyinkronkan properti secara manual dan membuang () secara manual, memang menyebabkan bug, dan beban kognitif.

Imo alangkah baiknya jika seorang pengembang dapat menggunakan animator, dengan didUpdate dan membuang dan debugFillProperties yang tepat dan keseluruhan bekerja, tanpa harus berpikir dua kali tentang itu (persis seperti yang kami lakukan ketika kami menggunakan TweenAnimationBuilder sekarang, yang merupakan alasan utama kami merekomendasikan semua pengembang kami secara default menggunakannya daripada mengelola Animator secara manual).

Jika demikian, ini benar-benar LGTM! Dalam hal menambah kerangka kerja, ini adalah kasus apakah ini harus dipromosikan ke pendekatan sintaksis kelas satu (yang berarti itu menjadi praktik umum dalam satu tahun atau lebih), atau apakah itu ada sebagai plugin yang persentase pengembang satu digit menggunakan.

Mengingat betapa sepelenya Property , rekomendasi saya kepada seseorang yang menyukai gaya itu adalah dengan membuatnya sendiri (mungkin dimulai dengan kode saya jika itu membantu) dan menggunakannya langsung di aplikasi mereka sesuai keinginan mereka, menyesuaikannya untuk mengatasi kebutuhan mereka. Itu bisa dibuat menjadi paket jika banyak orang menyukainya juga, meskipun sekali lagi untuk sesuatu yang sepele itu tidak jelas bagi saya seberapa besar manfaatnya vs hanya menyalinnya ke dalam kode seseorang dan menyesuaikannya sesuai kebutuhan.

Satu-satunya hal yang membuat saya kecewa adalah pembangun masa depan di sini. Tapi saya pikir ini hanya karena contoh yang Anda pilih?

Saya mencoba membahas contoh yang diberikan @rrousselGit . Pada prinsipnya dapat disesuaikan untuk bekerja untuk apa saja.

Misalnya, bisakah saya membuat ini?

Anda ingin memindahkan konstruktor AnimationController ke penutupan yang akan dipanggil, daripada memanggilnya setiap saat, karena initProperties dipanggil selama hot reload untuk mendapatkan penutupan baru tetapi biasanya Anda tidak ingin membuat pengontrol baru selama hot reload (misalnya akan mengatur ulang animasi). Tapi ya, selain itu sepertinya baik-baik saja. Anda bahkan dapat membuat AnimationControllerProperty yang menggunakan argumen konstruktor AnimationController dan melakukan hal yang benar dengan argumen tersebut (mis. memperbarui durasi pada hot reload jika berubah).

Imo alangkah baiknya jika seorang pengembang dapat menggunakan animator, dengan didUpdate dan membuang dan debugFillProperties yang tepat dan keseluruhan bekerja, tanpa harus berpikir dua kali tentang itu (persis seperti yang kami lakukan ketika kami menggunakan TweenAnimationBuilder sekarang, yang merupakan alasan utama kami merekomendasikan semua pengembang kami secara default menggunakannya daripada mengelola Animator secara manual).

Kekhawatiran saya karena pengembang tidak memikirkannya adalah jika Anda tidak memikirkan kapan sesuatu dialokasikan dan dibuang, Anda cenderung mengalokasikan banyak hal yang tidak selalu Anda butuhkan, atau menjalankan logika yang tidak' t perlu menjalankan, atau melakukan hal-hal lain yang menyebabkan kode kurang efisien. Itulah salah satu alasan saya enggan menjadikan ini gaya default yang direkomendasikan.

Anda bahkan dapat membuat AnimationControllerProperty yang mengambil argumen konstruktor AnimationController dan melakukan hal yang benar dengan argumen tersebut (misalnya memperbarui durasi saat hot reload jika berubah).

Terima kasih @Hixie itu sangat keren dan saya pikir mengatasi masalah ini dengan cukup baik.

Saya tidak menyarankan para pengembang tidak boleh memikirkan hal-hal ini, tetapi saya pikir 99% kasus penggunaan hal-hal ini hampir selalu terikat pada StatefulWidget tempat mereka digunakan, dan melakukan apa pun selain itu sudah membawa Anda ke tanah pengembang menengah.

Sekali lagi, saya tidak melihat bagaimana ini pada dasarnya berbeda dari merekomendasikan TweenAnimationBuilder daripada AnimatorController mentah. Ini pada dasarnya adalah gagasan bahwa JIKA Anda ingin keadaan sepenuhnya terkandung/dikelola dalam keadaan lain ini (dan itu biasanya yang Anda inginkan), maka lakukan dengan cara ini lebih sederhana dan lebih kuat.

Pada titik ini, kita harus mengatur panggilan dan mendiskusikannya bersama dengan berbagai pihak yang berkepentingan.
Karena diskusi ini tidak berlanjut, karena kami menjawab pertanyaan yang sama berulang-ulang.

Saya tidak mengerti bagaimana setelah diskusi yang panjang, dengan begitu banyak argumen yang dibuat, kita masih dapat berargumen bahwa Builder tidak menghindari kesalahan dibandingkan dengan StatefulWidget, atau bahwa kait tidak lebih dapat digunakan kembali daripada StatefulWidgets mentah.

Itu sangat membuat frustrasi untuk diperdebatkan mengingat semua kerangka kerja deklaratif utama (Bereaksi, Vue, Swift UI, Jetpack Compose) memiliki satu atau lain cara untuk menyelesaikan masalah ini secara asli.
Sepertinya hanya Flutter yang menolak untuk mempertimbangkan masalah ini.

@esDotDev Alasan utama IMHO untuk menggunakan AnimationBuilder atau TweenAnimationBuilder atau ValueListenableBuilder adalah bahwa mereka membangun kembali hanya ketika nilainya berubah, tanpa membangun kembali sisa widget host mereka . Ini adalah hal kinerja. Ini bukan tentang verbositas atau penggunaan kembali kode. Maksud saya, tidak apa-apa menggunakannya untuk alasan itu juga, jika Anda menganggapnya berguna untuk alasan itu, tapi itu bukan kasus penggunaan utama, setidaknya untuk saya. Ini juga sesuatu yang Properti (atau Hooks) tidak berikan kepada Anda. Dengan itu, Anda akhirnya membangun kembali widget _seluruh_ ketika sesuatu berubah, yang tidak baik untuk kinerja.

@rrousselGit

Sepertinya hanya Flutter yang menolak untuk mempertimbangkan masalah ini.

Saya telah menghabiskan berjam-jam waktu saya sendiri akhir pekan ini, belum lagi berjam-jam waktu Google sebelumnya, mempertimbangkan masalah ini, menjelaskan solusi yang mungkin, dan mencoba menggambarkan dengan tepat apa yang kami coba selesaikan. Tolong jangan bingung kurangnya pemahaman tentang apa yang menjadi masalah dengan penolakan untuk mempertimbangkannya. Terutama ketika saya sudah menjelaskan hal terbaik yang dapat dilakukan untuk memajukan ini (membuat aplikasi demo yang memiliki semua logika status yang "terlalu bertele-tele atau terlalu sulit", mengutip judul masalah, untuk digunakan kembali), yang orang lain di bug ini telah mengambilnya sebagai tugas, dan yang Anda tolak untuk berpartisipasi.

The main reason IMHO to use an AnimationBuilder or TweenAnimationBuilder or ValueListenableBuilder is that they rebuild only when the value changes, without rebuilding the rest of their host widget. It's a performance thing.

Menarik. Bagi kami, kami benar-benar tidak pernah mengukur atau mengamati peningkatan kinerja dari menyimpan rekondisi kecil seperti ini. Jauh lebih penting daripada basis kode aplikasi yang besar untuk menjaga kode tetap ringkas, mudah dibaca, dan bebas dari kesalahan rutin apa pun yang dapat terjadi saat Anda mengeluarkan ratusan file kelas setiap beberapa minggu.

Dari pengalaman kami, biaya pengecatan ulang piksel, yang tampaknya terjadi pada pohon penuh kecuali jika Anda bertujuan untuk menentukan Batas Pengecatan Ulang Anda, merupakan faktor yang jauh lebih penting dalam kinerja dunia nyata daripada biaya tata letak widget parsial. Terutama ketika Anda masuk ke rentang monitor 4k.

Tapi ini adalah contoh yang baik ketika pembangun benar-benar masuk akal untuk hal semacam ini. Jika saya ingin membuat sub-konteks, maka pembangun masuk akal dan merupakan cara yang bagus untuk sampai ke sana.

Sering kali kami tidak melakukannya, dan dalam hal ini, Builder hanya menambahkan kekacauan, tetapi kami menerimanya, karena alternatifnya hanyalah jenis kekacauan yang berbeda, dan setidaknya dengan Builder, semuanya dijamin bebas bug. Dalam kasus di mana seluruh tampilan dibangun kembali, atau tidak perlu ada pembangunan kembali tampilan sama sekali (TextEditingController, FocusController) menggunakan pembangun tidak masuk akal, dan dalam semua kasus, menggulirkan boilerplate dengan tangan pada dasarnya tidak KERING.

Ini tentu sangat spesifik situasi, karena masalah kinerja sering terjadi. Saya pikir masuk akal bagi orang untuk menggunakan sesuatu seperti Kait atau Properti jika mereka menyukai gaya itu. Itu mungkin hari ini dan tampaknya tidak memerlukan tambahan apa pun dari kerangka kerja (dan seperti yang ditunjukkan Properti, itu benar-benar tidak memerlukan banyak kode).

Tidak, tapi ini seperti meminta komunitas untuk membangun TweenAnimationBuilder dan ValueListenableBuilder dan tidak memberi mereka StatefulWidget untuk dibangun.

Bukannya Anda bertanya, tetapi salah satu manfaat utama dari jenis arsitektur ini adalah bahwa ia secara alami cocok untuk komponen kecil kecil yang dapat dengan mudah dibagikan. Jika Anda meletakkan satu bagian dasar kecil di tempatnya ...

StatefulWidget adalah _lot_ kode, dibandingkan dengan Properti, dan tidak sepele (tidak seperti Properti, yang sebagian besar merupakan kode lem). Yang mengatakan, jika Properti adalah sesuatu yang masuk akal untuk digunakan kembali secara luas (berlawanan dengan membuat versi yang dipesan lebih dahulu untuk setiap aplikasi atau tim berdasarkan kebutuhan yang tepat), maka saya akan mendorong seseorang yang menganjurkan penggunaannya untuk membuat paket dan mengunggahnya ke pub . Hal yang sama berlaku, memang, untuk Hooks. Jika itu adalah sesuatu yang disukai komunitas maka akan terlihat banyak kegunaannya, seperti Penyedia. Tidak jelas mengapa kita perlu menempatkan sesuatu seperti itu dalam kerangka itu sendiri.

Saya kira karena ini secara inheren dapat diperluas. Penyedia tidak, itu hanya alat sederhana. Ini adalah sesuatu yang dibuat untuk diperpanjang, seperti StatefulWidget, tetapi untuk StatefulComponents. Fakta itu relatif sepele tidak harus selalu menentangnya?

Catatan tentang "mereka yang lebih menyukai gaya ini". Jika Anda dapat menyimpan 3 penggantian dan 15-30 baris, itu hanya akan menjadi kemenangan yang mudah dibaca dalam banyak kasus. Secara objektif om. Itu juga secara objektif menghilangkan 2 seluruh kelas kesalahan (lupa membuang barang, lupa memperbarui deps).

Terima kasih banyak atas diskusi yang luar biasa, bersemangat untuk melihat ke mana arahnya, saya pasti akan meninggalkannya di sini :)

Saya minta maaf untuk mengatakan bahwa utas ini membuat saya kecewa untuk kembali ke flutter yang merupakan rencananya ketika menyelesaikan proyek lain yang sedang saya kerjakan. Saya juga merasa frustrasi karena

Itu sangat membuat frustrasi untuk diperdebatkan mengingat semua kerangka kerja deklaratif utama (Bereaksi, Vue, Swift UI, Jetpack Compose) memiliki satu atau lain cara untuk menyelesaikan masalah ini secara asli.

Saya setuju dengan @rrousselGit karena menurut saya kita tidak perlu menghabiskan waktu untuk membangun aplikasi contoh flutter karena masalah ini telah dijelaskan dengan jelas berulang kali di utas ini. Saya tidak bisa melihat bagaimana itu tidak hanya mendapatkan respons yang sama. Karena hal yang sama akan disajikan di sini. Pendapat saya tentang utas ini adalah bahwa dari sudut pandang kerangka kerja flutter lebih baik bagi pengembang flutter untuk mengulangi kode seumur hidup yang sama di beberapa widget daripada hanya menulisnya sekali dan selesai dengan itu.
Juga, kami tidak dapat menulis aplikasi dalam flutter jika kami mencari solusi karena kami membutuhkan solusi untuk menulis aplikasi. Sejak orang-orang bergetar dalam percakapan ini setidaknya sudah cukup jelas bahwa mereka tidak suka kait. Dan Flutter tidak memiliki solusi lain untuk masalah tersebut seperti yang dijelaskan dalam OP. Bagaimana itu bahkan harus ditulis.

Terasa (setidaknya bagi saya) bahwa ini tidak dianggap serius, maaf @Hixie , maksud saya ini tidak dianggap serius dalam arti: We understand the problem and want to solve it . Seperti kerangka deklaratif lainnya tampaknya telah dilakukan.
Di lain tetapi jenis catatan yang sama hal-hal seperti ini:

Apakah lebih mudah untuk menulis?
Pengembang harus belajar menulis kait, yang merupakan konsep baru, jadi lebih sulit

Membuat saya sedih. Mengapa meningkatkan atau mengubah apa pun? Anda selalu bisa membuat argumen itu apa pun yang terjadi. Bahkan jika solusi baru jauh lebih mudah dan menyenangkan setelah dipelajari. Anda dapat mengganti kait dalam pernyataan itu dengan banyak hal. Ibuku bisa saja menggunakan kalimat seperti itu tentang gelombang mikro 30 tahun yang lalu. Ini misalnya bekerja sama jika Anda mengganti "kait" dalam kalimat dengan "berkibar" atau "panah".

Apakah lebih mudah untuk menulis?
Itu sama, jadi sama mudahnya untuk menulis, yang cukup mudah

Saya tidak berpikir apa maksud @rrousselGit dengan is it easier to write? (pertanyaan jawaban boolean) adalah bahwa jika sama, jawabannya bukan false / undefined .

Saya tidak bisa melihat bagaimana kita akan pernah bisa sampai ke suatu tempat karena kita bahkan tidak setuju ada masalah, hanya saja banyak orang menganggap ini masalah. Misalnya:

Itu pasti sesuatu yang dibesarkan orang. Itu bukan sesuatu yang saya punya pengalaman mendalam. Itu bukan sesuatu yang saya rasakan sebagai masalah saat menulis aplikasi saya sendiri dengan Flutter. Itu tidak berarti bahwa itu bukan masalah nyata bagi sebagian orang.

Dan meskipun banyak yang telah memberikan banyak argumen berkali-kali mengapa solusi untuk OP perlu di inti.
Misalnya perlu di inti untuk membuat pihak ketiga dapat menggunakannya dengan mudah dan alami seperti yang mereka gunakan dan buat widget hari ini. Dan berbagai alasan lainnya. Mantranya sepertinya, masukkan saja ke dalam bungkusan. Tapi sudah ada paket sebagai pengaitnya. Jika itu yang sudah diselesaikan mengapa tidak menutup saja threadnya.

Saya sangat berharap Anda menerima @rrousselGit atas tawarannya dan mengatur panggilan, mungkin akan lebih mudah untuk melakukan diskusi waktu nyata tentang ini daripada menulis hal-hal bolak-balik sepanjang waktu. Jika ada orang dari kerangka kerja lain yang telah memecahkan masalah yang dijelaskan dalam OP, jika salah satu dari mereka benar-benar baik, mungkin mereka dapat menghubungi mereka untuk sementara juga dan membagikan 5 sen tentang hal-hal yang muncul. Seseorang selalu bisa bertanya.

Bagaimanapun, saya berhenti berlangganan sekarang karena saya agak sedih mengikuti utas ini karena saya tidak melihatnya ke mana-mana. Tapi saya berharap utas ini akan sampai pada keadaan menyetujui ada masalah sehingga dapat fokus pada kemungkinan solusi untuk OP. Karena tampaknya agak sia-sia untuk mengusulkan solusi jika Anda tidak memahami masalah yang dihadapi orang, seperti yang mungkin disetujui oleh @Hixie , maksud saya, karena orang-orang yang bermasalah akan memberi tahu Anda mengapa solusinya tidak berhasil setelahnya.

Saya benar-benar berharap Anda beruntung dalam mengakhiri utas ini dengan hanya mengatakan bahwa flutter seharusnya tidak menyelesaikan masalah ini pada intinya meskipun orang menginginkannya. Atau dengan mencari solusi. 😘

LayoutBuilder adalah widget yang sangat berbeda dari kebanyakan builder, FWIW. Tak satu pun dari proposal yang telah dibahas sejauh ini akan bekerja untuk masalah seperti LayoutBuilder, dan tidak ada persyaratan yang dijelaskan sebelum komentar Anda termasuk LayoutBuilder. Jika kita juga harus menggunakan fitur baru ini untuk menangani LayoutBuilder, itu penting untuk diketahui; Saya sarankan bekerja dengan @TimWhiting untuk memastikan contoh aplikasi yang akan kami jadikan dasar proposal menyertakan pembuat tata letak sebagai contoh.

@Hixie Ya, kami pasti membutuhkan beberapa sampel. Saya akan menyiapkan sesuatu (tapi saya masih berpikir bahwa perubahan kompiler diperlukan sehingga sampelnya mungkin tidak lengkap). Ide umumnya adalah - gula sintaks yang meratakan pembangun dan tidak peduli tentang bagaimana pembangun diimplementasikan.

Namun, saya mendapatkan kesan daripada tidak ada seorang pun di tim Flutter yang melihat SwiftUI lebih dalam, saya pikir kekhawatiran kami akan mudah dipahami jika tidak. Sangat penting untuk masa depan kerangka bahwa orang-orang yang bermigrasi dari platform lain memiliki perjalanan yang semulus mungkin, dan karenanya, diperlukan pemahaman yang baik tentang platform lain, dan pengetahuan tentang pro & kontra. Dan melihat apakah beberapa kontra Flutter dapat diperbaiki. Jelas Flutter mengambil banyak ide bagus dari React dan saya bisa melakukan hal yang sama dengan framework yang lebih baru.

@emanuel-lundman

Terasa (setidaknya bagi saya) bahwa ini tidak dianggap serius, maaf @Hixie , maksud saya ini tidak dianggap serius dalam arti: We understand the problem and want to solve it . Seperti kerangka deklaratif lainnya tampaknya telah dilakukan.

Saya sepenuhnya setuju bahwa saya tidak mengerti masalahnya. Itu sebabnya saya terus terlibat dalam masalah ini, mencoba memahaminya. Itu sebabnya saya menyarankan untuk membuat aplikasi demo yang merangkum masalah. Apakah itu sesuatu yang pada akhirnya kami putuskan untuk diperbaiki dengan mengubah kerangka kerja secara mendasar, atau dengan menambahkan fitur kecil ke kerangka kerja, atau dengan sebuah paket, atau tidak sama sekali, sangat tergantung pada apa masalahnya sebenarnya.

@szotp

Namun, saya mendapatkan kesan daripada tidak ada seorang pun di tim Flutter yang melihat SwiftUI lebih dalam, saya pikir kekhawatiran kami akan mudah dipahami jika tidak.

Saya telah mempelajari Swift UI. Ini tentu saja kurang verbose untuk menulis kode Swift UI daripada kode Flutter, tetapi biaya keterbacaan IMHO sangat tinggi. Ada banyak "keajaiban" (dalam arti, logika yang bekerja dengan cara yang tidak jelas dalam kode konsumen). Saya benar-benar percaya bahwa itu adalah gaya yang disukai beberapa orang, tetapi saya juga percaya bahwa salah satu kekuatan Flutter adalah bahwa ia memiliki sedikit sihir. Itu berarti Anda terkadang menulis lebih banyak kode, tetapi itu juga berarti bahwa men-debug kode itu _jauh_ lebih mudah.

Saya pikir ada ruang untuk banyak gaya kerangka kerja. MVC-style, React-style, super singkat magis, bebas sihir tapi bertele-tele... Salah satu keuntungan arsitektur Flutter adalah aspek portabilitas sepenuhnya terpisah dari kerangka itu sendiri, sehingga memungkinkan untuk memanfaatkan semua alat kami -- dukungan lintas platform, hot reload, dll -- tetapi buat kerangka kerja yang sama sekali baru. (Sudah ada kerangka kerja Flutter lainnya, misalnya flutter_sprite.) Demikian pula kerangka kerja itu sendiri dirancang dalam mode berlapis sehingga misalnya Anda dapat menggunakan kembali semua logika RenderObject kami tetapi dengan pengganti lapisan Widget, jadi jika verbositas dari Widget terlalu banyak, seseorang bisa membuat kerangka kerja alternatif yang menggantikannya. Dan tentu saja ada sistem pengemasan sehingga fitur dapat ditambahkan tanpa kehilangan kode kerangka kerja yang ada.

Bagaimanapun, titik penyimpangan saya di sini adalah bahwa ini tidak semua-atau-tidak sama sekali. Bahkan jika dalam jangka panjang kami tidak akhirnya mengadopsi solusi yang membuat Anda bahagia, itu tidak berarti Anda tidak dapat terus mendapatkan keuntungan dari bagian-bagian dari penawaran yang Anda sukai.


Saya mendorong orang-orang yang tertarik dengan masalah ini untuk bekerja dengan @TimWhiting untuk membuat aplikasi yang menunjukkan mengapa Anda ingin menggunakan kembali kode dan seperti apa hari ini ketika Anda tidak bisa (https://github.com/TimWhiting/local_widget_state_approaches). Ini akan secara langsung membantu kami membuat proposal tentang cara mengatasi masalah ini dengan cara yang memenuhi kebutuhan _semua_ orang yang berkomentar di sini (termasuk mereka yang menyukai Hooks dan mereka yang tidak menyukai Hooks).

Sangat sulit untuk memahami mengapa "_a sintaks gula yang meratakan pembangun dan tidak peduli tentang bagaimana pembangun diimplementasikan._" diinginkan oleh pengembang sebagai fitur kelas satu. Kami telah menguraikan masalah dengan pendekatan alternatif berulang-ulang.

Singkatnya, pembangun memecahkan masalah kegunaan ulang, tetapi sulit untuk dibaca dan ditulis. "Masalahnya" hanyalah bahwa kami menginginkan fungsionalitas seperti pembuat yang secara signifikan lebih mudah dibaca.

Tidak ada aplikasi yang dapat menunjukkannya dengan lebih jelas, jika Anda pada dasarnya tidak setuju bahwa 3 pembuat bersarang sulit dibaca, atau bahwa pembuat pada umumnya tidak benar-benar melayani tujuan penggunaan kembali kode. Lebih penting lagi untuk mendengar bahwa banyak dari kita sebenarnya sangat suka mengurangi bersarang, dan benar-benar tidak suka menduplikasi kode di seluruh aplikasi kita, jadi kita terjebak di antara 2 opsi yang tidak ideal.

Saya telah menghabiskan berjam-jam waktu saya sendiri akhir pekan ini, belum lagi berjam-jam waktu Google sebelumnya, mempertimbangkan masalah ini, menjelaskan solusi yang mungkin, dan mencoba menggambarkan dengan tepat apa yang kami coba selesaikan

Saya bersyukur untuk itu

Tolong jangan bingung kurangnya pemahaman tentang apa yang menjadi masalah dengan penolakan untuk mempertimbangkannya

Saya baik-baik saja dengan kurangnya pemahaman, tetapi situasi saat ini tampaknya tidak ada harapan.
Kami masih memperdebatkan poin-poin yang dibuat di awal diskusi.

Dari sudut pandang saya, saya merasa menghabiskan waktu berjam-jam untuk menulis komentar terperinci yang menunjukkan berbagai masalah dan menjawab pertanyaan, tetapi komentar saya ditolak dan pertanyaan yang sama diajukan lagi.

Misalnya, kurangnya keterbacaan sintaks saat ini menjadi pusat diskusi.
Saya membuat beberapa analisis masalah keterbacaan untuk mendukung ini:

Analisis ini memiliki jumlah 👍 yang signifikan dan orang lain tampaknya setuju

Namun menurut jawaban Anda baru-baru ini, tidak ada masalah keterbacaan: https://github.com/flutter/flutter/issues/51752#issuecomment -671009593

Anda juga menyarankan:

Widget build(context) {
  var result = Text('result:');
  var builder1 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Row(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream1, builder: builder1);
  var builder2 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Column(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream2, builder: builder2);
}

mengetahui bahwa ini tidak dapat dibaca

Dari kedua komentar tersebut, kita dapat menyimpulkan bahwa:

  • kami tidak setuju bahwa ada masalah keterbacaan
  • masih belum jelas apakah keterbacaan adalah bagian dari cakupan masalah ini atau tidak

Ini menyedihkan untuk didengar, mengingat satu-satunya tujuan kait adalah untuk meningkatkan sintaks Builder - yang berada di puncak dapat digunakan kembali tetapi memiliki keterbacaan/penulisan yang buruk
Jika kita tidak setuju pada fakta dasar seperti itu, saya tidak yakin apa yang bisa kita lakukan.

@Hixie terima kasih, ini sangat membantu untuk memahami sudut pandang Anda. Saya setuju bahwa mereka mungkin berlebihan dengan kode sulap, tetapi saya yakin setidaknya ada beberapa hal yang mereka lakukan dengan benar.

Dan saya sangat menyukai arsitektur berlapis Flutter. Saya juga ingin tetap menggunakan widget. Jadi mungkin jawabannya adalah dengan meningkatkan ekstensibilitas Dart & Flutter, yang bagi saya adalah:

Jadikan pembuatan kode lebih mulus - dimungkinkan untuk menerapkan keajaiban SwiftUI di Dart, tetapi pengaturan biasa yang diperlukan terlalu besar dan terlalu lambat.

Jika menggunakan pembuatan kode semudah mengimpor paket dan menambahkan beberapa anotasi, maka orang yang memiliki masalah yang dibahas hanya akan melakukannya dan berhenti mengeluh. Sisanya akan terus menggunakan StatefulWidgets lama yang bagus secara langsung.

EDIT: Saya pikir flutter generate adalah langkah ke arah yang baik, sayang sekali itu dihapus.

Saya pikir ini akan menjadi pertanyaan yang sangat menarik untuk ditanyakan dalam Survei Pengembang Flutter berikutnya.

Ini akan menjadi awal yang baik. Bagilah masalah ini dalam bagian/pertanyaan yang berbeda dan lihat apakah ini masalah nyata yang ingin diselesaikan oleh pengembang Flutter.

Setelah jelas, percakapan ini akan lebih lancar dan memperkaya

Dari sudut pandang saya, saya merasa menghabiskan waktu berjam-jam untuk menulis komentar terperinci yang menunjukkan berbagai masalah dan menjawab pertanyaan, tetapi komentar saya ditolak dan pertanyaan yang sama diajukan lagi.

Jika saya mengajukan pertanyaan yang sama, itu karena saya tidak mengerti jawabannya.

Misalnya, kembali ke komentar Anda sebelumnya (https://github.com/flutter/flutter/issues/51752#issuecomment-670959424):

Masalah yang diperdebatkan adalah:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

Kode ini tidak dapat dibaca.

Saya benar-benar tidak melihat apa yang tidak dapat dibaca tentangnya. Ini menjelaskan dengan tepat apa yang terjadi. Ada empat widget, tiga widget memiliki metode pembangun, satu hanya memiliki string. Saya pribadi tidak akan menghilangkan jenisnya, saya pikir itu membuatnya lebih sulit untuk dibaca karena saya tidak tahu apa variabelnya, tetapi itu bukan masalah besar.

Mengapa ini tidak terbaca?

Untuk lebih jelasnya, jelas Anda merasa itu tidak dapat dibaca, saya tidak mencoba mengatakan bahwa Anda salah. Aku hanya tidak mengerti mengapa.

Kami dapat memperbaiki masalah keterbacaan dengan memperkenalkan kata kunci baru yang mengubah sintaks menjadi:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

Kode ini secara signifikan lebih mudah dibaca, tidak terkait dengan kait, dan tidak mengalami keterbatasan.

Ini tentu kurang bertele-tele. Saya tidak yakin itu lebih mudah dibaca, setidaknya untuk saya. Ada lebih banyak konsep (sekarang kami memiliki widget dan fitur "kata kunci" ini); lebih banyak konsep berarti lebih banyak beban kognitif. (Ini juga berpotensi kurang efisien, tergantung pada seberapa banyak objek ini independen; misalnya jika animasi selalu berubah lebih sering daripada nilai yang dapat didengarkan dan streaming, sekarang kami membangun kembali ValueListenableBuilder dan StreamBuilder meskipun biasanya mereka tidak akan dipicu ; juga logika penginisialisasi sekarang harus dimasukkan dan dilewati setiap build.)

Anda telah mengatakan bahwa verbositas bukanlah masalahnya, jadi menjadi lebih singkat bukan mengapa itu lebih mudah dibaca, saya berasumsi (walaupun saya juga bingung tentang ini karena Anda memang memasukkan "terlalu bertele-tele" di judul masalah dan di deskripsi asli masalah). Anda menyebutkan menginginkan lebih sedikit indentasi, tetapi Anda menggambarkan versi penggunaan builder tanpa indentasi sebagai tidak dapat dibaca juga, jadi mungkin bukan indentasi dalam aslinya yang menjadi masalah.

Anda mengatakan bahwa pembangun adalah puncak penggunaan kembali dan bahwa Anda hanya menginginkan sintaks alternatif tetapi proposal yang Anda sarankan tidak seperti pembangun (mereka tidak membuat widget atau elemen), jadi itu bukan aspek pembangun yang Anda mencari.

Anda memiliki solusi yang Anda sukai (Hooks), yang sejauh yang saya tahu berfungsi dengan baik, tetapi Anda ingin sesuatu diubah dalam kerangka kerja sehingga orang akan menggunakan Hooks? Yang saya juga tidak mengerti, karena jika orang tidak cukup menyukai Hooks untuk menggunakannya sebagai paket, itu mungkin juga bukan solusi yang baik untuk kerangka kerja (secara umum, kami lebih banyak menggunakan paket, bahkan fitur tim Flutter menciptakan, untuk apa nilainya).

Saya mengerti bahwa ada keinginan untuk penggunaan kembali kode yang lebih mudah. Aku hanya tidak tahu apa artinya.

Bagaimana perbandingan berikut dalam keterbacaan dengan dua versi di atas?

Widget build(context) {
  return
    ValueListenableBuilder(valueListenable: someValueListenable, builder: (context, value, _) =>
    StreamBuilder(stream: someStream, builder: (context, value2) =>
    TweenAnimationBuilder(tween: Tween(...), builder: (context, value3) =>
    Text('$value $value2 $value3'),
  )));
}

@szotp Jika ada terlalu banyak gesekan di sekitar solusi codegen kami saat ini, jangan ragu untuk mengajukan bug meminta perbaikan di sana.

@jamesblasco Saya tidak berpikir ada keraguan bahwa ada masalah nyata di sini yang ingin dipecahkan orang. Pertanyaannya bagi saya adalah apa masalahnya, sehingga kami dapat merancang solusi.

Saya dapat menjawab kekhawatiran tentang kekurangan kait atau keinginan untuk dimasukkan dalam kode, tetapi saya tidak berpikir itu yang harus kita fokuskan sekarang.

Pertama-tama kita harus sepakat tentang apa masalahnya. Jika tidak, saya tidak melihat bagaimana kita bisa menyepakati topik lain.

Saya benar-benar tidak melihat apa yang tidak dapat dibaca tentangnya. Ini menjelaskan dengan tepat apa yang terjadi. Ada empat widget, tiga widget memiliki metode pembangun, satu hanya memiliki string. Saya pribadi tidak akan menghilangkan jenisnya, saya pikir itu membuatnya lebih sulit untuk dibaca karena saya tidak tahu apa variabelnya, tetapi itu bukan masalah besar.

Saya pikir sebagian besar masalah di sini adalah cara Anda membuat kode sangat berbeda dari cara kebanyakan orang membuat kode.

Misalnya, Flutter dan contoh aplikasi yang Anda berikan:

  • jangan gunakan dartfmt
  • gunakan always_specify_types

Dengan hanya dua poin ini, saya akan terkejut jika itu mewakili lebih dari 1% komunitas.

Karena itu apa yang Anda evaluasi sebagai dapat dibaca kemungkinan sangat berbeda dari apa yang menurut kebanyakan orang dapat dibaca.

Saya benar-benar tidak melihat apa yang tidak dapat dibaca tentangnya. Ini menjelaskan dengan tepat apa yang terjadi. Ada empat widget, tiga widget memiliki metode pembangun, satu hanya memiliki string. Saya pribadi tidak akan menghilangkan jenisnya, saya pikir itu membuatnya lebih sulit untuk dibaca karena saya tidak tahu apa variabelnya, tetapi itu bukan masalah besar.

Mengapa ini tidak terbaca?

Rekomendasi saya adalah menganalisis ke mana mata Anda melihat saat mencari hal tertentu, dan berapa banyak langkah yang diperlukan untuk sampai ke sana.

Mari kita lakukan percobaan:
Saya akan memberi Anda dua pohon widget. Satu menggunakan sintaks linier, yang lain dengan sintaks bersarang.
Saya juga akan memberi Anda hal-hal spesifik yang perlu Anda cari dalam cuplikan itu.

Apakah lebih mudah menemukan jawaban menggunakan sintaks linier atau sintaks bersarang?

Pertanyaan-pertanyaan:

  • Apa widget non-pembuat yang dikembalikan oleh metode pembuatan ini?
  • Siapa yang membuat variabel bar ?
  • Berapa banyak pembangun yang kita miliki?

Menggunakan pembangun:

 Pembuatan widget(konteks) {
 kembalikan ValueListenableBuilder(
 valueListenable: someValueListenable,
 pembangun: (konteks, foo, _) {
 kembalikan StreamBuilder(
 aliran: someStream,
 pembangun: (konteks, baz) {
 kembalikan TweenAnimationBuilder(
 tween: tween(...),
 pembangun: (konteks, bilah) {
 kembalikan Wadah();
 },
 );
 },
 );
 },
 );
 }

Menggunakan sintaks linier:

 Pembuatan widget (konteks) {
 final foo = kata kunci ValueListenableBuilder(valueListenable: someValueListenable);
 bilah terakhir = kata kunci StreamBuilder(aliran: someStream);
 final baz = kata kunci TweenAnimationBuilder(tween: Tween(...));

kembalikan Gambar(); }


Dalam kasus saya, saya kesulitan mencari kode bersarang untuk menemukan jawabannya.
Di sisi lain, menemukan jawaban dengan pohon linier adalah seketika

Anda menyebutkan menginginkan lebih sedikit indentasi, tetapi Anda menggambarkan versi penggunaan builder tanpa indentasi sebagai tidak dapat dibaca juga, jadi mungkin, bukan indentasi dalam aslinya yang menjadi masalah.

Apakah StreamBuilder yang dipecah menjadi beberapa variabel merupakan saran yang serius?
Dari pemahaman saya, ini adalah saran sarkastik untuk membuat argumen. Bukan? Apakah Anda benar-benar berpikir bahwa pola ini akan menghasilkan kode yang lebih mudah dibaca, bahkan pada widget besar?

Mengabaikan fakta bahwa contohnya tidak berfungsi, saya tidak keberatan memecahnya untuk menjelaskan mengapa itu tidak dapat dibaca. Apakah itu berharga?

``` anak panah
Pembuatan widget(konteks) {
kembali
ValueListenableBuilder(valueListenable: someValueListenable, builder: (konteks, nilai, _) =>
StreamBuilder(stream: someStream, builder: (konteks, nilai2) =>
TweenAnimationBuilder(tween: Tween(...), builder: (konteks, nilai3) =>
Teks('$nilai $nilai2 $nilai3'),
)));
}

Itu terlihat lebih baik.
Tapi itu dengan asumsi orang tidak menggunakan dartfmt

Dengan dartfmt, kami memiliki:

Widget build(context) {
  return ValueListenableBuilder(
      valueListenable: someValueListenable,
      builder: (context, value, _) => StreamBuilder(
          stream: someStream,
          builder: (context, value2) => TweenAnimationBuilder(
                tween: Tween(),
                builder: (context, value3) => Text('$value $value2 $value3'),
              )));
}

yang hampir tidak berbeda dengan kode aslinya.

Anda mengatakan bahwa pembangun adalah puncak penggunaan kembali dan bahwa Anda hanya menginginkan sintaks alternatif tetapi proposal yang Anda sarankan tidak seperti pembangun (mereka tidak membuat widget atau elemen), jadi itu bukan aspek pembangun yang Anda mencari.

Itu detail implementasi.
Tidak ada alasan khusus untuk memiliki elemen atau tidak.
Sebenarnya, mungkin menarik untuk memiliki sebuah Elemen, sehingga kita dapat memasukkan LayoutBuilder dan berpotensi GestureDetector .

Saya pikir itu prioritas rendah. Tetapi di komunitas Bereaksi, di antara perpustakaan kait yang berbeda, saya telah melihat:

  • useIsHovered – mengembalikan boolean yang memberi tahu apakah widget melayang
  • useSize – (Mungkin harus useContraints di Flutter) yang memberikan ukuran UI terkait.

(Ini juga berpotensi kurang efisien, tergantung pada seberapa banyak objek ini independen; misalnya jika animasi selalu berubah lebih sering daripada nilai yang dapat didengarkan dan streaming, sekarang kami membangun kembali ValueListenableBuilder dan StreamBuilder meskipun biasanya mereka tidak akan dipicu ; juga logika penginisialisasi sekarang harus dimasukkan dan dilewati setiap build.)

Itu tergantung pada bagaimana solusinya diselesaikan.

Jika kami mencari perbaikan bahasa, masalah ini tidak akan menjadi masalah sama sekali.

Kita bisa membuatnya:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

"mengkompilasi" menjadi:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

Jika kita menggunakan hook, maka flutter_hooks dilengkapi dengan widget HookBuilder , sehingga kita masih dapat membagi hal-hal saat kita membutuhkannya.
Demikian pula, perlu tolok ukur yang tepat untuk menentukan apakah itu benar-benar suatu masalah, terutama dalam contoh yang dibuat di sini.

Dengan kait, kami hanya membangun kembali satu Elemen.
Dengan Builders, pembangunan kembali dibagi menjadi beberapa Elemen. Itu menambahkan beberapa overhead juga, meskipun kecil.

Bukan tidak mungkin bahwa lebih cepat untuk mengevaluasi kembali semua kait. Tampaknya inilah kesimpulan tim React yang mereka buat saat mendesain hook.
Ini mungkin tidak berlaku untuk Flutter.

Mengapa ini tidak terbaca?

Karena bersarang - bersarang membuat lebih sulit untuk memindai dengan cepat dan mengetahui bagian mana yang dapat Anda abaikan dan mana yang penting untuk memahami apa yang sedang terjadi. Kodenya agak "berurutan" tetapi bersarang menyembunyikan ini. Bersarang juga membuatnya sulit untuk bekerja dengannya - bayangkan Anda ingin menyusun ulang dua hal - atau menyuntikkan hal baru di antara dua - sepele dalam kode yang benar-benar berurutan, tetapi sulit ketika Anda perlu bekerja dengan bersarang.

Ini sangat mirip dengan async/menunggu gula vs bekerja dengan API Future mentah, konsep berbasis kelanjutan dame di bawahnya (dan bahkan argumen yang mendukung dan menentang sangat mirip) - ya Future API dapat digunakan secara langsung dan tidak menyembunyikan apa pun, tetapi keterbacaan dan pemeliharaan tentu saja tidak bagus - async/await adalah pemenang di sana.

Rekomendasi saya adalah menganalisis ke mana mata Anda melihat saat mencari hal tertentu, dan berapa banyak langkah yang diperlukan untuk sampai ke sana.

Saya telah memprogram selama 25 tahun sekarang dalam lebih dari 10 bahasa yang berbeda dan itu dengan mudah cara yang lebih buruk untuk mengevaluasi apa yang membuat kode dapat dibaca. Keterbacaan kode sumber itu rumit, tetapi ini lebih tentang seberapa baik ia mengekspresikan konsep dan logika pemrograman, daripada "di mana mata saya melihat" atau berapa banyak baris kode yang digunakannya.

Atau lebih tepatnya, menurut saya kalian terlalu fokus pada keterbacaan dan kurang pada pemeliharaan .

Contoh Anda kurang dapat dibaca, karena __intent__ kode kurang jelas dan masalah yang berbeda disembunyikan di tempat yang sama membuatnya lebih sulit untuk dipertahankan.


final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);

Berapa nilainya? Sebuah widget? Sebuah variabel string? Maksud saya itu digunakan di dalam a
return Text('$value $value2 $value3');

Pada dasarnya yang Anda inginkan adalah dengan mereferensikan variabel A dalam metode pembuatan widget B, itu akan menyebabkan B membangun kembali setiap kali nilai A berubah? Itu benar-benar apa yang mobx lakukan, dan ia melakukannya persis dengan jumlah magic/boilerplate yang tepat.


final value2 = keyword StreamBuilder(stream: someStream);

Apa yang harus dikembalikan ini? Sebuah widget? Sebuah aliran? Nilai String?

Sekali lagi, ini terlihat seperti nilai string. Jadi, Anda hanya ingin dapat mereferensikan aliran dalam metode pembuatan, menyebabkan widget itu membangun kembali setiap kali aliran memancarkan nilai dan memiliki akses ke nilai yang dipancarkan dan membuat/memperbarui/membuang aliran setiap kali widget dibuat/diperbarui /hancur? Dalam satu baris kode? Di dalam metode build?

Ya, dengan mobx Anda dapat membuat metode build Anda terlihat persis seperti contoh "lebih mudah dibaca" Anda (kecuali Anda mereferensikan observables). Anda masih harus menulis kode aktual yang melakukan semua pekerjaan, seperti yang Anda lakukan dengan kait. Kode sebenarnya adalah sekitar 10 baris, dan dapat digunakan kembali di widget apa pun.


final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

Kelas bernama "TweenAnimationBuilder" mengembalikan string?! Saya bahkan tidak akan mendekati mengapa ini adalah ide yang buruk.

Tidak ada perbedaan dalam indentasi/keterbacaan antara:

Future<double> future;

AsyncSnapshot<double> value = keyword FutureBuilder<double>(future: future);

dan:

Future<double> future;

double value = await future;

Keduanya melakukan hal yang sama persis: Mendengarkan objek dan membuka bungkus nilainya.

Saya benar-benar tidak melihat apa yang tidak dapat dibaca tentangnya. Ini menjelaskan dengan tepat apa yang terjadi. Ada empat widget, tiga widget memiliki metode pembangun, satu hanya memiliki string. Saya pribadi tidak akan menghilangkan jenisnya, saya pikir itu membuatnya lebih sulit untuk dibaca karena saya tidak tahu apa variabelnya, tetapi itu bukan masalah besar.

Argumen yang sama dapat diterapkan pada rantai Janji/Masa Depan.

foo().then(x =>
  bar(x).then(y =>
    baz(y).then(z =>
      qux(z)
    )
  )
)

vs

let x = await foo();
let y = await bar(x);
let z = await baz(y);
await qux(z);

Orang dapat mengatakan bahwa cara penulisan pertama memperjelas bahwa Janji sedang dibuat di bawah tenda, dan bagaimana tepatnya rantai itu terbentuk. Saya ingin tahu apakah Anda berlangganan itu, atau jika Anda menganggap Janji berbeda secara mendasar dari Pembangun karena mereka pantas mendapatkan sintaks.

Kelas bernama "TweenAnimationBuilder" mengembalikan string?! Saya bahkan tidak akan mendekati mengapa ini adalah ide yang buruk.

Anda dapat membuat argumen yang sama tentang Promises/Futures, dan mengatakan bahwa await mengaburkan fakta bahwa ia mengembalikan Promise.

Saya harus mencatat bahwa gagasan "membuka" sesuatu melalui sintaks bukanlah hal baru. Ya, dalam bahasa utama itu datang melalui async/menunggu, tetapi, misalnya, F# memiliki ekspresi komputasi , mirip dengan notasi do dalam beberapa bahasa FP hardcore. Di sana, ia memiliki lebih banyak kekuatan dan digeneralisasi untuk bekerja dengan pembungkus apa pun yang memenuhi hukum tertentu. Saya tidak menyarankan menambahkan Monads ke Dart, tapi saya pikir perlu dikemukakan bahwa pasti ada preseden untuk sintaks tipe-safe untuk "membuka" hal-hal yang tidak selalu sesuai dengan panggilan asinkron.

Mengambil langkah mundur, saya pikir satu hal yang banyak orang di sini perjuangkan (termasuk saya sendiri) adalah pertanyaan tentang keterbacaan ini. Seperti yang telah disebutkan oleh @rrousselGit , ada banyak contoh di seluruh rangkaian masalah keterbacaan ini dengan pendekatan berbasis Builder saat ini. Bagi banyak dari kita, tampaknya jelas bahwa ini:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

secara signifikan kurang dapat dibaca daripada ini:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

Tapi itu jelas tidak terbukti dengan sendirinya, karena @Hixie dan @Rudiksz tidak yakin (atau secara aktif menentang) gagasan bahwa yang kedua lebih mudah dibaca daripada yang pertama.

Jadi, inilah uraian saya (untuk jumlah kecil berapa pun nilainya) tentang mengapa blok kode kedua lebih mudah dibaca daripada yang pertama:

1. Bock kode pertama secara signifikan lebih menjorok daripada yang kedua

Dalam pengalaman saya, lekukan biasanya sama dengan asinkronisitas, percabangan, atau panggilan balik, yang semuanya membutuhkan lebih banyak beban kognitif untuk dipikirkan daripada kode linier yang tidak menjorok. Blok kode pertama memiliki beberapa lapisan lekukan, dan karena itu saya membutuhkan waktu yang tidak terlalu lama untuk mengerjakan apa yang terjadi di sini, dan apa yang akhirnya dirender (satu Text ). Mungkin orang lain lebih baik dalam mengerjakan lekukan itu dalam pikiran mereka.


Di blok kode kedua, tidak ada lekukan yang mengurangi masalah.

2. Blok kode pertama membutuhkan lebih banyak sintaks untuk mengekspresikan maksudnya

Di blok kode pertama, ada tiga pernyataan return , tiga pernyataan pembangun, tiga header lambda, tiga konteks dan terakhir tiga nilai. Pada akhirnya apa yang kami pedulikan adalah ketiga nilai tersebut - sisanya adalah boilerplate untuk membawa kami ke sana. Saya benar-benar menemukan ini sebagai bagian paling menantang dari blok kode ini. Ada begitu banyak hal yang terjadi, dan bagian-bagian yang benar-benar saya pedulikan (nilai-nilai yang dikembalikan oleh pembangun) adalah bagian yang sangat kecil sehingga saya menghabiskan sebagian besar energi mental saya untuk mengotak-atik boilerplate daripada berfokus pada bagian-bagian yang benar-benar saya butuhkan ( lagi, nilai).


Di blok kode kedua, ada pengurangan besar boilerplate sehingga saya dapat fokus pada bagian yang saya pedulikan - sekali lagi, nilainya.

3. Blok kode pertama menyembunyikan bagian terpenting dari metode build di bagian terdalam dari nesting

Saya menyadari bahwa semua bagian dari metode build ini penting, tetapi saya menemukan bahwa ketika saya membaca gaya kode UI deklaratif ini, hal yang biasanya saya cari adalah apa pun yang ditampilkan pengguna, yang dalam hal ini adalah widget Text disematkan di pembuat bersarang terdalam. Alih-alih berada di depan dan di tengah, widget Text itu terkubur dalam beberapa lapisan lekukan, sintaks, dan maksud. Jika Anda melempar Column atau Row di salah satu lapisan ini, itu menjadi lebih bersarang lebih dalam, dan pada saat itu Anda bahkan tidak mendapat manfaat hanya dengan menelusuri ke bagian yang paling berlekuk .


Di blok kode kedua, Widget yang dapat di-render yang sebenarnya dikembalikan ada di bagian bawah fungsi, yang langsung terlihat. Selain itu, saya telah menemukan bahwa ketika Anda memiliki sesuatu seperti sintaks OP yang diusulkan, Anda dapat mengandalkan visual Widget selalu berada di bagian bawah fungsi, yang membuat kode lebih mudah diprediksi dan mudah dibaca.

Mengenai bersarang, ada perbedaan antara bersarang yang mengekspresikan _tree_ dan bersarang yang mengekspresikan _sequence_ .

Dalam kasus normal View -> Text bersarang dan semacamnya, bersarang penting karena mewakili hubungan induk-anak di layar. Untuk fitur seperti Context (tidak yakin apakah Flutter memilikinya), ini mewakili cakupan konteks. Jadi bersarang itu sendiri memiliki makna semantik penting dalam kasus-kasus itu dan tidak dapat diabaikan. Anda tidak bisa hanya menukar tempat orang tua dan anak dan mengharapkan hasilnya sama.

Namun, dengan bersarangnya Builder (alias "Render Props" di Bereaksi), atau bersarang dari Promises, bersarang seharusnya mengomunikasikan urutan transformasi / augmentasi . Pohon itu sendiri tidak begitu penting — misalnya, saat bersarang independen ABuilder -> BBuilder -> CBuilder , hubungan induk-anak mereka tidak menyampaikan arti tambahan.

Selama ketiga nilai tersedia dalam cakupan di bawah ini, struktur pohonnya tidak terlalu relevan. Ini secara konseptual "datar", dan bersarang hanyalah artefak sintaksis. Tentu saja, mereka dapat menggunakan nilai satu sama lain (dalam hal ini urutannya penting), tetapi ini juga terjadi dengan panggilan fungsi berurutan, dan itu dapat dilakukan tanpa bersarang.

Inilah mengapa async/await menarik. Ini menghapus informasi tambahan (hubungan orang tua-anak dari Janji) yang menjelaskan mekanisme tingkat rendah, dan sebagai gantinya memungkinkan Anda fokus pada maksud tingkat tinggi (menggambarkan urutan).

Pohon adalah struktur yang lebih fleksibel daripada daftar. Tetapi ketika setiap orang tua hanya memiliki satu anak, itu menjadi pohon patologis — pada dasarnya, sebuah daftar. Async/Await dan Hooks menyadari bahwa kita membuang sintaks pada sesuatu yang tidak menyampaikan informasi, dan menghapusnya.

Ini sebenarnya menarik karena saya sebelumnya mengatakan "ini bukan tentang boilerplate" dan sekarang sepertinya saya bertentangan dengan diri saya sendiri. Saya pikir ada dua hal di sini.

Dengan sendirinya, Builder (atau setidaknya Render Props in React) adalah solusi (AFAIK) untuk masalah "logika yang dapat digunakan kembali". Hanya saja mereka tidak terlalu ergonomis jika Anda menggunakannya banyak. Anda tentu tidak disarankan oleh lekukan untuk menggunakan lebih dari 4 atau 5 di antaranya dalam komponen yang sama. Setiap level berikutnya adalah hit keterbacaan.

Jadi bagian yang tampaknya belum terpecahkan bagi saya adalah mengurangi biaya pembaca untuk bersarang. Dan argumen itu persis sama dengan argumen untuk async / await .

Ini tidak dapat dibaca karena alasan berikut:

  • Penggunaan spasi putih yang berlebihan tidak dapat diskalakan dengan baik. Kami hanya memiliki begitu banyak baris di monitor kami, dan memaksa pengguliran mengurangi keterbacaan dan meningkatkan beban bawaan. Bayangkan kita sudah memiliki pohon widget 60 baris, dan Anda hanya memaksakan tambahan 15 pada saya untuk pembangun, tidak ideal.
  • Ini membuang-buang ruang Hz, yang kami terbatas untuk mengarah ke pembungkus ekstra, yang selanjutnya membuang-buang ruang garis.
  • Ini mendorong simpul daun, alias konten, lebih jauh dari sisi kiri, dan ke dalam pohon di tempat yang lebih sulit dikenali dengan pandangan sekilas.
  • Secara signifikan lebih sulit untuk mengidentifikasi 'pemain kunci' atau 'non boilerplate' secara sekilas. Saya harus "menemukan hal-hal penting" sebelum penalaran saya bisa dimulai.

Cara lain untuk melihat ini adalah dengan hanya menyorot kode non-boilerplate, dan apakah itu dikelompokkan bersama agar mata Anda dapat dengan mudah berpesta, atau tersebar di mana-mana agar mata Anda harus memindai sekitar:

Dengan penyorotan, ini sangat mudah untuk dipikirkan. Tanpa itu, saya perlu membaca seluruh bagian verbositas sebelum saya dapat mengetahui siapa yang menggunakan apa dan di mana:
image

Sekarang bandingkan dengan ini, penyorotan pada dasarnya berlebihan, karena tidak ada tempat lain untuk mata saya pergi:
image

Perlu dicatat mungkin ada ketidaksepakatan dalam keterbacaan vs grokability. @Hixie kemungkinan menghabiskan waktunya di file kelas monolitik, di mana ia harus terus-menerus membaca dan memahami pohon besar, sedangkan Pengembang Aplikasi khas Anda lebih banyak tentang membangun ratusan kelas yang lebih kecil, dan ketika Anda mengelola banyak kelas kecil, kemampuan grok adalah kuncinya . Ini bukan karena kodenya dapat dibaca ketika Anda memperlambat dan membacanya, tetapi saya dapat mengetahui apa yang dilakukan ini secara sekilas, jadi saya dapat melompat dan mengubah atau memperbaiki sesuatu.

Sebagai referensi, padanan Context di React adalah InheritedWidgets/Provider

Satu-satunya perbedaan di antara mereka adalah, di React sebelum kait kita _had_ menggunakan pola Builder untuk menggunakan Context/Inheritedwidget

Sedangkan Flutter memiliki cara untuk mengikat pembangunan kembali dengan panggilan fungsi sederhana.
Jadi tidak perlu kait untuk meratakan pohon menggunakan InheritedWidgets — yang mengatasi masalah Pembangun

Itu mungkin salah satu alasan mengapa diskusi lebih sulit, karena kita lebih jarang membutuhkan Builder.

Tapi perlu disebutkan memperkenalkan solusi seperti kait akan menyelesaikan keduanya https://github.com/flutter/flutter/issues/30062
dan https://github.com/flutter/flutter/issues/12992

Tampaknya juga @Hixie lebih terbiasa membaca pohon bersarang dalam karena Flutter pada dasarnya adalah semua pohon, jauh lebih banyak daripada bahasa lain menurut saya. Sebagai salah satu pengembang utama di Flutter itu sendiri, tentu saja, dia akan memiliki lebih banyak pengalaman dengan itu. Flutter pada dasarnya dapat dianggap sebagai kerangka kerja kiri ke kanan , dengan sarang yang dalam, seperti HTML yang saya kira @Hixie telah memiliki pengalaman, setelah membuat spesifikasi HTML5. Ini untuk mengatakan, titik paling kanan dari blok kode adalah di mana logika utama dan nilai kembali berada.

Namun, sebagian besar pengembang tidak, atau berasal dari bahasa yang lebih atas ke bawah , di mana logikanya, sekali lagi, dibaca dari atas ke bawah, bukan di pohon bersarang; itu berada di titik paling bawah dari blok kode. Oleh karena itu, apa yang dapat dibaca olehnya belum tentu demikian dengan banyak pengembang lain, yang berpotensi mengapa Anda melihat dikotomi pendapat tentang keterbacaan di sini.

Cara lain untuk melihatnya, adalah berapa banyak kode yang dibutuhkan otak saya untuk diedit secara visual. Bagi saya ini secara akurat mewakili "pekerjaan kasar" yang harus dilakukan otak saya sebelum saya dapat menguraikan apa yang dikembalikan dari pohon:
image

Sederhananya, versi pembangun memiliki jejak vertikal 4x lebih tinggi sambil menambahkan secara harfiah tidak ada informasi atau konteks tambahan, DAN mengemas kode dengan cara yang jauh lebih jarang/tersebar. Dalam pikiran saya, itu adalah kasus terbuka dan tertutup, secara objektif kurang dapat dibaca karena alasan itu saja, dan itu bahkan tidak mempertimbangkan beban kognitif tambahan di sekitar lekukan dan barisan kurung kurawal yang telah kita semua tangani dalam flutter.

Pikirkan mata saya sebagai cpu yang lapar, mana yang lebih dioptimalkan untuk pemrosesan? :)

Dalam kasus normal View -> Text nesting dan semacamnya, nesting penting karena merepresentasikan _hubungan induk-anak_ di layar. Untuk fitur seperti Context (tidak yakin apakah Flutter memilikinya), ini mewakili cakupan konteks. Jadi bersarang itu sendiri memiliki makna semantik penting dalam kasus-kasus itu dan tidak dapat diabaikan. Anda tidak bisa hanya menukar tempat orang tua dan anak dan mengharapkan hasilnya sama.

Sangat setuju dan saya sebutkan ini sebelumnya. Secara semantik tidak masuk akal untuk membuat lapisan konteks tambahan di pohon tampilan visual, karena saya menggunakan pengontrol non-visual tambahan yang memiliki status. Menggunakan 5 animator, sekarang widget Anda sedalam 5 lapisan? Hanya pada tingkat tinggi itu saja, pendekatan saat ini agak berbau.

Ada dua masalah yang muncul di sini.

  1. Saya menduga ada beberapa ketidaksepakatan tentang seberapa sulit/eksplisit seharusnya ketika menggunakan beberapa sumber daya yang mahal. Filosofi Flutter adalah mereka harus lebih sulit/eksplisit sehingga pengembang berpikir serius tentang kapan dan bagaimana menggunakannya. Aliran, animasi, pembuat tata letak, dll. mewakili biaya non-sepele yang dapat digunakan secara tidak efisien jika terlalu mudah.

  2. Build adalah sinkronisasi, tetapi hal paling menarik yang Anda tangani sebagai pengembang aplikasi adalah asinkron. Tentu saja kami tidak dapat membuat build async. Kami menciptakan kemudahan ini seperti Stream/Animation/FutureBuilder tetapi mereka tidak selalu bekerja cukup baik untuk kebutuhan pengembang. Ini mungkin mengatakan bahwa kami tidak banyak menggunakan Stream atau FutureBuilder dalam kerangka kerja.

Saya tidak berpikir solusinya adalah memberi tahu pengembang untuk selalu menulis objek render khusus saat bekerja dengan operasi async tentunya. Tetapi dalam contoh yang saya lihat di bug ini, ada campuran pekerjaan asinkron dan sinkronisasi yang tidak bisa kita tunggu begitu saja. Build harus menghasilkan sesuatu di setiap panggilan.

fwiw, tim React membahas penggunaan kembali masalah keterbacaan sebagai motivasi 1:
Kait Motivasi: Sulit untuk menggunakan kembali logika stateful antar komponen
Bereaksi tidak menawarkan cara untuk "melampirkan" perilaku yang dapat digunakan kembali ke komponen ... Anda mungkin akrab dengan pola ... yang mencoba menyelesaikan ini. Tetapi pola-pola ini mengharuskan Anda untuk merestrukturisasi komponen saat Anda menggunakannya, yang dapat menjadi rumit dan membuat kode lebih sulit untuk diikuti .

Ini sangat mirip dengan bagaimana Flutter saat ini tidak menawarkan kepada kita cara untuk 'menulis status' secara asli. Itu juga menggemakan apa yang terjadi ketika kita menjadi pembangun, yang memodifikasi pohon tata letak kita dan membuatnya lebih rumit untuk dikerjakan, dan "lebih sulit untuk diikuti", kata pohon.

@dnfield jika build harus dipanggil setiap kali, mungkin kita bisa membuat kait tidak dalam metode build agar build selalu sinkron, yaitu meletakkannya di dalam kelas di mana initState dan dispose adalah. Apakah ada masalah dengan melakukannya, dari mereka yang menulis kait?

Anda dapat membuat argumen yang sama tentang Promises/Futures, dan mengatakan bahwa await mengaburkan fakta bahwa ia mengembalikan Promise.

Tidak. Await secara harfiah hanyalah gula sintaksis untuk satu fitur tunggal. Jika Anda menggunakan Futures verbose, atau sintaks deklaratif, __intent__ kodenya sama.

Tuntutan di sini adalah untuk memindahkan kode sumber yang menangani masalah yang sama sekali berbeda di bawah payung yang sama, menyembunyikan semua jenis perilaku yang berbeda di balik satu kata kunci dan mengklaim bahwa entah bagaimana itu mengurangi beban kognitif.

Itu sepenuhnya salah, karena sekarang setiap kali saya menggunakan kata kunci itu saya perlu bertanya-tanya tentang apakah hasilnya akan melakukan operasi asinkron, memicu pembangunan kembali yang tidak perlu, menginisialisasi objek berumur panjang, melakukan panggilan jaringan, membaca file dari disk atau mengembalikan nilai statis sederhana . Semua ini adalah situasi yang sangat berbeda dan saya harus terbiasa dengan rasa kail yang saya gunakan.

Saya mengerti dari diskusi bahwa sebagian besar pengembang di sini tidak suka direpotkan dengan detail semacam ini dan menginginkan pengembangan yang mudah, dengan hanya dapat menggunakan "pengait" ini tanpa harus khawatir tentang detail implementasi.
Menggunakan apa yang disebut "kait" ini mau tak mau tanpa memahami implikasi penuhnya akan menyebabkan kode yang tidak efisien dan buruk, dan akan menyebabkan orang menembak diri mereka sendiri - oleh karena itu bahkan tidak menyelesaikan masalah "melindungi pengembang pemula".

Jika kasus penggunaan Anda sederhana, ya, Anda dapat menggunakan kait mau tak mau. Anda dapat menggunakan dan membuat sarang semua yang Anda inginkan, tetapi karena aplikasi Anda menjadi kompleks sehingga Anda kesulitan menggunakan kembali kode, saya pikir Anda harus lebih memperhatikan kode dan arsitektur Anda sendiri. Jika saya sedang membangun aplikasi untuk jutaan pengguna yang berpotensi, saya akan sangat ragu untuk menggunakan "ajaib" yang mengabstraksikan detail penting dari saya. Saat ini saya menemukan Flutter's API tepat untuk menjadi sederhana untuk kasus penggunaan yang sangat sederhana, dan masih fleksibel untuk memungkinkan siapa saja mengimplementasikan segala jenis logika kompleks dengan cara yang sangat efisien.

@Rudiksz Sekali lagi, tidak ada yang memaksa Anda untuk pindah ke kait. Gaya saat ini akan tetap ada. Apa argumen Anda dalam menghadapi pengetahuan ini?

Lagi pula, orang masih dapat menulis kode yang efisien begitu mereka melihat bahwa beberapa kait entah bagaimana memblokir aplikasi mereka; mereka akan melihatnya ketika mereka membuat profil atau bahkan hanya menjalankan aplikasi, seperti yang Anda lakukan dengan gaya saat ini.

@Rudiksz Sekali lagi, tidak ada yang memaksa Anda untuk pindah ke kait. Gaya saat ini akan tetap ada. Apa argumen Anda dalam menghadapi pengetahuan ini?

Ya ampun, argumen yang sama ini juga berlaku untuk orang-orang yang mengeluh tentang masalah dengan kerangka kerja. Tidak ada yang memaksa mereka untuk tidak menggunakan paket kait.

Aku akan sangat blak-blakan di sini.
Masalah ini sebenarnya bukan tentang kait, widget stateful, dan siapa yang menggunakan apa, melainkan tentang mengembalikan praktik terbaik selama puluhan tahun agar beberapa orang dapat menulis 5 baris kode lebih sedikit.

Argumen Anda tidak benar-benar berhasil. Alasan masalah ini dibuat adalah karena paket flutter_hooks tidak melakukan semua yang mungkin dilakukan dengan memiliki sesuatu dalam kerangka kerja, sedangkan model saat ini, berdasarkan sudah berada dalam kerangka kerja secara asli. Argumennya adalah untuk memindahkan fitur flutter_hooks ke dalam kerangka secara asli. Argumen Anda menyatakan bahwa apa pun yang dapat saya lakukan dengan model saat ini, saya juga dapat melakukannya dengan paket kait, yang tidak benar, sepertinya dari orang lain dalam diskusi ini. Jika itu benar, maka itu akan berhasil, yang juga berarti bahwa kait ada dalam kerangka kerja secara asli, dan oleh karena itu, sekali lagi, karena kait dan non-kait akan setara, Anda dapat menggunakan model saat ini serta berbasis kait model, yang saya perdebatkan.

Saya tidak yakin dari mana praktik terbaik Anda berasal, karena saya tahu bahwa menjaga kode agar mudah dibaca adalah praktik terbaik, dan sarang yang berlebihan adalah pola anti. Praktik terbaik mana yang sebenarnya Anda maksud?

fwiw, tim React membahas penggunaan kembali masalah keterbacaan sebagai motivasi 1:
Kait Motivasi: Sulit untuk menggunakan kembali logika stateful antar komponen
Bereaksi tidak menawarkan cara untuk "melampirkan" perilaku yang dapat digunakan kembali ke komponen ... Anda mungkin akrab dengan pola ... yang mencoba menyelesaikan ini. Tetapi pola-pola ini mengharuskan Anda untuk merestrukturisasi komponen saat Anda menggunakannya, yang dapat menjadi rumit dan membuat kode lebih sulit untuk diikuti .

Saya mendengar semua orang mengoceh tentang bagaimana Flutter jauh lebih menakjubkan daripada React. Mungkin karena ia tidak melakukan semuanya seperti yang dilakukan React? Anda tidak dapat memiliki keduanya, Anda tidak dapat mengatakan bahwa Flutter jauh di depan React dan juga meminta agar ia melakukan semuanya persis seperti yang dilakukan React.

Solusi apa pun yang Flutter putuskan untuk digunakan untuk masalah tertentu harus didasarkan pada kemampuannya sendiri. Saya tidak akrab dengan React tetapi tampaknya saya kehilangan beberapa teknologi yang sangat menakjubkan. :/

Saya tidak berpikir ada yang berdebat bahwa Flutter harus melakukan segalanya seperti React.

Tapi faktanya, layer widget Flutter sangat terinspirasi dari React. Itu dinyatakan dalam dokumentasi resmi.
Dan sebagai konsekuensinya, Widget memiliki manfaat dan masalah yang sama dengan React Components.

Ini juga berarti bahwa React memiliki lebih banyak pengalaman daripada Flutter dalam menangani masalah ini.
Itu menghadapi mereka lebih lama, dan memahami mereka dengan lebih baik.

Jadi tidak mengherankan jika solusi untuk masalah Flutter mirip dengan solusi untuk masalah React.

API pengguna @Rudiksz Flutter sangat mirip dengan model berbasis kelas React, meskipun API internal mungkin berbeda (saya tidak tahu apakah mereka berbeda, saya tidak terlalu sering menggunakan API internal). Saya mendorong Anda untuk mencoba Bereaksi dengan kait untuk melihat bagaimana keadaannya, seperti yang saya nyatakan sebelumnya bahwa tampaknya ada dikotomi pendapat yang didasarkan hampir secara eksklusif pada mereka yang memiliki dan belum menggunakan konstruksi seperti kait dalam kerangka kerja lain.

Mengingat kesamaan mereka, seharusnya tidak mengherankan bahwa solusi untuk masalah terlihat serupa, seperti yang dikatakan di atas.

Tolong, mari kita mencoba yang terbaik untuk tidak bertengkar satu sama lain.

Satu-satunya hal yang akan membawa kita ke pertempuran adalah menghentikan diskusi ini dan gagal menemukan solusi.

Saya mendengar semua orang mengoceh tentang bagaimana Flutter jauh lebih menakjubkan daripada React. Mungkin karena ia tidak melakukan semuanya seperti yang dilakukan React? Anda tidak dapat memiliki keduanya, Anda tidak dapat mengatakan bahwa Flutter jauh di depan React dan juga meminta agar ia melakukan semuanya persis seperti yang dilakukan React.

Menunjukkan bahwa tim React memiliki motivasi yang sama ketika mereka menemukan hook, memvalidasi kekhawatiran yang kami ungkapkan di sini. Ini tentu memvalidasi bahwa ada masalah dalam menggunakan kembali dan menggabungkan logika stateful umum dalam jenis kerangka kerja berbasis komponen ini, dan juga pada tingkat tertentu memvalidasi diskusi tentang keterbacaan, bersarang, dan masalah umum dengan "kekacauan" dalam pandangan Anda.

Tidak mengoceh tentang apa pun, saya bahkan tidak pernah bekerja di React, dan saya suka Flutter. Saya dapat dengan mudah melihat masalahnya di sini.

@Rudiksz kami tidak dapat memastikan apakah itu berkinerja baik dalam praktik sampai kami mempraktikkannya. Tidak mudah untuk memutuskan sekarang.

@Hixie ini adalah contoh perjalanan yang mungkin dimiliki pengguna flutter umum untuk menerapkan widget untuk menampilkan nama panggilan pengguna dari userId baik dengan HookWidget dan StatefulWidget .

__pengait widget__

String useUserNickname(Id userid) {
  final name = useState("");
  useEffect(async () {
    name.value = "Loading...";
    name.value = await fetchNicknames()[userId;
  }, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__widget stateful__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";

   initState() {
     super.initState();
     fetchAndUpdate();
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      final result = await fetchNicknames()[widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

sejauh ini tidak ada yang menarik. kedua solusi tersebut cukup dapat diterima, lugas, dan berkinerja.
sekarang kita ingin menggunakan UserNickname di dalam ListView . seperti yang Anda lihat fetchNicknames mengembalikan peta nama panggilan, bukan hanya satu nama panggilan. jadi menyebutnya setiap kali berlebihan. beberapa solusi yang bisa kita terapkan disini:

  • pindahkan logika panggilan fetchNicknames() ke widget induk dan simpan hasilnya.
  • menggunakan pengelola cache.

solusi pertama dapat diterima tetapi memiliki 2 masalah.
1 - itu membuat UserNickname tidak berguna karena sekarang hanya widget Teks dan jika Anda ingin menggunakannya di tempat lain, Anda harus mengulangi apa yang Anda lakukan di widget induk (yang memiliki ListView ) . logika untuk menampilkan nama panggilan milik UserNickname tetapi kita harus memindahkannya secara terpisah.
2 - kita dapat menggunakan fetchNicknames() di banyak sub-pohon lainnya dan lebih baik menyimpannya di cache untuk semua aplikasi, tidak hanya satu bagian dari aplikasi.

jadi bayangkan kita memilih manajer cache dan menyediakan kelas CacheManager dengan InheritedWidgets atau Provider .

setelah menambahkan dukungan untuk caching:

__pengait widget__

String useUserNickname(Id userid) {
  final context = useContext();
  final cache = Provider.of<CacheManager>(context);
  final name = useState("");
  useEffect(async () {
    name.value = "Loading...";
    var cachedValue = cache.get("nicknames");
    if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        cache.set("nicknames", result );
        cachedValue = result ;
    }
    final result = cachedValue[widget.userId];
    name.value = result ;
  }, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__widget stateful__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";
  CacheManager cache;

   initState() {
     super.initState();
     fetchAndUpdate();
     this.cache = Provider.of<CacheManager>(context);
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      var cachedValue = this.cache.get("nicknames");
      if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        this.cache.set("nicknames", result );
        cachedValue = result ;
      }
      final result = cachedValue [widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

kami memiliki server soket yang memberi tahu klien ketika nama panggilan berubah.

__pengait widget__

String useUserNickname(Id userid) {
  final context = useContext();
  final cache = Provider.of<CacheManager>(context);
  final notifications = Provider.of<ServiceNotifications>(context);
  final name = useState("");

  fetchData() async {
    name.value = "Loading...";
    var cachedValue = cache.get("nicknames");
    if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        cache.set("nicknames", result );
        cachedValue = result ;
    }
    final result = cachedValue[widget.userId];
    name.value = result ;
   }

  useEffect(() {
     final sub = notifications.on("nicknameChanges", fetchData);
     return () => sub.unsubscribe();
   }, [])

  useEffect(fetchData, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__widget stateful__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";
  CacheManager cache;
  ServerNotification notifications;
  CancelableOperation sub;

   initState() {
     super.initState();
     fetchAndUpdate();
     this.cache = Provider.of<CacheManager>(context);
     this.notifications = Provider.of<ServerNotification>(context);
     this.sub = notifications.on("nicknameChanges", fetchAndUpdate);
   }

   dispose() {
      super.dispose();
      this.sub.unsubscribe();
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      var cachedValue = this.cache.get("nicknames");
      if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        this.cache.set("nicknames", result );
        cachedValue = result ;
      }
      final result = cachedValue [widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

sejauh ini implementasi keduanya dapat diterima dan baik. IMO boilerplate di statful tidak ada masalah sama sekali. masalah muncul ketika kita membutuhkan widget seperti UserInfo yang memiliki nama panggilan dan avatar pengguna. kita juga tidak bisa menggunakan widget UserNickname karena kita perlu menampilkannya dalam kalimat seperti "Selamat datang [nama pengguna]".

__pengait widget__

useFetchUserNickname(userId) // old code
useUserAvatar(userId) // implementation like `useFetchUserNickname`

class UserNickname extends HookWidget {
  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    final avatar = useUserAvatar(userId);
    return Row(
      children: [Image.network(avatar), Text(nickname)],
    );
  }
}

tapi untuk __stateful widget__ kita tidak bisa hanya menggunakan logika yang kita tulis. kita harus memindahkan logika ke dalam kelas (seperti Property yang Anda sarankan) dan kita masih perlu menulis lem widget dengan kelas properti lagi di widget baru.

jika Anda melihat perubahan pada 3 contoh pertama, kami tidak mengubah widget itu sendiri sama sekali karena satu-satunya perubahan yang diperlukan adalah logika status dan satu-satunya tempat yang berubah adalah semua, logika status.
ini memberi kami logika keadaan yang bersih (berpendapat), dapat dikomposisi, dan benar-benar dapat digunakan kembali yang dapat kami gunakan di mana saja.

IMHO satu-satunya masalah adalah memanggil useUserNickname menakutkan karena satu fungsi dapat melakukan sebanyak ini.
tetapi dalam pengalaman saya selama bertahun-tahun dalam bereaksi dan menggunakan flutter_hooks dalam 2 aplikasi yang sedang dalam produksi rn (yang sangat menggunakan kait) membuktikan bahwa tidak memiliki manajemen status yang baik (saya juga mencoba MobX dan solusi manajemen negara lainnya tetapi lem di widget selalu ada) jauh lebih menakutkan. Saya tidak perlu menulis 5 halaman dokumen untuk setiap layar di aplikasi frontend sehingga saya mungkin perlu menambahkan beberapa fitur kecil dalam beberapa bulan setelah rilis pertama untuk memahami cara kerja halaman aplikasi saya. Aplikasi memanggil server terlalu banyak? tugas mudah Saya menggunakan kait terkait dan mengubahnya dan seluruh aplikasi diperbaiki karena seluruh aplikasi menggunakan kait itu. kita dapat memiliki hal-hal serupa di aplikasi tanpa menggunakan kait dengan abstraksi yang baik tetapi apa yang saya katakan adalah bahwa kait adalah abstraksi yang bagus.

Saya cukup yakin @gaearon bisa mengatakannya lebih baik dari saya. (jika dia setuju dengan saya ofc)

melihat contoh di atas, tidak ada metode di atas (status dan widget kait) yang lebih berkinerja daripada yang lain. tapi intinya adalah salah satunya mendorong orang untuk menulis kode performant.

juga dimungkinkan untuk memperbarui hanya subpohon yang perlu kita perbarui seperti StreamBuilder ketika ada terlalu banyak pembaruan (misalnya animasi) dengan:

1 - cukup buat widget baru yang merupakan opsi yang benar-benar layak untuk HookWidget dan StatefulWidget/StatelessWidget
2 - menggunakan sesuatu yang mirip dengan HookWidgetBuilder dalam paket flutter_hooks karena data widget induk dan anak digabungkan dengan sangat erat.

Catatan tambahan: Saya sangat menghargai @Hixie dan @rrousselGit karena telah mendiskusikan topik ini dan mencurahkan banyak energi untuk masalah ini. Saya sangat menantikan hasil pembicaraan ini.

Saya pikir saya datang dengan sesuatu yang sangat keren/elegan, berdasarkan titik awal @Hixie . Belum siap untuk dibagikan, tetapi ini memungkinkan saya untuk membuat beberapa contoh kode yang cukup baik, yang menurut saya akan lebih mudah untuk membandingkan apples:apples daripada hooks yang terlihat begitu asing.

Jadi, bayangkan kita memiliki StatefulWidget dengan tanda tangan ini:

class ExampleSimple extends StatefulWidget {
  final Duration duration1;
  final Duration duration2;
  final Duration duration3;

  const ExampleSimple({Key key, this.duration1, this.duration2, this.duration3}) : super(key: key);

  <strong i="9">@override</strong>
  _ExampleSimpleState createState() => _ExampleSimpleState();
}

Jika kami mengimplementasikan status menggunakan pengontrol animator Vanilla, kami mendapatkan sesuatu seperti:

class _ExampleSimpleVanillaState extends State<ExampleSimpleVanilla> with SingleTickerProviderStateMixin {
  AnimationController _anim1;
  AnimationController _anim2;
  AnimationController _anim3;

  <strong i="13">@override</strong>
  void initState() {
    _anim1 = AnimationController(duration: widget.duration1, vsync: this);
    _anim1.forward();
    _anim2 = AnimationController(duration: widget.duration2, vsync: this);
    _anim2.forward();
    _anim3 = AnimationController(duration: widget.duration3, vsync: this);
    _anim3.forward();
    super.initState();
  }

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: _anim2.value * 20, horizontal: _anim3.value * 30,),
      color: Colors.red.withOpacity(_anim1.value),
    );
  }

  <strong i="15">@override</strong>
  void didUpdateWidget(ExampleSimpleVanilla oldWidget) {
    if (oldWidget.duration1 != widget.duration1) {
      _anim1.duration = widget.duration1;
    }
    if (oldWidget.duration2 != widget.duration2) {
      _anim1.duration = widget.duration1;
    }
    if (oldWidget.duration3 != widget.duration3) {
      _anim1.duration = widget.duration1;
    }
    super.didUpdateWidget(oldWidget);
  }

  <strong i="16">@override</strong>
  void dispose() {
    _anim1.dispose();
    _anim2.dispose();
    _anim3.dispose();
    super.dispose();
  }
}

Jika kita membuatnya menggunakan StatefulProperty, kita menghasilkan sesuatu yang lebih seperti ini:

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulAnimationProperty _anim1;
  StatefulAnimationProperty _anim2;
  StatefulAnimationProperty _anim3;

  <strong i="6">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _anim1 = initProperty(_anim1, StatefulAnimationProperty(duration: widget.duration1, playOnInit: true));
    _anim2 = initProperty(_anim2, StatefulAnimationProperty(duration: widget.duration2, playOnInit: true));
    _anim3 = initProperty(_anim3, StatefulAnimationProperty(duration: widget.duration3, playOnInit: true));
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="7">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: _anim2.controller.value * 20, horizontal: _anim3.controller.value * 30,),
      color: Colors.red.withOpacity(_anim1.controller.value),
    );
  }
}

Beberapa catatan tentang perbedaan di sini:

  1. Di atas, satu adalah 20 baris, yang lain adalah 45. Satu adalah 1315 karakter yang lain adalah 825. Hanya 3 baris dan 200 karakter yang penting di kelas ini (apa yang terjadi di build), jadi ini sudah merupakan peningkatan besar dalam sinyal : rasio
  2. Opsi vanilla memiliki banyak titik di mana bug dapat dibuat. Lupa membuang, atau lupa menangani didChange, atau membuat kesalahan di didChange, dan Anda memiliki bug di basis kode Anda. Ini menjadi lebih buruk, ketika beberapa jenis pengontrol digunakan. Kemudian Anda memiliki fungsi tunggal yang merobohkan objek dari semua jenis yang berbeda, yang tidak akan dinamai Nice dan berurutan seperti ini. Itu menjadi berantakan dan cukup mudah untuk membuat kesalahan atau melewatkan entri.
  3. Opsi vanilla tidak menyediakan metode untuk menggunakan kembali pola atau logika umum, seperti playOnInit, jadi saya harus menduplikasi logika ini, atau membuat beberapa fungsi khusus di kelas tunggal yang ingin menggunakan Animator.
  4. Tidak perlu memahami SingleTickerProviderMixin di sini, yang merupakan 'ajaib' dan mengaburkan apa itu Ticker bagi saya selama berbulan-bulan (jika dipikir-pikir, saya seharusnya baru saja membaca kelas, tetapi setiap tutorial hanya mengatakan: Tambahkan mixin ajaib ini). Di sini Anda dapat melihat langsung kode sumber untuk StatefulAnimationProperty, dan melihat bagaimana pengontrol animasi menggunakan penyedia ticker secara langsung dan dalam konteks.

Anda memang harus memahami apa yang StatefulPropertyManager lakukan, tetapi yang terpenting ini dipelajari sekali dan diterapkan ke objek jenis apa pun, SingleTickerProviderMixin terutama khusus untuk menggunakan Pengontrol Animator, dan setiap pengontrol mungkin memiliki pencampurannya sendiri untuk membuat penggunaan lebih mudah, yang menjadi berantakan. Hanya memiliki "StatefulObjects" terpisah yang mengetahui semua hal ini (seperti halnya pembangun!), Jauh lebih bersih dan skala lebih baik.

Kode untuk StatefulAnimationProperty akan terlihat seperti ini:

class StatefulAnimationProperty extends BaseStatefulProperty<StatefulAnimationProperty> implements TickerProvider {
  final Duration duration;
  final TickerProvider vsync;
  final bool playOnInit;

  StatefulAnimationProperty({<strong i="7">@required</strong> this.duration, <strong i="8">@required</strong> this.vsync, this.playOnInit = false});

  AnimationController get controller => _controller;
  AnimationController _controller;

  Ticker _ticker;

  <strong i="9">@override</strong>
  Ticker createTicker(onTick) {
    _ticker ??= Ticker((elapsed)=>handleTick(elapsed, onTick));
    return _ticker;
  }

  handleTick(Duration elapsed, TickerCallback onTick) {
    managerWidget.buildWidget(); //TODO: This just calls setState() on the host widget. Is there some better way to do this?
    onTick(elapsed);
  }

  <strong i="10">@override</strong>
  void init(StatefulAnimationProperty old) {
    _controller = old?.controller ??
        AnimationController(
          duration: duration ?? Duration(seconds: 1),
          vsync: vsync ?? this,
        );
    if (playOnInit && old?.controller == null) {
      _controller.forward();
    }
    super.init(old);
  }

  <strong i="11">@override</strong>
  void update(StatefulAnimationProperty old) {
    if (duration != old.duration) {
      _controller.duration = duration;
    }
    if (vsync != old.vsync) {
      _controller.resync(vsync);
    }
    super.update(old);
  }

  <strong i="12">@override</strong>
  void dispose() {
    _controller?.dispose();
    _ticker?.dispose();
    super.dispose();
  }
}

Akhirnya, perlu diperhatikan, keterbacaan dapat dibuat lebih baik dengan menggunakan ekstensi, sehingga kita dapat memiliki sesuatu seperti:

  void initStatefulProperties({bool firstRun = false}) {
    _anim1.init(duration: widget.duration1, playOnInit: true);
    _anim2.init(duration: widget.duration2, playOnInit: true);
    _anim3.init(duration: widget.duration3, playOnInit: true);
    super.initStatefulProperties(firstRun: firstRun);
  }

[Sunting] Seolah ingin membuat poin saya sendiri, contoh vanilla saya memiliki bug. Saya lupa memberikan durasi yang benar ke setiap animator di didUpdateWidget. Berapa lama waktu yang kami butuhkan untuk menemukan bug itu di alam liar, jika tidak ada yang memperhatikan dalam tinjauan kode?? Apakah ada yang tidak melihatnya saat membaca? Membiarkannya karena itu adalah contoh sempurna dari apa yang terjadi di dunia nyata.

Berikut pandangan mata burung, dengan boilerplate ditandai dengan warna merah:
image

Ini tidak akan terlalu buruk jika itu murni boilerplate, dan kompiler meneriaki Anda jika itu hilang. Tapi itu semua opsional! Dan ketika dihilangkan, menciptakan bug. Jadi ini sebenarnya praktik yang sangat buruk dan tidak KERING sama sekali. Di sinilah pembangun masuk, tetapi mereka hanya bagus untuk kasus penggunaan yang sederhana.

Apa yang menurut saya sangat menarik tentang ini, adalah bagaimana seratus baris dan mixin sederhana ke State, membuat sekelompok kelas yang ada menjadi mubazir. Hampir tidak ada kebutuhan sekarang untuk menggunakan TickerProviderMixins misalnya. TweenAnimationBuilder hampir tidak perlu digunakan, kecuali jika Anda benar-benar _ingin_ membuat sub-konteks. Banyak poin nyeri tradisional seperti mengelola pengontrol fokus dan pengontrol input teks berkurang secara substansial. Menggunakan Streams menjadi jauh lebih menarik dan tidak terlalu kaku. Di seluruh basis kode, penggunaan Builder secara umum dapat dikurangi yang akan menyebabkan pohon lebih mudah di-grok.

Juga membuatnya _sangat_ mudah untuk membuat objek status kustom Anda sendiri, seperti contoh FetchUser yang tercantum sebelumnya, yang saat ini pada dasarnya memerlukan pembangun.

Saya pikir ini akan menjadi pertanyaan yang sangat menarik untuk ditanyakan dalam Survei Pengembang Flutter berikutnya.

Ini akan menjadi awal yang baik. Bagilah masalah ini dalam bagian/pertanyaan yang berbeda dan lihat apakah ini masalah nyata yang ingin diselesaikan oleh pengembang Flutter.

Setelah jelas, percakapan ini akan lebih lancar dan memperkaya

Reaksi Emoji di bawah setiap komentar memberikan gambaran yang jelas apakah komunitas melihat ini sebagai masalah atau tidak. Pendapat pengembang yang membaca 250+ komentar panjang untuk masalah ini sangat berarti.

@esDotDev Itu mirip dengan beberapa ide yang telah saya mainkan, meskipun saya suka ide Anda untuk menjadikan properti itu sendiri sebagai penyedia ticker, saya tidak mempertimbangkannya. Satu hal yang hilang dari implementasi Anda yang menurut saya perlu kita tambahkan adalah penanganan TickerMode (yang merupakan inti dari TickerProviderStateMixin).

Hal utama yang saya perjuangkan adalah bagaimana melakukan ini dengan cara yang efisien. Misalnya, ValueListenableBuilder mengambil argumen turunan yang dapat digunakan untuk meningkatkan kinerja secara terukur. Saya tidak melihat cara untuk melakukannya dengan pendekatan Properti.

@Hixie
Saya memahami bahwa kerugian efisiensi dengan pendekatan seperti ini tampaknya tidak dapat dihindari. Tapi saya suka pola pikir Flutter untuk mengoptimalkan setelah Anda membuat profil kode Anda. Ada banyak aplikasi yang akan mendapat manfaat dari kejelasan dan keringkasan pendekatan Properti. Opsi untuk memprofilkan kode Anda, dan memfaktorkan ulang menjadi pembangun atau memisahkan sepotong widget ke dalam widgetnya sendiri selalu ada.

Dokumentasi hanya perlu mencerminkan praktik terbaik dan memperjelas pengorbanannya.

Hal utama yang saya perjuangkan adalah bagaimana melakukan ini dengan cara yang efisien. Misalnya, ValueListenableBuilder mengambil argumen turunan yang dapat digunakan untuk meningkatkan kinerja secara terukur. Saya tidak melihat cara untuk melakukannya dengan pendekatan Properti.

Hm, saya pikir inti dari Properties adalah untuk objek non-visual. Jika sesuatu ingin memiliki slot konteks di pohon, maka benda itu harus menjadi pembangun (sebenarnya ini adalah satu-satunya hal yang sekarang harus menjadi pembangun menurut saya?)

Jadi kita akan memiliki StatefulValueListenableProperty yang sering kita gunakan ketika kita hanya ingin mengikat seluruh tampilan. Kami kemudian juga memiliki ValueListenableBuilder jika kami ingin beberapa sub-bagian dari pohon kami dibangun kembali.

Ini juga mengatasi masalah bersarang, karena menggunakan pembangun sebagai simpul daun, hampir tidak mengganggu keterbacaan, seperti bersarang 2 atau 3 di bagian atas pohon widget Anda.

@TimWhiting Sebagian besar filosofi desain Flutter adalah memandu orang menuju pilihan yang tepat. Saya ingin menghindari mendorong orang untuk mengikuti gaya yang kemudian harus mereka tinggalkan untuk mendapatkan kinerja yang lebih baik. Mungkin tidak ada cara untuk memenuhi semua kebutuhan sekaligus, tetapi kita harus mencobanya.

@Hixie
Bagaimana dengan sesuatu seperti ini untuk pembangun?

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulAnimationProperty _anim1;
  StatefulAnimationBuilderProperty _anim2;

  <strong i="7">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _anim1 = initProperty(_anim1, StatefulAnimationProperty(duration: widget.duration1, playOnInit: true));
    _anim2 = initProperty(_anim3, StatefulAnimationBuilderProperty(duration: widget.duration3, playOnInit: true));
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="8">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red.withOpacity(_anim1.controller.value),
      child: _anim2(child: SomeChildWidget()),
    );
  }
}

Bisakah Anda menguraikan? Saya tidak yakin saya mengerti proposalnya.

Saya pikir dia mengatakan bahwa StatefulProperty dapat menyediakan metode build opsional untuk properti yang memiliki beberapa komponen visual:

return Column(
   children: [
      TopContent(),
      _valueProperty.build(SomeChildWidget()),
   ]
)

Yang cukup imo,

Ya, saya tidak tahu apakah itu akan berhasil, tetapi metode build akan mengambil anak seperti builder biasa, kecuali properti builder lainnya disetel oleh properti.
Jika Anda memerlukan konteks dari builder, maka metode build menerima argumen builder yang menyediakan konteks.

Di bawah tenda, metode ini mungkin hanya membuat pembangun normal dengan properti yang ditentukan, dan meneruskan argumen anak ke pembangun normal dan mengembalikannya.

Misalkan Anda memiliki kode ini:

Widget build(BuildContext context) {
  return ExpensiveParent(
    child: ValueListenableBuilder(
      valueListenable: foo,
      child: ExpensiveChild(),
      builder: (BuildContext context, value, Widget child) {
        return SomethingInTheMiddle(
          value: value,
          child: child,
        );
      }
    ),
  );
}

... bagaimana Anda akan mengubahnya?

@esDotDev Saya suka ide Anda untuk menjadikan properti itu sendiri sebagai penyedia ticker, saya belum mempertimbangkannya.

Ya, salah satu aspek terkuat dari pendekatan gaya kait ini, adalah Anda dapat _ sepenuhnya_ merangkum logika stateful, apa pun itu. Jadi dalam hal ini daftar lengkap untuk AC adalah:

  1. Buat ACnya
  2. Beri tanda centang
  3. Menangani perubahan widget
  4. Menangani pembersihan ac dan ticker
  5. Bangun kembali tampilan di centang

Saat ini semuanya terpecah dengan pengembang yang menangani (atau tidak menangani) 1,3,4 secara manual dan berulang, dan SingleTickerProviderMixin semi-ajaib menangani 2 dan 5 (dengan kami melewati 'ini' sebagai vsync, yang membingungkan saya selama berbulan-bulan! ). Dan SingleTickerProviderMixin sendiri jelas merupakan upaya perbaikan untuk jenis masalah ini, jika tidak, mengapa tidak langsung saja dan meminta kami mengimplementasikan TickerProvider untuk setiap kelas, itu akan jauh lebih jelas.

Misalkan Anda memiliki kode ini:

Widget build(BuildContext context) {
  return ExpensiveParent(
    child: ValueListenableBuilder(
      valueListenable: foo,
      child: ExpensiveChild(),
      builder: (BuildContext context, value, Widget child) {
        return SomethingInTheMiddle(
          value: value,
          child: child,
        );
      }
    ),
  );
}

... bagaimana Anda akan mengubahnya?

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulValueListenableBuilder _fooBuilder;

  <strong i="10">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _fooBuilder = initProperty(StatefulValueListenableProperty(valueListenable: widget.foo)); 
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    return ExpensiveParent(
      child: SomethingInTheMiddle(
        _fooBuilder.value,
        _fooBuilder.builder(childBuilder: () => ExpensiveChild()),
      ),
    );
  }
}

@Hixie
Terima kasih untuk contoh. Saya memberikan kesempatan terbaik saya. Saya mungkin melewatkan sesuatu.

Penting untuk dicatat bahwa builder men-cache anak. Pertanyaannya adalah kapan perlu membangun kembali anak itu? Saya pikir itu adalah pertanyaan yang Anda coba ajukan..

@Hixie apakah Anda sudah melihat https://github.com/flutter/flutter/issues/51752#issuecomment -671104377
Saya pikir ada beberapa poin yang sangat bagus.
Saya membangun sesuatu hari ini dengan banyak ValueListenableBuilder dan saya hanya bisa mengatakan itu tidak bagus untuk dibaca.

@Hixie
Terima kasih untuk contoh. Saya memberikan kesempatan terbaik saya. Saya mungkin melewatkan sesuatu.

Saya tidak berpikir ini berfungsi karena Properti mengikat ke status yang ditentukan, jadi ExpensiveParent selalu dibangun kembali di sini. Lalu saya pikir caching anak juga bermasalah, seperti pada contoh Builder itu akan tahu untuk hanya membangun kembali anak ketika negara induk dibangun, tetapi dalam metode ini Properti tidak tahu kapan harus membatalkan cache itu (tapi mungkin ini dapat dipecahkan?)

Tapi tbh, ini adalah kasus penggunaan yang sempurna untuk pembangun, ketika Anda ingin memperkenalkan konteks baru. Saya pikir cukup elegan untuk hanya memiliki konteks StatefulProperties (keadaan murni) dan StatefulWidgets (campuran status dan tata letak).

Setiap kali Anda sengaja membuat sub-konteks, Anda akan melakukannya lebih jauh ke bawah pohon Anda, yang membantu memerangi salah satu kelemahan utama pembangun (bersarang paksa di seluruh pohon)

@escamoteur (dan @sahandevs yang menulis komentar itu) ya saya mempelajarinya sebelumnya. Saya pikir itu pasti membantu menunjukkan jenis logika yang ingin dihapus orang. Saya pikir, bagaimanapun, bahwa contoh itu sendiri agak meragukan karena saya mengharapkan sebagian besar logika (misalnya segala sesuatu di sekitar caching) berada dalam logika bisnis keadaan aplikasi, dan tidak jauh dari widget. Saya juga tidak bisa melihat cara yang baik untuk mendapatkan sintaks sesingkat yang diusulkan dalam komentar itu tanpa merusak hot reload (misalnya jika Anda mengubah jumlah kait yang Anda gunakan, tidak jelas bagaimana mereka bisa tetap stateful di reload ).

Konon, menurut saya karya @esDotDev dan @TimWhiting di atas sangat menarik dan bisa menyelesaikan masalah ini. Ini tidak sesingkat Hooks, tetapi lebih dapat diandalkan. Saya pikir masuk akal untuk mengemas sesuatu seperti itu, bahkan bisa menjadi Favorit Flutter jika berfungsi dengan baik. Saya tidak yakin itu masuk akal sebagai fitur kerangka kerja inti karena peningkatannya tidak _itu_ substansial setelah Anda memperhitungkan kompleksitas di sekitar properti bangunan dan dampak kinerja, dan bagaimana orang yang berbeda lebih suka gaya yang berbeda. Pada akhirnya, seharusnya tidak masalah bagi orang yang berbeda untuk menggunakan gaya yang berbeda, tetapi kami tidak ingin kerangka kerja inti memiliki banyak gaya, itu hanya menyesatkan bagi pengembang baru.

Ada juga argumen yang dibuat bahwa cara yang tepat untuk mempelajari Flutter adalah dengan memahami widget terlebih dahulu, lalu mempelajari alat yang mengabstraksikannya (Hooks atau apa pun), daripada langsung beralih ke sintaks abstrak. Jika tidak, Anda kehilangan komponen kunci tentang cara kerja sistem yang kemungkinan akan menyesatkan Anda dalam hal penulisan kode kinerja.

Saya juga tidak bisa melihat cara yang baik untuk mendapatkan sintaks sesingkat yang diusulkan dalam komentar itu tanpa merusak hot reload (misalnya jika Anda mengubah jumlah kait yang Anda gunakan, tidak jelas bagaimana mereka bisa tetap stateful di reload ).

Kait bekerja dengan hot-reload tanpa masalah.
Kait pertama dengan runtimeType yang tidak cocok menyebabkan semua kait berikutnya dihancurkan.

Ini mendukung penambahan, penghapusan, dan penataan ulang.

Saya pikir ada argumen bahwa abstraksi penuh lebih disukai daripada parsial yang ada saat ini.

Jika saya ingin memahami cara kerja Animator dalam konteks Properti, saya akan mengabaikannya sepenuhnya, atau langsung masuk, dan semuanya baik-baik saja di sana, mandiri dan koheren.

Jika saya ingin memahami bagaimana AnimatorController bekerja dalam konteks StatefulWidget, saya perlu (dipaksa) untuk memahami kait siklus hidup dasar, tetapi kemudian terhindar dari memahami cara kerja mekanisme centang yang mendasarinya. Ini adalah yang terburuk dari kedua dunia dalam beberapa hal. Tidak cukup sihir untuk membuatnya 'berfungsi', tetapi cukup untuk membingungkan pengguna baru dan memaksa mereka untuk mempercayai secara membabi buta pada beberapa mixin (yang dengan sendirinya merupakan konsep baru untuk sebagian besar) dan properti vsync yang ajaib.

Saya tidak yakin dengan contoh lain dalam basis kode, tetapi ini akan berlaku untuk situasi apa pun di mana beberapa mixin pembantu telah disediakan untuk StatefulWidget, tetapi masih ada beberapa bootstrap lain yang harus selalu dilakukan. Dev akan mempelajari bootstrap (bagian yang membosankan) dan mengabaikan Mixin (bagian yang menarik/kompleks)

Konon, menurut saya karya @esDotDev dan @TimWhiting di atas sangat menarik dan bisa menyelesaikan masalah ini. Ini tidak sesingkat Hooks, tetapi lebih dapat diandalkan

Bagaimana ini lebih dapat diandalkan?

Kami masih tidak dapat membuat/memperbarui properti secara kondisional atau di luar siklus hidupnya karena kami dapat memasuki kondisi buruk. Misalnya, memanggil properti secara kondisional tidak akan membuang properti saat kondisinya salah.
Dan semua properti masih dievaluasi ulang pada setiap pembangunan kembali.

Tetapi ini menyebabkan banyak masalah, seperti memaksa pengguna untuk menggunakan ! mana pun setelah NNBD atau berpotensi mengizinkan pengguna mengakses properti sebelum diperbarui.

Misalnya, bagaimana jika seseorang membaca properti di dalam didUpdateWidget ?

  • Apakah initProperties dieksekusi sebelum siklus hidup? Tapi itu berarti kita mungkin harus memperbarui properti beberapa kali per build.
  • Apakah initProperties dieksekusi setelah didUpdateWidget? Kemudian menggunakan properti di dalam didUpdateWidget dapat menyebabkan status usang

Jadi pada akhirnya, kami memiliki semua masalah kait tetapi:

  • kami tidak dapat menggunakan Properti di dalam `StatelessWidget. Jadi keterbacaan StreamBuilder/ValueListenableBuilder/... masih menjadi masalah – yang menjadi perhatian utama.
  • ada banyak kasus tepi
  • lebih sulit untuk membuat properti khusus (kita tidak bisa hanya mengekstrak banyak properti menjadi suatu fungsi)
  • lebih sulit untuk mengoptimalkan pembangunan kembali

Pada akhirnya, contoh yang diberikan tidak berbeda dalam perilaku dari:

class Example extends StatelessWidget {
  <strong i="29">@override</strong>
  Widget build(context) {
    final value1 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));
    final value2 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));
    final value3 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    return Container(
     margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
     color: Colors.red.withOpacity(value1),
      child: _anim2(child: SomeChildWidget()),
    );
  }
}

Tetapi sintaks ini mendukung lebih banyak hal, seperti:

Pengembalian awal:

class Example extends StatelessWidget {
  <strong i="34">@override</strong>
  Widget build(context) {
    final value1 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    if (condition) {
      return Container();
    }

    final value2 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    ...
  }
}

yang akan membuang value2 ketika condition beralih ke false

Mengekstrak bundel pembangun ke dalam suatu fungsi:

Widget build(context) {
  final foo = keyword FooBuilder();
  final bar = keyword BarBuilder();

  return Text('$foo $bar');
}

dapat diubah menjadi:

Builder<String> LabelBuilder() builder* {
  final foo = keyword FooBuilder();
  final bar = keyword BarBuilder();

  return '$foo $bar';
}

Widget build(context) {
  final label = keyword LabelBuilder();

  return Text(label);
}

Optimalkan pembangunan kembali

Parameter child masih layak:

Widget build(context) {
  final value = keyword StreamBuilder();

  return Builder(
    builder: (context, child) {
      final value2 = keyword TweenAnimationBuilder();
      final value = keyword ValueListenableBuilder();

      return Whatever(child: child);
    },
    child: ExpensiveChild()
  );
}

Sebagai bagian dari bahasa, kita bahkan dapat memiliki gula sintaks untuk ini:

Widget build(context) {
  return Scaffold(
    body: {
      final value = keyword TweenAnimationBuilder();
      final value2 = keyword ValueListenableBuilder();

      return Text();
    },
  );
}

Bonus: Sebagai fitur bahasa, panggilan bersyarat didukung

Sebagai bagian dari bahasa, kami dapat mendukung skenario tersebut:

Widget build(context) {
  String label;

  if (condition) {
    label = keyword LabelBuilder();
  } else {
    label = keyword AnotherBuilder();
  }

  final value2 = keyword WhateverBuilder();

  return ...
}

Ini tidak terlalu berguna, tetapi didukung – karena sintaks dikompilasi, ia dapat membedakan setiap penggunaan keyword dengan mengandalkan metadata yang tidak tersedia sebaliknya.

Mengenai keterbacaan pembangun, berikut adalah contoh sebelumnya, tetapi dilakukan dengan pembangun. Ini menyelesaikan semua keandalan dan kebutuhan penggunaan kode, tetapi lihat apa yang telah dilakukan pada pohon widget saya yang buruk :'(

class _ExampleSimpleBuilderState extends State<ExampleSimpleBuilder> {
  <strong i="6">@override</strong>
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
        tween: Tween(begin: 0, end: 1),
        duration: widget.duration1,
        builder: (_, value1, __) {
          return TweenAnimationBuilder<double>(
              tween: Tween(begin: 0, end: 1),
              duration: widget.duration2,
              builder: (_, value2, __) {
                return TweenAnimationBuilder<double>(
                    tween: Tween(begin: 0, end: 1),
                    duration: widget.duration3,
                    builder: (_, value3, __) {
                      return Container(
                        margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
                        color: Colors.red.withOpacity(value1),
                      );
                    });
              });
        });
  }
}

Jauh lebih sulit (setidaknya bagi mata saya) untuk menemukan kode yang penting. Juga, fwiw, saya harus memulai dari awal seperti 3 kali ketika menulis ini, karena saya terus-menerus bingung dengan braket mana yang termasuk di mana, di mana semi-colans saya harus pergi dll. Pembangun bersarang tidak menyenangkan untuk menulis atau bekerja di dalamnya. Satu titik koma dan dartfmt yang salah benar-benar menghancurkan semuanya.

Bagaimana ini lebih dapat diandalkan?

Ini adalah contoh sempurna mengapa _harus_ ini menjadi plugin inti imo. Pengetahuan domain yang diperlukan di sini adalah _deep_. Saya memiliki pengetahuan skrip untuk menerapkan sistem caching sederhana seperti ini, saya bahkan tidak memiliki pengetahuan domain yang dekat untuk mengetahui setiap kasus tepi yang dapat terjadi, atau keadaan buruk yang dapat kita alami. Selain Remi, saya rasa ada 4 developer di dunia di luar tim Flutter yang mengetahui hal ini! (melebih-lebihkan jelas).

Masalah mendukung Widget Tanpa Kewarganegaraan adalah masalah yang bagus. Di satu sisi saya mengerti, StatefulWidgets anehnya bertele-tele. Di sisi lain, di sini kita benar-benar berbicara tentang verbositas murni. Tidak ada bug yang dapat terjadi karena harus mendefinisikan 2 kelas, tidak mungkin Anda dapat mengacaukannya, kompiler tidak mengizinkan Anda, tidak pernah ada hal menarik yang ingin saya lakukan di StatelessWidget. Jadi saya tidak menjual ini menjadi masalah besar ... PASTI akan menyenangkan untuk dimiliki, tapi ini adalah 5% terakhir, bukan sesuatu untuk terjebak.

Di sisi lain... sintaks dari remi dengan dukungan kata kunci benar-benar indah dan sangat fleksibel/kuat. Dan jika itu memberi Anda dukungan StatelessWidget gratis, maka itu hanya tambahan

Mendukung StatelessWidget adalah masalah besar IMO. Opsional, tapi masih sangat keren.

Meskipun saya setuju bahwa itu tidak kritis, orang-orang sudah berebut menggunakan fungsi alih-alih StatelessWidget.
Mengharuskan orang untuk menggunakan StatefulWidget untuk menggunakan Builder (karena sebagian besar Builder kemungkinan akan memiliki setara Properti) hanya akan memperdalam konflik.

Tidak hanya itu, di dunia di mana kita dapat membuat fungsi tingkat tinggi di dart (https://github.com/dart-lang/language/issues/418), kita bisa menyingkirkan kelas sama sekali:

<strong i="9">@StatelessWidget</strong>
Widget Example(BuildContext context, {Key key, String param}) {
  final value = keyword StreamBuilder();

  return Text('$value');
}

kemudian digunakan sebagai:

Widget build(context) {
  // BuildContext and Key are automatically injected
  return Example(param: 'hello');
}

Ini adalah sesuatu yang didukung oleh functional_widget – yang merupakan pembuat kode tempat Anda menulis fungsi dan menghasilkan kelas untuk Anda – yang juga mendukung HookWidget .

Perbedaannya adalah, memiliki dukungan untuk fungsi tingkat tinggi di Dart akan menghilangkan kebutuhan akan pembuatan kode untuk mendukung sintaks tersebut.

Saya menduga apa yang dimaksud @Hixie dengan lebih andal, apakah tidak menderita urutan operasi/masalah bersyarat yang dimiliki kait, karena itu sangat 'tidak dapat diandalkan' dari POV arsitektur (walaupun saya menyadari itu adalah aturan yang mudah untuk belajar dan tidak melanggar sekali belajar).

Tapi begitu juga proposal Anda dengan kata kunci. Saya pikir kasus untuk kata kunci baru cukup kuat:

  • Lebih fleksibel dan dapat disusun daripada mencangkok ke Negara
  • Bahkan sintaks yang lebih ringkas
  • Bekerja di Stateless yang merupakan pilihan yang sangat bagus untuk dimiliki

Apa yang saya tidak suka tentang itu, adalah kami mengkhawatirkan biaya pengaturan properti pada beberapa objek sederhana beberapa kali/build, tetapi kemudian menganjurkan solusi yang pada dasarnya akan menciptakan sejuta tingkat konteks dan banyak biaya tata letak. Apakah saya salah paham?

Kelemahan lainnya adalah ide sihir ini. Tetapi jika Anda akan melakukan sesuatu yang ajaib, saya pikir kata kunci baru adalah cara yang efektif untuk menyelesaikannya, karena memudahkan untuk menyoroti dan memanggil komunitas, dan menjelaskan apa itu dan cara kerjanya. Pada dasarnya semua orang membicarakannya untuk tahun depan di Flutter dan saya yakin kita akan melihat ledakan plugin keren yang muncul darinya.

Saya menduga apa yang dimaksud @Hixie dengan lebih andal, apakah tidak menderita urutan operasi/masalah bersyarat yang dimiliki kait, karena itu sangat 'tidak dapat diandalkan' dari POV arsitektur (walaupun saya menyadari itu adalah aturan yang mudah untuk belajar dan tidak melanggar sekali belajar).

Tetapi hook juga tidak mengalami masalah seperti itu, karena hook dapat dianalisis secara statis, dan oleh karena itu kita dapat memiliki kesalahan kompilasi ketika disalahgunakan.

Ini bukan masalah

Demikian pula jika kesalahan khusus tidak boleh dilakukan, maka seperti yang saya sebutkan sebelumnya, Properti menderita masalah yang sama persis.
Kami tidak dapat menulis secara wajar:

Property property;

<strong i="12">@override</strong>
void initProperties() {
  if (condition) {
    property = init(property, MyProperty());
  }
}

karena mengalihkan condition dari true ke false tidak akan membuang properti.

Kami juga tidak bisa menyebutnya dalam satu lingkaran. Itu tidak masuk akal, karena ini adalah tugas satu kali. Apa kasus penggunaan menjalankan properti dalam satu lingkaran?

Dan fakta bahwa kita dapat membaca properti dalam urutan apa pun terdengar berbahaya
Misalnya kita bisa menulis:

Property first;
Property second;

<strong i="20">@override</strong>
void initProperties() {
  // The state of first depends on second, but second is updated after first
  // So we could end up in a bad state, similar to how the build method of a Widget should depend
  // on the context.size
  first = init(property, MyProperty(second?.value));

  second = init(property, Whatever());
}
> class _ExampleSimpleBuilderState extends State<ExampleSimpleBuilder> {
>   <strong i="5">@override</strong>
>   Widget build(BuildContext context) {
>     return TweenAnimationBuilder<double>(
>         tween: Tween(begin: 0, end: 1),
>         duration: widget.duration1,
>         builder: (_, value1, __) {
>           return TweenAnimationBuilder<double>(
>               tween: Tween(begin: 0, end: 1),
>               duration: widget.duration2,
>               builder: (_, value2, __) {
>                 return TweenAnimationBuilder<double>(
>                     tween: Tween(begin: 0, end: 1),
>                     duration: widget.duration3,
>                     builder: (_, value3, __) {
>                       return Container(
>                         margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
>                         color: Colors.red.withOpacity(value1),
>                       );
>                     });
>               });
>         });
>   }
> }

Ini adalah contoh yang aneh. Apakah Anda yakin AnimatedContainer belum bisa melakukan ini?

Tentu saja. Contoh di sini adalah menggunakan 3 animasi di beberapa widget untuk melakukan "X". X sengaja disederhanakan dalam contoh untuk menyoroti jumlah pelat ketel.

Jangan fokus pada bagaimana saya menggunakannya. Dalam contoh nyata, widget "inti" akan menjadi seratus baris atau sesuatu, properti animasi tidak akan sesederhana itu, dan kami akan memiliki beberapa penangan dan fungsi lain yang ditentukan. Asumsikan saya melakukan sesuatu yang tidak ditangani oleh salah satu widget implisit (tidak sulit karena selain AnimatedContainer, mereka sangat bertujuan tunggal).

Intinya adalah bahwa ketika membangun sesuatu seperti ini, pembangun tidak bekerja dengan baik, karena mereka memasukkan Anda ke dalam lubang keterbacaan (dan dapat ditulis) untuk memulai, karena itu mereka sangat cocok untuk kasus penggunaan sederhana, mereka tidak "menulis" dengan baik. Compose adalah komposisi dari 2 hal atau lebih.

Jangan fokus pada bagaimana saya menggunakannya. Dalam contoh nyata, ...

...dan kembali ke titik awal. Mengapa Anda tidak memberikan contoh nyata ?

Anda memerlukan contoh nyata menggunakan animasi yang kompleks?
https://github.com/gskinnerTeam/flutter_vignettes

Menampilkan beberapa animasi kompleks yang arbitrer tidak akan menghasilkan apa-apa selain mengaburkan contoh. Cukup untuk mengatakan, ada banyak kasus penggunaan untuk menggunakan beberapa animator (atau objek stateful lainnya yang dapat Anda bayangkan) di dalam beberapa widget

Tentu saja. Contoh di sini adalah menggunakan 3 animasi di beberapa widget untuk melakukan "X". X sengaja disederhanakan dalam contoh untuk menyoroti jumlah pelat ketel.

widget "inti" akan menjadi seratus baris atau sesuatu

Di posting lain Anda memposting contoh dengan boilerplate yang mengaburkan "inti", tetapi sekarang Anda memberi tahu kami bahwa intinya akan menjadi ratusan baris? Jadi pada kenyataannya, pelat ketel akan sangat kecil dibandingkan dengan inti? Anda tidak dapat memiliki keduanya.
Anda terus-menerus mengubah argumen Anda.

Jangan fokus pada bagaimana saya menggunakannya. Dalam contoh nyata, ...

...dan kembali ke titik awal. Mengapa Anda tidak memberikan contoh nyata ?

Mungkin karena butuh banyak waktu untuk membuat contoh nyata ketika hanya bermain-main dengan berbagai ide. Tujuannya adalah agar pembaca membayangkan bagaimana itu bisa digunakan dalam situasi nyata, tidak menyebutkan bahwa ada cara untuk mengatasinya. Tentu saja seseorang dapat menggunakan wadah animasi, tetapi bagaimana jika tidak? Bagaimana jika itu terlalu rumit untuk dibuat hanya dengan wadah animasi.

Sekarang, para penulis tidak menggunakan contoh yang benar-benar nyata, yang dapat ditunjukkan baik atau buruk, saya tidak memiliki pendapat tentang itu, saya hanya mengomentari kecenderungan di utas ini untuk memunculkan perbaikan masalah yang tidak sepenuhnya menyelesaikan masalah yang dihadapi. Ini tampaknya menjadi sumber utama kebingungan antara pendukung hook dan lawan, karena masing-masing tampaknya berbicara melewati yang lain sampai batas tertentu, jadi saya mendukung proposal Hixie untuk membuat beberapa aplikasi nyata sehingga lawan tidak dapat mengatakan bahwa contoh "nyata" adalah tidak ditampilkan dan pendukung tidak dapat mengatakan bahwa seseorang hanya harus membayangkan skenario dunia nyata.

Saya pikir saya katakan adalah konyol untuk memiliki kelas 100 baris, di mana setengahnya adalah boilerplate. Yang persis apa yang saya gambarkan di sini. Inti, betapapun besar, tidak boleh dikaburkan oleh sekelompok kebisingan, yang tentu saja terjadi saat menggunakan banyak pembangun.

Dan alasannya adalah kemampuan pemindaian, keterbacaan, dan pemeliharaan di seluruh basis kode yang besar. Ini bukan penulisan baris, meskipun menulis di builder adalah pecundang produktivitas karena kecenderungan untuk masuk ke kurung kurawal neraka.

Di posting lain Anda memposting contoh dengan boilerplate yang mengaburkan "inti", tetapi sekarang Anda memberi tahu kami bahwa intinya akan menjadi ratusan baris? Jadi pada kenyataannya, pelat ketel akan sangat kecil dibandingkan dengan inti? Anda tidak dapat memiliki keduanya.
Anda terus-menerus mengubah argumen Anda.

Sekali lagi, masalah ini bukan tentang boilerplate tetapi keterbacaan dan penggunaan kembali.
Tidak masalah jika kita memiliki 100 baris.
Yang penting adalah seberapa dapat dibaca/dipelihara/dapat digunakan kembali baris-baris ini.

Bahkan jika argumennya tentang boilerplate, mengapa saya, pengguna, mentolerir boilerplate seperti itu, dengan cara yang cukup setara untuk mengekspresikan hal yang sama? Pemrograman adalah tentang membuat abstraksi dan mengotomatisasi tenaga kerja, saya tidak benar-benar melihat gunanya mengulangi hal yang sama berulang-ulang di berbagai kelas dan file.

Anda memerlukan contoh nyata menggunakan animasi yang kompleks?
https://github.com/gskinnerTeam/flutter_vignettes

Tentunya, Anda tidak dapat mengharapkan saya untuk menggali seluruh proyek Anda. File mana yang harus saya lihat?

Menampilkan beberapa animasi kompleks yang arbitrer tidak akan menghasilkan apa-apa selain mengaburkan contoh.

Sebaliknya. Menampilkan beberapa animasi kompleks sewenang-wenang yang tidak dapat diselesaikan oleh solusi yang ada akan menjadi contohnya, dan itulah yang terus ditanyakan Hixie, saya percaya.

Cukup pindai gif dan mulailah membayangkan bagaimana Anda bisa membuat beberapa hal itu. Repo itu sebenarnya adalah 17 aplikasi mandiri. Anda juga tidak dapat mengharapkan saya untuk menulis beberapa animasi sewenang-wenang hanya untuk membuktikan kepada Anda bahwa animasi yang kompleks bisa ada. Saya telah membangunnya selama 20 tahun mulai dari Flash, masing-masing berbeda dari yang terakhir. Dan itu tidak khusus untuk Animasi, mereka hanyalah API paling sederhana yang paling dikenal untuk menggambarkan poin yang lebih besar.

Seperti Anda tahu bagaimana, ketika Anda menggunakan animator, ada seperti 6 hal yang perlu Anda lakukan setiap saat, tetapi juga membutuhkan kait siklus hidup?? Ok, jadi sekarang perpanjang ke APA SAJA yang memiliki 6 langkah yang harus Anda lakukan setiap saat... Dan Anda perlu menggunakannya di 2 tempat. Atau Anda perlu menggunakan 3 dari mereka sekaligus. Ini jelas merupakan masalah di wajahnya, saya tidak tahu apa lagi yang bisa saya tambahkan untuk menjelaskannya.

Pemrograman adalah tentang membuat abstraksi dan mengotomatisasi tenaga kerja, saya tidak benar-benar melihat gunanya mengulangi hal yang sama berulang-ulang di berbagai kelas dan file.

__Semua__? Jadi kinerja, rawatan tidak relevan?

Ada saatnya ketika "mengotomatiskan" pekerjaan dan "melakukan" pekerjaan adalah sama.

Teman-teman, tidak apa-apa jika Anda tidak punya waktu atau keinginan untuk membuat contoh nyata, tetapi tolong, jika Anda tidak tertarik membuat contoh untuk menjelaskan masalah, Anda juga tidak boleh mengharapkan orang untuk kemudian merasa terdorong untuk memecahkan masalah ( yang lebih banyak pekerjaan daripada membuat contoh untuk menunjukkan masalah). Tidak seorang pun di sini diharuskan melakukan apa pun untuk siapa pun, ini adalah proyek sumber terbuka di mana kita semua berusaha untuk saling membantu.

@TimWhiting maukah Anda meletakkan file lisensi di repo https://github.com/TimWhiting/local_widget_state_approaches Anda? Beberapa orang tidak dapat berkontribusi tanpa lisensi yang berlaku (BSD, MIT, atau yang serupa idealnya).

mengapa saya, pengguna, mentolerir boilerplate seperti itu dalam hal apa pun, dengan cara yang

Ya, pemeliharaan dan kinerja tentu saja penting. Maksud saya ketika ada solusi yang setara, kita harus memilih yang memiliki lebih sedikit boilerplate, lebih mudah dibaca, lebih dapat digunakan kembali, dan seterusnya. Itu bukan untuk mengatakan bahwa kait adalah jawabannya, karena saya belum mengukur kinerjanya misalnya, tetapi mereka lebih dapat dipertahankan dalam pengalaman saya. Saya masih tidak yakin tentang argumen Anda tentang bagaimana hal itu memengaruhi pekerjaan Anda jika konstruksi seperti kait dimasukkan ke dalam inti Flutter.

Cukup pindai gif dan mulailah membayangkan bagaimana Anda bisa membuat beberapa hal itu.

Saya memindai gif. Saya tidak akan menggunakan widget pembangun.
Banyak animasi yang sangat kompleks sehingga jika saya tahu Anda menerapkannya menggunakan pembangun tingkat yang lebih tinggi, saya mungkin tidak akan menggunakan paket Anda.

Bagaimanapun, diskusi ini tampaknya menjadi tidak terkendali dengan perselisihan yang lebih pribadi. Kita harus fokus pada tugas utama yang ada. Saya tidak yakin bagaimana pendukung hook dapat menunjukkan contoh yang lebih kecil jika lawan akan, seperti yang saya katakan sebelumnya, menemukan perbaikan yang tidak benar-benar menyelesaikan masalah yang diajukan. Saya pikir kita harus berkontribusi ke repositori @TimWhiting untuk saat ini.

Menampilkan beberapa animasi kompleks sewenang-wenang yang tidak dapat diselesaikan oleh solusi yang ada akan menjadi contohnya, dan itulah yang terus ditanyakan Hixie, saya yakin.

Menampilkan contoh dari sesuatu yang tidak mungkin saat ini berada di luar cakupan masalah ini.
Masalah ini adalah tentang meningkatkan sintaks dari apa yang sudah layak, bukan membuka blokir beberapa hal yang tidak mungkin hari ini.

Permintaan apa pun untuk menyediakan sesuatu yang tidak mungkin hari ini adalah di luar topik.

@TimWhiting maukah Anda meletakkan file lisensi di repo https://github.com/TimWhiting/local_widget_state_approaches Anda? Beberapa orang tidak dapat berkontribusi tanpa lisensi yang berlaku (BSD, MIT, atau yang serupa idealnya).

Selesai. Maaf, saya tidak punya banyak waktu untuk mengerjakan contoh-contohnya, tapi mungkin saya akan membahasnya minggu ini.

Menampilkan beberapa animasi kompleks sewenang-wenang yang tidak dapat diselesaikan oleh solusi yang ada akan menjadi contohnya, dan itulah yang terus ditanyakan Hixie, saya yakin.

Menampilkan contoh dari sesuatu yang tidak mungkin saat ini berada di luar cakupan masalah ini.
Masalah ini adalah tentang meningkatkan sintaks dari apa yang sudah layak, bukan membuka blokir beberapa hal yang tidak mungkin hari ini.

Permintaan apa pun untuk menyediakan sesuatu yang tidak mungkin hari ini adalah di luar topik.

Biarkan saya ulangi apa yang saya katakan.

Menampilkan beberapa kasus penggunaan kompleks sewenang-wenang yang sulit untuk ditulis dan yang dapat ditingkatkan secara signifikan tanpa memengaruhi kinerja akan menjadi contohnya, dan itulah yang terus ditanyakan Hixie, saya yakin.

Saya memahami dorongan untuk lebih sedikit boilerplate, lebih dapat digunakan kembali, lebih banyak keajaiban. Saya suka harus menulis lebih sedikit kode juga, dan bahasa/kerangka kerja yang melakukan lebih banyak pekerjaan cukup menggugah selera.
Sampai sekarang tidak ada contoh/solusi kombo yang disajikan di sini akan secara drastis meningkatkan kode. Artinya, jika kita peduli lebih dari sekadar berapa banyak baris kode yang harus kita tulis.

Teman-teman, tidak apa-apa jika Anda tidak punya waktu atau keinginan untuk membuat contoh nyata, tetapi tolong, jika Anda tidak tertarik membuat contoh untuk menjelaskan masalah, Anda juga tidak boleh mengharapkan orang untuk kemudian merasa terdorong untuk memecahkan masalah ( yang lebih banyak pekerjaan daripada membuat contoh untuk menunjukkan masalah). Tidak seorang pun di sini diharuskan melakukan apa pun untuk siapa pun, ini adalah proyek sumber terbuka di mana kita semua berusaha untuk saling membantu.

@TimWhiting maukah Anda meletakkan file lisensi di repo https://github.com/TimWhiting/local_widget_state_approaches Anda? Beberapa orang tidak dapat berkontribusi tanpa lisensi yang berlaku (BSD, MIT, atau yang serupa idealnya).

Saya menghabiskan sekitar 6 jam membuat berbagai contoh dan cuplikan kode ini. Tapi saya melihat benar-benar tidak ada gunanya memberikan contoh nyata dari animasi kompleks hanya untuk membuktikan bahwa mereka bisa ada.

Permintaan pada dasarnya adalah untuk mengubah ini menjadi sesuatu yang tidak dapat ditangani oleh AnimatedContainer:

Container(margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30), color: Colors.red.withOpacity(value1));

Ini sangat sepele sampai-sampai hampir sengaja mengabaikan masalah ini. Apakah sangat sulit untuk membayangkan bahwa saya mungkin memiliki beberapa tombol yang berdenyut, beberapa partikel bergerak, mungkin beberapa bidang teks yang memudar saat penskalaan, atau beberapa kartu yang terbalik? Mungkin saya membuat soundbar dengan 15 bar independen, mungkin saya menggeser menu masuk tetapi juga membutuhkan kemampuan untuk menggeser item individual kembali. Dan terus, dan terus, dan terus. Dan ini hanya untuk Animasi. Ini berlaku untuk kasus penggunaan apa pun yang memberatkan dalam konteks widget.

Saya pikir saya memberikan contoh kanonik yang sangat baik tentang masalah dengan kedua pembangun, dan penggunaan kembali negara bagian Vanilla:
https://github.com/flutter/flutter/issues/51752#issuecomment -671566814
https://github.com/flutter/flutter/issues/51752#issuecomment -671489384

Anda hanya perlu membayangkan banyak dari contoh ini (pilih racun Anda), tersebar jauh dan luas di seluruh proyek dengan 1000+ file kelas, dan Anda mendapatkan gambaran sempurna tentang masalah keterbacaan dan pemeliharaan yang kami coba hindari.

Apakah contoh gambar yang disediakan oleh @esDotDev , yang menunjukkan bagaimana pembuatan kode membuat kode lebih sulit dibaca, tidak cukup untuk Anda, @Rudiksz ? Apa yang kurang dari mereka? Saya kira tidak ada metrik kinerja di sana tetapi @rrousselGit yakin mereka tidak kurang berkinerja daripada pembangun.

@esDotDev Saya pikir intinya adalah memiliki contoh kanonik tunggal dari mana semua solusi manajemen siklus hidup dapat dibandingkan (bukan hanya kait tetapi juga yang lain di masa depan). Ini prinsip yang sama dengan TodoMVC, Anda tidak perlu menunjuk ke berbagai implementasi lain di React, Vue, Svelte, dll yang menunjukkan perbedaan di antara mereka, Anda ingin semuanya menerapkan aplikasi yang sama, _then_ Anda dapat membandingkan.

Itu masuk akal bagi saya, tetapi saya tidak mengerti mengapa itu harus lebih besar dari satu halaman.

Mengelola beberapa animasi adalah contoh sempurna dari sesuatu yang umum, membutuhkan banyak boilerplate, rawan kesalahan, dan tidak memiliki solusi yang baik saat ini. Jika itu tidak penting, jika orang akan mengatakan bahwa mereka bahkan tidak mengerti bagaimana animasi bisa menjadi rumit, maka jelas kasus penggunaan apa pun akan dirobohkan untuk konteks kasus penggunaan, dan bukan masalah arsitektur yang kami sedang mencoba untuk menggambarkan.

Tentu, repositori dari @TimWhiting tidak memiliki aplikasi yang lengkap, ia memiliki halaman tunggal sebagai contoh seperti yang Anda katakan, jika Anda dapat membuat contoh animasi kanonik untuk repositori itu dari mana orang lain dapat mengimplementasikan solusi mereka, itu akan berhasil.

Saya juga tidak berpikir kita membutuhkan aplikasi besar atau apa pun, tetapi harus ada kompleksitas yang cukup mirip dengan TodoMVC. Pada dasarnya itu harus cukup sehingga lawan Anda tidak bisa mengatakan "baik saya bisa melakukan ini dengan lebih baik dengan cara ini".

@Hixie Permintaan aplikasi nyata untuk membandingkan pendekatan cacat.

Ada dua kekurangan:

  • Kami belum menyepakati masalah, seperti yang Anda katakan sendiri, Anda tidak memahaminya
  • Kami tidak dapat menerapkan contoh dalam kondisi produksi nyata, karena kami akan memiliki bagian yang hilang.

Misalnya, kami tidak dapat menulis aplikasi menggunakan:

final snapshot = keyword StreamBuilder();

karena ini tidak dilaksanakan.

Kami juga tidak dapat menilai kinerja, karena ini membandingkan POC vs kode produksi.

Kami tidak dapat mengevaluasi apakah sesuatu seperti "kait tidak dapat dipanggil secara kondisional" juga rawan kesalahan, karena tidak ada integrasi kompiler untuk menunjukkan kesalahan ketika ada penyalahgunaan.

Menilai kinerja desain, mengevaluasi kegunaan API, mengimplementasikan hal-hal sebelum kami mengimplementasikan ... itu semua adalah bagian dari desain API. Selamat datang di pekerjaan saya. :-) (Flutter trivia: tahukah Anda bahwa beberapa ribu baris pertama RenderObject dan RenderBox dkk diimplementasikan sebelum kita membuat dart:ui?)

Itu tidak mengubah fakta bahwa Anda meminta hal yang mustahil.

Beberapa proposal yang dibuat di sini adalah bagian dari bahasa atau penganalisis. Mustahil bagi masyarakat untuk menerapkannya.

Saya tidak begitu yakin, kerangka kerja dan bahasa lain melakukan desain API sepanjang waktu, saya rasa itu tidak terlalu berbeda di sini, atau bahwa Flutter memiliki beberapa perbedaan atau kesulitan yang luar biasa untuk desain API daripada bahasa lain. Seperti, mereka melakukannya tanpa dukungan kompiler atau penganalisis, mereka hanya bukti konsep.

Saya telah mengumpulkan contoh skenario animasi 'kompleks' yang memanfaatkan 3 animasi dengan baik dan cukup sarat dengan boilerplate dan cruft.

Penting untuk dicatat bahwa saya bisa saja melakukan animasi apa pun yang membutuhkan snap keras kembali ke posisi awal (menghilangkan semua widget implisit), atau rotasi pada sumbu z, atau skala pada sumbu tunggal, atau kasus penggunaan lain yang tidak tercakup oleh IW. Saya khawatir itu mungkin tidak dianggap serius (meskipun desainer saya akan menyerahkan barang-barang ini kepada saya sepanjang hari) jadi saya membangun sesuatu yang lebih 'dunia nyata'.

Jadi di sini adalah perancah sederhana, memiliki 3 panel yang dapat dibuka dan ditutup. Ini menggunakan 3 animator dengan status diskrit. Dalam hal ini saya tidak benar-benar membutuhkan kontrol penuh dari AnimatorController, TweenAnimationBuilder akan baik-baik saja, tetapi sarang yang dihasilkan di pohon saya akan sangat tidak diinginkan. Saya tidak dapat membuat sarang TAB di bawah pohon, karena panel memiliki ketergantungan pada nilai satu sama lain. AnimatedContainer tidak ada pilihan di sini karena setiap panel perlu meluncur dari layar, mereka tidak "memukul".
https://i.imgur.com/BW6M3uM.gif
image

class _SlidingPanelViewState extends State<SlidingPanelView> with TickerProviderStateMixin {
  AnimationController leftMenuAnim;
  AnimationController btmMenuAnim;
  AnimationController rightMenuAnim;

  <strong i="12">@override</strong>
  void initState() {
    // Here I have to pass vsync to AnimationController, so I have to include a SingleTickerProviderMixin and somewhat magically pass 'this' as vsync.
    leftMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    btmMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    rightMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    // Here I have to call forward 3 times, cause there's no way to automate this common setup behavior
    leftMenuAnim.forward();
    btmMenuAnim.forward();
    rightMenuAnim.forward();
    // Here I have to manually bind to build, cause there is encapsulate this common setup behavior
    leftMenuAnim.addListener(() => setState(() {}));
    btmMenuAnim.addListener(() => setState(() {}));
    rightMenuAnim.addListener(() => setState(() {}));
    super.initState();
  }

  // Following 2 fxn are a blind spot as far as compiler is concerned.
  // Things may, or may not be implemented correctly, no warnings, no errors.
  <strong i="13">@override</strong>
  void dispose() {
    btmMenuAnim.dispose();
    leftMenuAnim.dispose();
    rightMenuAnim.dispose();
    super.dispose();
  }

  <strong i="14">@override</strong>
  void didUpdateWidget(SlidingPanelView oldWidget) {
    if (leftMenuAnim.duration != widget.slideDuration) {
      leftMenuAnim.duration = widget.slideDuration;
      btmMenuAnim.duration = widget.slideDuration;
      rightMenuAnim.duration = widget.slideDuration;
    }
    super.didUpdateWidget(oldWidget);
  }

  // End error-prone blind spot without a single line of unique code
  // ~50 lines in we can start to see some unique code

  void _toggleMenu(AnimationController anim) {
    bool isOpen = anim.status == AnimationStatus.forward || anim.value == 1;
    isOpen ? anim.reverse() : anim.forward();
  }

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    double leftPanelSize = 320;
    double leftPanelPos = -leftPanelSize * (1 - leftMenuAnim.value);
    double rightPanelSize = 230;
    double rightPanelPos = -rightPanelSize * (1 - rightMenuAnim.value);
    double bottomPanelSize = 80;
    double bottomPanelPos = -bottomPanelSize * (1 - btmMenuAnim.value);
    return Stack(
      children: [
        //Bg
        Container(color: Colors.white),
        //Content Panel
        Positioned(
          top: 0,
          left: leftPanelPos + leftPanelSize,
          bottom: bottomPanelPos + bottomPanelSize,
          right: rightPanelPos + rightPanelSize,
          child: ChannelInfoView(),
        ),
        //Left Panel
        Positioned(
          top: 0,
          left: leftPanelPos,
          bottom: bottomPanelPos + bottomPanelSize,
          width: leftPanelSize,
          child: ChannelMenu(),
        ),
        //Bottom Panel
        Positioned(
          left: 0,
          right: 0,
          bottom: bottomPanelPos,
          height: bottomPanelSize,
          child: NotificationsBar(),
        ),
        //Right Panel
        Positioned(
          top: 0,
          right: rightPanelPos,
          bottom: bottomPanelPos + bottomPanelSize,
          width: rightPanelSize,
          child: SettingsMenu(),
        ),
        // Buttons
        Row(
          children: [
            Button("left", ()=>_toggleMenu(leftMenuAnim)),
            Button("btm", ()=>_toggleMenu(btmMenuAnim)),
            Button("right", ()=>_toggleMenu(rightMenuAnim)),
          ],
        )
      ],
    );
  }
}

//Demo helpers
Widget Button(String lbl, VoidCallback action) => FlatButton(child: Text(lbl), onPressed: action, color: Colors.grey);
Widget ChannelInfoView() => _buildPanel(Colors.red);
Widget ChannelMenu() => _buildPanel(Colors.pink);
Widget SettingsMenu() => _buildPanel(Colors.blue);
Widget NotificationsBar() => _buildPanel(Colors.grey);
Widget _buildPanel(Color c) => Container(color: c, child: Container(color: Colors.white.withOpacity(.5)), padding: EdgeInsets.all(10));

Jadi, dari 100 baris atau lebih di badan itu, kira-kira 40% atau lebih adalah pelat ketel murni. 15 baris khususnya, di mana ada yang hilang atau salah ketik, dapat menyebabkan bug yang sulit dikenali.

Jika kita menggunakan sesuatu seperti StatefulProperty, itu akan mengurangi boilerplate menjadi 15% atau lebih (menghemat sekitar 25 baris). Secara kritis, ini akan sepenuhnya menyelesaikan masalah bug licik dan duplikat logika bisnis, tetapi masih sedikit bertele-tele terutama karena memerlukan StatefulWidget, yang merupakan hit 10 baris langsung dari atas.

Jika kami menggunakan sesuatu seperti 'kata kunci', kami mengurangi garis boilerplate menjadi 0%. Seluruh fokus kelas dapat berada pada logika bisnis (unik) dan elemen pohon visual. Kami membuat penggunaan StatefulWIdgets jauh lebih jarang secara umum, sebagian besar tampilan menjadi 10 atau 20% lebih sedikit verbose dan lebih fokus.

Juga, perlu dicatat bahwa skenario Panel di atas adalah dunia nyata, dan di dunia nyata jelas pendekatan ini benar-benar tidak baik, jadi kami tidak menggunakannya, dan Anda tidak akan melihatnya di basis kode. Kami juga tidak akan menggunakan pembuat bersarang, karena itu terlihat kotor sehingga Anda juga tidak akan melihatnya.

Kami membangun widget SlidingPanel khusus, yang mengambil properti IsOpen, dan membuka dan menutup sendiri. Ini umumnya solusi di setiap kasus penggunaan ini di mana Anda memerlukan beberapa perilaku tertentu, Anda memindahkan logika stateful ke dalam beberapa widget ultra-spesifik, dan Anda menggunakannya. Anda pada dasarnya menulis ImplicitlyAnimatedWidget Anda sendiri.

Ini umumnya bekerja dengan baik, tetapi ini masih waktu dan usaha, dan secara harfiah SATU-SATUNYA alasan itu ada karena menggunakan Animasi sangat sulit (yang ada menyebabkan penggunaan kembali komponen stateful secara umum sangat sulit). Di Unity atau AIR misalnya, saya tidak akan membuat kelas khusus hanya untuk memindahkan panel pada satu sumbu, itu hanya akan menjadi satu baris kode untuk membuka atau menutup, tidak akan ada widget khusus untuk dilakukan. Di Flutter kita harus membuat widget khusus, karena itu benar-benar satu-satunya cara yang masuk akal untuk merangkum bootstrap dan pembongkaran AnimatorController (kecuali kita ingin bersarang, bersarang, bersarang dengan TAB)

Poin utama saya di sini adalah hal semacam ini adalah mengapa contoh dunia nyata sangat sulit ditemukan. Sebagai pengembang, kami tidak dapat membiarkan hal-hal ini terlalu banyak ada di basis kode kami, jadi kami mengatasinya dengan solusi yang kurang ideal namun efektif. Kemudian ketika Anda melihat basis kode, Anda hanya melihat solusi ini berlaku dan semuanya tampak baik-baik saja, tetapi mungkin bukan itu yang ingin dibuat oleh tim, mungkin butuh 20% lebih lama untuk sampai ke sana, mungkin total sulit untuk di-debug, tidak ada yang terlihat jelas saat melirik kode.

Untuk kelengkapan, berikut adalah use case yang sama, dibuat dengan builder. Jumlah baris sangat berkurang, tidak ada peluang untuk bug, tidak perlu mempelajari konsep asing RE TickerProviderMixin...tetapi sarang itu adalah kesedihan, dan cara berbagai variabel ditaburkan di seluruh pohon (nilai akhir dinamis, nilai1, nilai2 dll ) membuat logika bisnis jauh lebih sulit untuk dibaca daripada yang seharusnya.

``` anak panah
kelas _SlidingPanelViewState memperluas Status{
bool isLeftMenuOpen = benar;
bool isRightMenuOpen = benar;
bool isBtmMenuOpen = true;

@mengesampingkan
Pembuatan widget (konteks BuildContext) {
kembalikan TweenAnimationBuilder(
tween: Tween(mulai: 0, akhir: isLeftMenuOpen ? 1: 0),
durasi: widget.slideDuration,
pembangun: (_, leftAnimValue, __) {
kembalikan TweenAnimationBuilder(
tween: Tween(mulai: 0, akhir: isRightMenuOpen ? 1: 0),
durasi: widget.slideDuration,
pembangun: (_, rightAnimValue, __) {
kembalikan TweenAnimationBuilder(
tween: Tween(mulai: 0, akhir: isBtmMenuOpen ? 1: 0),
durasi: widget.slideDuration,
pembangun: (_, btmAnimValue, __) {
double leftPanelSize = 320;
double leftPanelPos = -leftPanelSize * (1 - leftAnimValue);
double rightPanelSize = 230;
double rightPanelPos = -rightPanelSize * (1 - rightAnimValue);
double bottomPanelSize = 80;
double bottomPanelPos = -bottomPanelSize * (1 - btmAnimValue);
kembali Tumpukan (
anak-anak: [
//Bg
Wadah (warna: Colors.white),
//Area konten utama
Diposisikan (
atas: 0,
kiri: leftPanelPos + leftPanelSize,
bawah: bottomPanelPos + bottomPanelSize,
kanan: rightPanelPos + rightPanelSize,
anak: ChannelInfoView(),
),
//Panel Kiri
Diposisikan (
atas: 0,
kiri: leftPanelPos,
bawah: bottomPanelPos + bottomPanelSize,
lebar: leftPanelSize,
anak: ChannelMenu(),
),
//Panel bawah
Diposisikan (
kiri: 0,
kanan: 0,
bawah: bottomPanelPos,
tinggi: ukuranPanel bawah,
anak: NotificationsBar(),
),
//Panel Kanan
Diposisikan (
atas: 0,
kanan: rightPanelPos,
bawah: bottomPanelPos + bottomPanelSize,
lebar: rightPanelSize,
anak: SettingsMenu(),
),
// Tombol
Baris(
anak-anak: [
Tombol("kiri", () => setState(() => isLeftMenuOpen = !isLeftMenuOpen)),
Tombol("btm", () => setState(() => isBtmMenuOpen = !isBtmMenuOpen)),
Tombol("kanan", () => setState(() => isRightMenuOpen = !isRightMenuOpen)),
],
)
],
);
},
);
},
);
},
);
}
}

Yang terakhir itu menarik... Awalnya saya akan menyarankan bahwa pembangun harus berada di sekitar widget Diposisikan daripada Stack (dan saya masih akan menyarankan itu untuk panel kiri dan kanan) tetapi kemudian saya menyadari bahwa yang paling bawah mempengaruhi ketiganya, dan saya menyadari bahwa pembuat hanya memberi Anda satu argumen child sebenarnya tidak cukup, karena Anda benar-benar ingin mempertahankan konstanta Container dan Row membangun. Saya kira Anda bisa membuatnya di atas pembuat pertama.

Poin utama saya di sini adalah hal semacam ini adalah mengapa contoh dunia nyata sangat sulit ditemukan. Sebagai pengembang, kami tidak dapat membiarkan hal-hal ini terlalu banyak ada di basis kode kami, jadi kami mengatasinya dengan solusi yang kurang ideal namun efektif. Kemudian ketika Anda melihat basis kode, Anda hanya melihat solusi ini berlaku dan semuanya tampak baik-baik saja, tetapi mungkin bukan itu yang ingin dibuat oleh tim, mungkin butuh 20% lebih lama untuk sampai ke sana, mungkin total sulit untuk di-debug, tidak ada yang terlihat jelas saat melirik kode.

Sebagai catatan, ini bukan kecelakaan. Ini sangat banyak dengan desain. Memiliki widget ini menjadi widget mereka sendiri meningkatkan kinerja. Kami sangat menginginkan ini menjadi cara orang menggunakan Flutter. Inilah yang saya maksud di atas ketika saya menyebutkan "sebagian besar filosofi desain Flutter adalah memandu orang menuju pilihan yang tepat".

Saya awalnya akan menyarankan bahwa pembangun harus berada di sekitar widget Diposisikan daripada Stack (dan saya masih akan menyarankan itu untuk panel kiri dan kanan)

Saya benar-benar menghilangkannya demi ringkas, tetapi biasanya saya mungkin menginginkan wadah Konten di sini, dan itu akan menggunakan ukuran dari ketiga menu untuk menentukan posisinya sendiri. Artinya ketiganya harus berada di puncak pohon, dan jika ada yang dibangun kembali, saya perlu seluruh tampilan untuk dibangun kembali, tidak ada jalan keluarnya. Ini pada dasarnya adalah perancah gaya desktop klasik Anda.

Tentu saja kita bisa mulai merobek pohon, selalu menjadi pilihan, kita sering menggunakannya untuk widget yang lebih besar, tapi saya belum pernah melihat bahwa benar-benar meningkatkan grokability secara sekilas, ada langkah kognitif ekstra yang perlu dilakukan pembaca pada saat itu titik. Saat Anda memecahkan pohon itu, saya tiba-tiba mengikuti jejak tugas variabel untuk mencari tahu apa yang dilewati di sini dan bagaimana semuanya cocok bersama menjadi tersumbat. Menyajikan pohon sebagai pohon yang dapat dicerna selalu lebih mudah untuk dipikirkan dari pengalaman saya.

Mungkin kasus penggunaan yang baik untuk RenderObject khusus.

Atau 3 AnimatorObjects yang mudah dikelola yang dapat menghubungkan diri mereka sendiri ke dalam siklus hidup widget :D

Memiliki widget ini menjadi widget mereka sendiri meningkatkan kinerja.

Karena kita mendapatkan beton di sini: Dalam hal ini karena ini adalah perancah, setiap sub-tampilan sudah widget itu sendiri, orang ini bertanggung jawab untuk meletakkan dan menerjemahkan anak-anak. Anaknya adalah BottomMenu(), ChannelMenu(), SettingsView(), MainContent() dll

Dalam hal ini kami membungkus sekelompok widget mandiri, dengan lapisan widget mandiri lainnya, hanya untuk mengelola boilerplate untuk memindahkannya. Saya tidak percaya ini adalah kemenangan kinerja? Dalam hal ini kita didorong ke dalam apa yang _pikirkan_ kerangka kerja yang ingin kita lakukan, dan bukan apa yang sebenarnya ingin kita lakukan, yaitu menulis tampilan yang sama-sama berkinerja dengan cara yang lebih ringkas dan koheren.

[Sunting] Saya akan memperbarui contoh untuk menambahkan konteks ini

Alasan saya menyarankan RenderObject khusus adalah karena itu akan membuat tata letak lebih baik. Saya setuju bahwa aspek animasi akan berada dalam widget stateful, itu hanya akan menurunkan tiga 0..1 ganda (nilai1, nilai2, nilai3) ke objek render alih-alih memiliki matematika Stack. Tapi itu kebanyakan tontonan untuk diskusi ini; pada titik di mana Anda melakukan ini, Anda masih ingin melakukan penyederhanaan yang diberikan oleh Hooks atau sesuatu yang serupa di widget stateful itu.

Pada catatan yang lebih relevan, saya memiliki celah dalam membuat demo untuk proyek @TimWhiting : https://github.com/TimWhiting/local_widget_state_approaches/pull/1
Saya ingin tahu seperti apa versi Hooks. Saya tidak yakin saya dapat melihat cara yang baik untuk membuatnya lebih sederhana, terutama mempertahankan karakteristik kinerja (atau meningkatkannya; ada komentar di sana yang menunjukkan tempat yang saat ini kurang optimal).

(Saya juga sangat ingin tahu apakah ini adalah jenis hal di mana jika kami menemukan cara untuk menyederhanakannya, kami akan menyelesaikannya di sini, atau jika ada hal-hal penting yang perlu kami selesaikan sebelum kami selesai.)

Apakah hal-hal restorasi penting untuk contoh ini? Saya mengalami kesulitan mengikuti karena itu, dan tidak benar-benar tahu apa yang dilakukan RestorationMixin. Saya berasumsi itu ... akan mengambil sedikit untuk memahami ini untuk saya. Saya yakin Remi akan membuat versi hooks dalam 4 detik :)

Menggunakan API pemulihan menggunakan HookWidget daripada StatefulHookWidget tidak didukung saat ini.

Idealnya kita harus bisa berubah

final value = useState(42);

ke dalam:

final value = useRestorableInt(42);

Tetapi perlu beberapa pemikiran, karena API restorasi saat ini tidak benar-benar dirancang dengan mempertimbangkan kait.

Sebagai catatan tambahan, React hooks hadir dengan fitur "kunci", biasanya digunakan seperti:

int userId;

Future<User> user = useMemo(() => fetchUser(id), [id]);

di mana kode ini berarti "cache hasil panggilan balik, dan evaluasi kembali panggilan balik setiap kali ada perubahan di dalam array"

Flutter_hooks telah membuat implementasi ulang 1to1 dari ini (karena ini hanya sebuah port), tetapi saya tidak berpikir itu yang ingin kami lakukan untuk kode yang dioptimalkan Flutter.

Kami mungkin ingin:

int userId;

Future<User> user = useMemo1(id, (id) => fetchUser(id));

yang akan melakukan hal yang sama, tetapi hilangkan tekanan memori dengan menghindari alokasi daftar dan menggunakan fungsi tear-off

Ini tidak kritis pada tahap ini, tetapi perlu disebutkan jika kita berencana untuk menggunakan flutter_hooks sebagai contoh.

@Hixie Saya mem-porting contoh animasi Anda ke kait

Itu adalah contoh yang menarik, pujian untuk memikirkannya!
Ini adalah contoh yang baik dalam hal itu, secara default, penerapan "aktif" dan "durasi" ada di mana-mana (dan saling bergantung pada saat itu).
Sampai-sampai ada banyak panggilan "jika (aktif)" / "controller.repeat"

Sedangkan dengan kait, semua logika ditangani secara deklaratif dan terkonsentrasi di satu tempat, tanpa duplikat.

Contoh ini juga menunjukkan bagaimana kait dapat digunakan untuk menyimpan objek dengan mudah — yang terlalu sering memperbaiki masalah pembangunan kembali ExpensiveWidget.
Kami mendapatkan manfaat dari konstruktor const, tetapi bekerja dengan parameter dinamis.

Kami juga mendapatkan hot-reload yang lebih baik. Kita dapat mengubah durasi Timer.periodic untuk warna latar belakang dan langsung melihat perubahan yang terjadi.

@rrousselGit apakah Anda memiliki tautan? Saya tidak melihat sesuatu yang baru di repositori @TimWhiting .

Menggunakan API pemulihan menggunakan HookWidget daripada StatefulHookWidget tidak didukung saat ini.

Solusi apa pun yang kami temukan, kami perlu memastikan bahwa solusi tersebut tidak perlu mengetahui setiap mixin terakhir. Jika seseorang ingin menggunakan solusi kami dalam kombinasi dengan beberapa paket lain yang memperkenalkan mixin seperti TickerProviderStateMixin atau RestorationMixin, mereka seharusnya dapat melakukannya.

https://github.com/TimWhiting/local_widget_state_approaches/pull/3

Setuju, tapi saya tidak khawatir tentang itu. useAnimationController tidak mengharuskan pengguna untuk peduli dengan SingleTickerProvider misalnya.

AutomaritKeepAlive bisa mendapatkan keuntungan dari perlakuan yang sama.
Salah satu hal yang saya pikirkan adalah memiliki kait "useKeepAlive(bool)"

Itu menghindari mixin dan "super.build(context)" (yang terakhir cukup membingungkan)

Hal menarik lainnya adalah perubahan yang diperlukan selama refactoring.

Misalnya, kita dapat membandingkan perbedaan antara perubahan yang diperlukan untuk mengimplementasikan TickerMode untuk pendekatan mentah vs kait:

Beberapa hal lain tercampur dalam diff, tetapi kita dapat melihat darinya bahwa:

  • StatefulWidget diperlukan untuk memindahkan logika ke siklus hidup yang sama sekali berbeda
  • Perubahan kait adalah murni aditif. Baris yang ada tidak diedit/dipindahkan.

Imo ini sangat penting dan kunci kemenangan dari gaya self-contained state object ini. Memiliki segala sesuatu berdasarkan konteks bersarang di pohon pada dasarnya lebih sulit dan lebih berbelit-belit untuk refactor dan berubah, yang selama proyek memiliki efek yang tidak terlihat tetapi pasti pada kualitas akhir basis kode.

Sepakat!
Itu juga membuat ulasan kode lebih mudah dibaca:

final value = useSomething();
+ final value2 = useSomethingElse();

return Container(
  color: value.color,
-  child: Text('${value.name}'),
+  child: Text('${value.name} $value2'),
);

vs:

return SomethingBuilder(
  builder: (context, value) {
-    return Container(
-      color: value.color,
-      child: Text('$value'),
+    return SomethingElseBuilder(
+      builder: (context, value2) {
+        return Container(
+          color: value.color,
+          child: Text('${value.name} $value2'),
+        );
+      }
    );
  },
);

Tidak jelas di perbedaan kedua bahwa Container tidak berubah dan hanya Text berubah

@rrousselGit Apakah menurut Anda masuk akal untuk mencoba dan melakukan versi yang mewakili tampilannya dengan dukungan kata kunci? Apakah secara substansial mirip dengan kait hanya dengan kata kunci 'penggunaan' khusus, atau apakah itu menjadi lebih mudah untuk diikuti? Sulit untuk membandingkan pendekatan hook secara merata karena memiliki begitu banyak konsep asing seperti useEffect, useMemo dll yang menurut saya membuatnya terlihat lebih ajaib daripada itu?

Hal lain yang perlu diperhatikan, adalah di mana semua ini benar-benar akan berhasil, adalah jika Anda memiliki banyak widget, yang semuanya perlu berbagi logika 'langkah-warna' ini, tetapi menggunakan warna yang dihasilkan dengan cara yang sama sekali berbeda. Dengan pendekatan gaya kait, kami hanya menggabungkan logika apa pun yang masuk akal untuk digunakan kembali, dan kami cukup menggunakannya. Tidak ada sudut arsitektur yang kami paksa, itu benar-benar agnostik dan fleksibel.

Dalam pendekatan Stateful, kita dipaksa untuk

  • salin dan tempel logika (sangat tidak dapat dipertahankan)
  • menggunakan pembangun (tidak super mudah dibaca, terutama saat menggunakan bersarang)
  • pencampuran (tidak menulis dengan baik, sangat mudah bagi mixin yang berbeda untuk berkonflik dalam status bersama mereka)

Hal utama yang saya pikir adalah bahwa pada yang terakhir, Anda segera memiliki masalah arsitektur, di mana saya harus meletakkan ini di pohon saya, bagaimana cara terbaik untuk merangkum ini, haruskah itu pembangun, atau mungkin widget khusus? Dengan yang pertama, satu-satunya keputusan adalah file mana yang akan menyimpan potongan logika yang digunakan kembali ini, tidak ada dampak pada pohon Anda sama sekali. Ini sangat bagus secara arsitektur ketika Anda ingin menggunakan beberapa enkapsulasi logika ini bersama-sama, memindahkannya ke atas dan ke bawah dalam hierarki Anda atau pindah ke widget saudara, dll.

Saya, sama sekali bukan, pengembang ahli. Tapi gaya baru menggunakan kembali logika dan menulisnya di satu tempat sangat berguna.

Saya sudah lama tidak menggunakan Vue.js, tetapi mereka bahkan memiliki API sendiri (terinspirasi dari Hooks) untuk versi berikutnya, mungkin layak untuk dilihat.

Dan CMIIW, menurut saya aturan react hooks (jangan gunakan conditional), tidak berlaku dengan Composition API. Jadi, Anda tidak perlu menggunakan linter untuk menegakkan aturan.

Sekali lagi bagian motivasi sangat memperkuat OP di sini:

MOTIVASI
Kode komponen kompleks menjadi lebih sulit untuk dipikirkan seiring dengan bertambahnya fitur dari waktu ke waktu. Ini terjadi terutama ketika pengembang membaca kode yang tidak mereka tulis sendiri.
[Ada] Kurangnya mekanisme yang bersih dan bebas biaya untuk mengekstraksi dan menggunakan kembali logika di antara banyak komponen.

Kata kuncinya adalah "bersih" dan "bebas biaya". Mixin bebas biaya tetapi tidak bersih. Pembangun bersih, tetapi mereka tidak bebas biaya dalam arti keterbacaan, atau dalam arti arsitektur widget karena mereka lebih sulit untuk bergerak di sekitar pohon dan alasan dalam hal hierarki.

Saya juga berpikir ini penting untuk dicatat dalam diskusi keterbacaan: "_terjadi terutama ketika pengembang membaca kode yang tidak mereka tulis_". Tentu saja _pembuat bersarang_Anda_ mungkin mudah dibaca, Anda tahu apa yang ada di sana dan dapat dengan nyaman melewatinya, ia membaca kode orang lain, seperti yang Anda lakukan pada proyek yang lebih besar, atau kode Anda sendiri dari minggu/bulan yang lalu, ketika menjadi cukup mengganggu/sulit untuk menguraikan & memperbaiki hal-hal ini.

Beberapa bagian lain yang sangat relevan.

Mengapa hanya memiliki komponen saja tidak cukup:

Membuat ... komponen memungkinkan kita untuk mengekstrak bagian berulang dari antarmuka yang digabungkan dengan fungsinya menjadi potongan kode yang dapat digunakan kembali. Ini saja bisa membuat aplikasi kita cukup jauh dalam hal pemeliharaan dan fleksibilitas. Namun, pengalaman kolektif kami telah membuktikan bahwa ini saja mungkin tidak cukup, terutama ketika aplikasi Anda menjadi sangat besar – pikirkan beberapa ratus komponen. Ketika berhadapan dengan aplikasi besar seperti itu, berbagi dan menggunakan kembali kode menjadi sangat penting.

Tentang mengapa mengurangi fragmentasi logis dan merangkum hal-hal dengan lebih kuat adalah sebuah kemenangan:

fragmentasi inilah yang membuat sulit untuk memahami dan memelihara komponen yang kompleks. Pemisahan opsi mengaburkan masalah logis yang mendasarinya. Selain itu, saat mengerjakan satu masalah logis, kita harus terus-menerus "melompat" di sekitar blok opsi untuk kode yang relevan. Akan jauh lebih baik jika kita dapat menyusun kode yang terkait dengan masalah logis yang sama.

Saya ingin tahu apakah ada proposal lain untuk contoh kanonik yang kami coba tingkatkan selain yang saya kirimkan.
Jika itu adalah titik awal yang ingin digunakan orang, maka itu bagus. Namun, saya akan mengatakan masalah terbesar dengannya saat ini adalah verbositas; tidak banyak kode untuk digunakan kembali dalam contoh itu. Jadi tidak jelas bagi saya apakah itu representasi yang baik dari masalah seperti yang dijelaskan dalam OP.

Saya telah berpikir lebih jauh tentang bagaimana mengekspresikan karakteristik yang secara pribadi akan saya cari dalam sebuah solusi, dan itu membuat saya menyadari salah satu masalah besar yang saya lihat dengan proposal Hooks saat ini, yang merupakan salah satu alasan saya tidak akan melakukannya. ingin menggabungkannya ke dalam kerangka Flutter: Lokalitas dan Enkapsulasi, atau lebih tepatnya, ketiadaan. Desain Hooks menggunakan status global (misalnya, statis untuk melacak widget mana yang sedang dibangun). IMHO ini adalah karakteristik desain yang harus kita hindari. Secara umum kami mencoba membuat API mandiri, jadi jika Anda memanggil fungsi dengan satu parameter, Anda harus yakin bahwa itu tidak akan dapat melakukan apa pun dengan nilai di luar parameter itu (inilah sebabnya kami meneruskan BuildContext , daripada setara dengan useContext ). Saya tidak mengatakan ini adalah karakteristik yang diinginkan semua orang; dan tentu saja orang dapat menggunakan Hooks jika itu tidak menjadi masalah bagi mereka. Hanya saja itu adalah sesuatu yang saya ingin hindari melakukan lebih banyak di Flutter. Setiap kali kami memiliki status global (misalnya dalam binding) kami akhirnya menyesalinya.

Kait mungkin bisa menjadi metode pada konteks (saya pikir mereka ada di versi awal), tetapi jujur, saya tidak melihat banyak nilai dalam menggabungkannya seperti saat ini. Penggabungan perlu memiliki beberapa keuntungan untuk memisahkan paket, seperti peningkatan kinerja atau pengait alat debugging khusus. Kalau tidak, mereka hanya akan menambah kebingungan, misalnya Anda akan memiliki 3 cara resmi untuk mendengarkan yang dapat didengarkan: AnimatedBuilder, StatefulWidget & useListenable.

Jadi bagi saya, cara yang harus dilakukan adalah meningkatkan pembuatan kode - Saya telah mengusulkan beberapa perubahan: https://github.com/flutter/flutter/issues/63323

Jika saran ini benar-benar diterapkan, orang yang menginginkan solusi ajaib seperti SwiftUI di aplikasi mereka dapat membuat paket dan tidak mengganggu orang lain.

Membahas validitas hooks agak di luar topik pada tahap ini, karena sejauh yang saya tahu kami masih belum sepakat tentang masalah tersebut.

Seperti yang dinyatakan beberapa kali dalam masalah ini, ada banyak solusi lain, beberapa di antaranya adalah fitur bahasa.
Hooks hanyalah port dari solusi yang ada dari teknologi lain yang relatif murah untuk diterapkan dalam bentuk paling dasar.

Fitur ini dapat mengambil jalur yang sama sekali berbeda dari hook, seperti yang dilakukan SwiftUI atau Jetpack Compose; proposal "named mixin", atau gula sintaksis untuk proposal Builder.

Saya bersikeras pada fakta bahwa masalah ini pada intinya meminta penyederhanaan pola seperti StreamBuilder:

  • StreamBuilder memiliki keterbacaan/penulisan yang buruk karena bersarang
  • Mixin dan fungsi bukanlah alternatif yang memungkinkan untuk StreamBuilder
  • Copy-paste implementasi StreamBuilder di semua StatefulWidgets di seluruh tidak masuk akal

Semua komentar sejauh ini menyebutkan alternatif StreamBuilder, baik untuk perilaku yang berbeda (membuat objek sekali pakai, membuat permintaan HTTP, ...) atau mengusulkan sintaks yang berbeda.

Saya tidak yakin apa lagi yang harus dikatakan, jadi saya tidak melihat bagaimana kita bisa maju lebih jauh.
Apa yang Anda tidak mengerti/tidak setuju dalam pernyataan ini @Hixie?

@rrousselGit Bisakah Anda membuat aplikasi demo yang menunjukkan ini? Saya mencoba membuat aplikasi demo yang menunjukkan apa yang saya pahami sebagai masalahnya, tetapi ternyata saya tidak benar. (Saya tidak yakin apa perbedaan antara "pola seperti StreamBuilder" dan apa yang saya lakukan di aplikasi demo.)

  • StreamBuilder memiliki keterbacaan/penulisan yang buruk karena bersarang

Anda sudah mengatakan verbositas bukanlah masalahnya. Bersarang hanyalah aspek lain dari bertele-tele. Jika bersarang benar-benar menjadi masalah, kita harus mencari Padding, Expanded, Flexible, Center, SizedBox dan semua widget lain yang menambahkan nesting tanpa alasan yang jelas. Tapi bersarang dapat dengan mudah diselesaikan dengan memisahkan widget monolitik.

Copy-paste implementasi StreamBuilder di semua StatefulWidgets di seluruh tidak masuk akal

Maksud Anda salin-tempel baris kode yang membuat dan membuang aliran yang perlu dibuat dan dibuang StatefulWidgets? Ya, itu benar-benar masuk akal.

Jika Anda memiliki 10 atau Tuhan melarang ratusan _different_ StatefulWidgets kustom yang perlu membuat/membuang aliran mereka sendiri - "gunakan" dalam terminologi kait-, Anda memiliki masalah yang lebih besar untuk dikhawatirkan daripada penggunaan kembali atau bersarang ""logika". Saya akan khawatir tentang mengapa aplikasi saya harus membuat begitu banyak aliran berbeda sejak awal.

Agar adil, saya pikir tidak apa-apa bagi seseorang untuk berpikir bahwa pola tertentu tidak masuk akal di aplikasi mereka. (Itu tidak berarti bahwa kerangka kerja harus mendukung itu secara asli, tetapi akan lebih baik untuk setidaknya mengizinkan paket untuk menyelesaikannya.) Jika seseorang tidak ingin menggunakan stream.listen atau StreamBuilder(stream) , itu hak mereka, dan mungkin sebagai hasilnya kami dapat menemukan pola yang lebih baik untuk semua orang.

Agar adil, saya pikir tidak apa-apa bagi seseorang untuk berpikir bahwa pola tertentu tidak masuk akal di aplikasi mereka.

Saya 100% pada halaman yang sama dengan Anda.
Tentu, orang dapat melakukan apa pun yang mereka inginkan di aplikasi mereka. Apa yang saya coba dapatkan adalah bahwa semua masalah dan kesulitan yang dijelaskan di utas ini adalah hasil dari kebiasaan pemrograman yang buruk, dan sebenarnya tidak ada hubungannya dengan Dart atau Flutter. Itu seperti, hanya pendapat saya, tetapi saya akan mengatakan jika ada yang menulis aplikasi yang membuat lusinan aliran di semua tempat mungkin harus meninjau desain aplikasi mereka sebelum meminta kerangka kerja untuk "ditingkatkan".

Misalnya, implementasi kait yang berhasil masuk ke contoh repo.

  <strong i="10">@override</strong>
  Widget build(BuildContext context) {
    final value = useAnimation(animation);

    final screenHeight = MediaQuery.of(context).size.height;
    final textHeight =
        useMemoized(() => math.sqrt(screenHeight), [screenHeight]);

    return Text(
      'Change Duration',
      style: TextStyle(fontSize: 10.0 + value * textHeight),
    );
  }

Saya punya firasat buruk tentang ini, jadi saya memeriksa beberapa internal, dan menambahkan beberapa cetakan debug untuk memeriksa apa yang terjadi.
Anda dapat melihat dari output di bawah bahwa hook Listenable memeriksa cuaca yang telah diperbarui pada setiap centang animasi. Seperti, apakah widget yang "menggunakan" yang dapat didengarkan diperbarui? Apakah durasinya diubah? Apakah instance diganti?
Kait memoized, saya bahkan tidak tahu apa masalahnya dengan itu. Ini mungkin dimaksudkan untuk men-cache suatu objek, namun pada setiap build widget memeriksa apakah objek tersebut berubah? Apa? Mengapa? Tentu saja, karena digunakan di dalam widget stateful dan beberapa widget lain di atas pohon mungkin mengubah nilainya sehingga kita perlu melakukan polling untuk perubahan. Ini adalah perilaku polling literal yang merupakan kebalikan dari pemrograman "reaktif".

Yang lebih buruk, kait "baru" dan "lama" keduanya memiliki tipe instans yang sama, keduanya memiliki nilai yang sama, namun fungsi tersebut mengulangi nilai untuk memeriksa apakah mereka berubah. Pada _setiap animasi centang_.

Ini adalah output yang saya dapatkan, ad infinitum.

/flutter (28121): Use hook:_ListenableHook
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_ListenableHook'
I/flutter (28121): Hook1 keys:null
I/flutter (28121): Hook2 :Instance of '_ListenableHook'
I/flutter (28121): Hook2 keys:null
I/flutter (28121): 2. Shoud we preserve the  state of _ListenableHook:true
I/flutter (28121): 3: --------------
I/flutter (28121): checking if the listenable did change
I/flutter (28121): Did the listenable change?false
I/flutter (28121): Use hook:_MemoizedHook<double>
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_MemoizedHook<double>'
I/flutter (28121): Hook1 keys:[1232.0]
I/flutter (28121): Hook2 :Instance of '_MemoizedHook<double>'
I/flutter (28121): Hook2 keys:[1232.0]
I/flutter (28121): iterating over the hooks keys
I/flutter (28121): 2. Shoud we preserve the  state of _MemoizedHook<double>:true
I/flutter (28121): Use hook:_ListenableHook
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_ListenableHook'
I/flutter (28121): Hook1 keys:null
I/flutter (28121): Hook2 :Instance of '_ListenableHook'
I/flutter (28121): Hook2 keys:null
I/flutter (28121): 2. Shoud we preserve the  state of _ListenableHook:true

Semua pekerjaan ini dilakukan pada setiap centang animasi tunggal. Jika saya menambahkan kait lain seperti "warna akhir = useAnimation(animationColor);", untuk menganimasikan warna juga, sekarang widget memeriksa _dua_ kali apakah sudah diperbarui.

Saya duduk di sini menonton teks bernyawa bolak-balik tanpa perubahan dalam status aplikasi atau widget apa pun atau pohon widget, dan tetap saja kaitnya terus-menerus memeriksa apakah pohon/widget diperbarui. Memiliki setiap widget yang "menggunakan" kait khusus ini melakukan perilaku polling ini adalah buruk.

Menangani logika inisialisasi/pembaruan/pembuangan objek status di dalam metode build hanyalah desain yang buruk. Tidak ada peningkatan yang diperoleh dalam penggunaan kembali, hotreload atau beban kognitif, membenarkan dampak pada kinerja.
Sekali lagi, menurut saya. Kait menjadi sebuah paket, siapa pun dapat menggunakannya jika mereka pikir keuntungan membenarkan biaya overhead.

Saya juga tidak berpikir sejumlah fitur bahasa, sihir kompiler, atau abstraksi dapat mencegah pemeriksaan yang tidak perlu, jika kita mulai mencoba mengabstraksikan semua yang ada di dalam proses pembuatan. Jadi kita memiliki alternatif seperti memperluas StatefulWidget. Sesuatu yang sudah bisa dilakukan, dan diberhentikan berkali-kali.

@Hixie Anda belum menjawab pertanyaannya. Apa yang tidak Anda pahami/setujui dalam daftar poin di atas?

Saya tidak dapat membuat contoh tanpa mengetahui apa yang Anda ingin saya tunjukkan.

@Rudiksz Pasti solusi apa pun yang benar-benar kami pertimbangkan perlu diprofilkan dan di-benchmark untuk memastikan itu tidak memperburuk keadaan. Sebagian dari cara saya membuat aplikasi demo yang saya kirimkan ke @TimWhiting dimaksudkan untuk mencakup dengan tepat jenis pola yang mudah

@rrousselGit Saya tidak benar-benar ingin membahas ini karena ini sangat subjektif tetapi karena Anda bertanya:

  • Pertama, saya akan menghindari penggunaan Streams secara umum. ValueListenable adalah IMHO pola yang jauh lebih baik untuk kasus penggunaan yang kurang lebih sama.
  • Saya tidak berpikir StreamBuilder sangat sulit untuk dibaca atau ditulis; tetapi, seperti yang dikomentari @satvikpendem sebelumnya , riwayat saya adalah dengan pohon yang sangat bersarang di HTML, dan saya telah menatap pohon Flutter selama 4 tahun sekarang (saya telah mengatakan sebelumnya bahwa kompetensi inti Flutter adalah cara berjalan dengan efisien di pohon raksasa), jadi saya mungkin memiliki toleransi yang lebih tinggi daripada kebanyakan orang, dan pendapat saya di sini tidak terlalu relevan.
  • Mengenai apakah mixin dan fungsi dapat menjadi alternatif yang memungkinkan untuk StreamBuilder, saya pikir Hooks menunjukkan dengan cukup baik bahwa Anda pasti dapat menggunakan fungsi untuk mendengarkan aliran, dan mixin dapat dengan jelas melakukan apa pun yang dapat dilakukan kelas di sini, jadi saya tidak mengerti mengapa mereka melakukannya juga tidak menjadi solusi.
  • Akhirnya, mengenai implementasi copy-paste, itu masalah subjektif. Saya pribadi tidak menyalin dan menempelkan logika di initState/didUpdateWidget/dispose/build, saya menulisnya lagi setiap kali, dan tampaknya sebagian besar baik-baik saja. Ketika "di luar kendali" saya memasukkannya ke dalam widget seperti StreamBuilder. Jadi sekali lagi pendapat saya mungkin tidak relevan di sini.

Sebagai aturan umum, apakah saya mengalami masalah yang Anda lihat atau tidak, tidaklah relevan. Pengalaman Anda valid terlepas dari pendapat saya. Saya senang bekerja mencari solusi atas masalah yang Anda alami, meskipun saya tidak merasakan masalah tersebut. Satu-satunya efek dari saya tidak mengalami masalah sendiri adalah lebih sulit bagi saya untuk memahami masalah dengan baik, seperti yang telah kita lihat dalam diskusi ini.

Apa yang saya ingin Anda tunjukkan adalah pola pengkodean yang menurut Anda tidak dapat diterima, dalam demo yang cukup signifikan sehingga ketika seseorang membuat solusi, Anda tidak akan mengabaikannya dengan mengatakan bahwa mungkin sulit untuk digunakan ketika dalam situasi yang berbeda (yaitu sertakan semua situasi yang relevan, misalnya, pastikan untuk menyertakan bagian "perbarui" atau bagian lain apa pun yang menurut Anda penting untuk ditangani), atau mengatakan bahwa solusinya bekerja dalam satu kasus tetapi tidak dalam kasus umum (mis. umpan balik Anda sebelumnya, memastikan bahwa parameter berasal dari banyak tempat dan memperbarui dalam berbagai situasi sehingga jelas bahwa solusi seperti Properti di atas tidak akan mengesampingkan kode umum).

Masalahnya adalah, Anda meminta contoh untuk sesuatu yang ada dalam domain "jelas" bagi saya.

Saya tidak keberatan membuat beberapa contoh, tetapi saya tidak tahu apa yang Anda harapkan, karena saya tidak mengerti apa yang tidak Anda mengerti.

Saya sudah mengatakan semua yang harus saya katakan.
Satu-satunya hal yang dapat saya lakukan tanpa memahami apa yang tidak Anda pahami adalah mengulanginya sendiri.

Saya dapat membuat beberapa cuplikan di sini berjalan, tetapi itu sama dengan mengulangi diri saya sendiri.
Jika cuplikan tidak berguna, saya tidak mengerti mengapa dapat menjalankannya akan mengubah apa pun.

Mengenai apakah mixin dan fungsi dapat menjadi alternatif yang memungkinkan untuk StreamBuilder, saya pikir Hooks menunjukkan dengan cukup baik bahwa Anda pasti dapat menggunakan fungsi untuk mendengarkan aliran, dan mixin dapat dengan jelas melakukan apa pun yang dapat dilakukan kelas di sini, jadi saya tidak mengerti mengapa mereka melakukannya juga tidak menjadi solusi.

Kait tidak boleh dianggap sebagai fungsi.
Mereka adalah konstruksi bahasa baru yang mirip dengan Iterable/Stream

Fungsi tidak dapat melakukan apa yang dilakukan kait — tidak memiliki Status atau kemampuan untuk membuat widget dibangun kembali.

Masalah dengan mixin ditunjukkan di OP. TL; DR: Nama bentrok pada variabel dan tidak mungkin untuk menggunakan kembali campuran yang sama beberapa kali.

@rrousselGit Nah, karena Anda tidak keberatan membuat beberapa contoh, dan karena contoh yang saya minta sudah jelas, mari kita mulai dengan beberapa contoh yang jelas ini dan beralih dari sana.

Saya tidak mengatakan contohnya sudah jelas, tetapi masalahnya adalah.
Yang saya maksud adalah, saya tidak bisa membuat contoh baru. Semua yang harus saya katakan sudah ada di utas ini:

Saya tidak bisa memikirkan apa pun untuk ditambahkan ke contoh-contoh ini.

Tapi FWIW saya sedang mengerjakan aplikasi cuaca sumber terbuka menggunakan Riverpod. Saya akan menautkannya di sini setelah selesai.


Saya telah membuat polling di twitter yang menanyakan beberapa pertanyaan tentang Builder terkait dengan masalah yang dibahas di sini:

https://twitter.com/remi_rousselet/status/1295453683640078336

Jajak pendapat masih tertunda, tetapi berikut adalah angka saat ini:

Screenshot 2020-08-18 at 07 01 44

Fakta bahwa 86% dari 200 orang menginginkan cara untuk menulis Pembangun yang tidak melibatkan bersarang berbicara sendiri.

Untuk lebih jelasnya, saya tidak pernah menyarankan agar kita tidak membahas masalah ini. Jika saya pikir kita tidak harus mengatasinya, masalah ini akan selesai.

Saya kira saya akan mencoba membuat contoh yang menggunakan cuplikan yang Anda tautkan.

Saya dapat membantu Anda membuat contoh berdasarkan cuplikan yang ditautkan, tetapi saya perlu tahu mengapa cuplikan ini tidak cukup baik
Jika tidak, satu-satunya hal yang dapat saya lakukan adalah membuat cuplikan ini dikompilasi, tetapi saya ragu itu yang Anda inginkan.

Misalnya, berikut adalah intisari tentang banyak ValueListenableBuilder+TweenAnimationBuilder https://Gist.github.com/rrousselGit/a48f541ffaaafe257994c6f98992fa73

Misalnya, berikut adalah intisari tentang banyak ValueListenableBuilder+TweenAnimationBuilder https://Gist.github.com/rrousselGit/a48f541ffaaafe257994c6f98992fa73

FWIW, contoh yang satu ini bisa lebih mudah diimplementasikan di mobx.
Ini sebenarnya lebih pendek dari implementasi kait Anda.

Observable Mobx adalah ValueNotifiers pada steroid dan widget Observer-nya adalah evolusi dari ValueListenableBuilder milik Flutter - ia dapat mendengarkan lebih dari satu ValueNotifier.
Menjadi pengganti drop-in untuk kombo ValueNotifier/ValueListenableBuilder, berarti Anda masih menulis kode Flutter idiomatik, yang sebenarnya merupakan faktor penting.

Karena masih menggunakan pembuat Tween bawaan Flutter, di sini tidak perlu mempelajari/menerapkan widget/kait baru (dengan kata lain tidak memerlukan fitur baru) dan tidak memiliki hit kinerja kait.

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'counters.dart';

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

class MyApp extends StatelessWidget {
  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Home1(),
    );
  }
}

var counters = Counters();

class Home1 extends StatelessWidget {
  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 5),
                curve: Curves.easeOut,
                tween: IntTween(end: counters.firstCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
            RaisedButton(
              onPressed: () => counters.firstCounter += 100,
              child: Text('+'),
            ),
            // Both counters have voluntarily a different Curve and duration
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 2),
                curve: Curves.easeInOut,
                tween: IntTween(end: counters.secondCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
            RaisedButton(
              onPressed: () => counters.secondCounter += 100,
              child: Text('+'),
            ),
            const Text('total:'),
            // The total must take into account the animation of both counters
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 5),
                curve: Curves.easeOut,
                tween: IntTween(
                    end: counters.firstCounter + counters.secondCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Counters.dart sesederhana...

part 'counters.g.dart';

class Counters = _Counters with _$Counters;
abstract class _Counters with Store {
  <strong i="18">@observable</strong>
  int firstCounter = 0;

  <strong i="19">@observable</strong>
  int secondCounter = 0;
}

Inilah implementasi lain yang bahkan tidak memerlukan pembuat animasi. Metode pembuatan widget semurni mungkin, hampir seperti file html semantik ... seperti template.

https://Gist.github.com/Rudiksz/cede1a5fe88e992b158ee3bf15858bd9

@Rudiksz Perilaku bidang "total" rusak dalam cuplikan Anda. Itu tidak cocok dengan contoh, di mana kedua penghitung dapat bernyawa bersama tetapi menyelesaikan animasi pada waktu yang berbeda dan dengan kurva yang berbeda.
Demikian pula, saya tidak yakin apa yang ditambahkan contoh ini ke varian ValueListenableBuilder .

Adapun inti terakhir Anda, TickerProvider rusak karena tidak mendukung TickerMode – pendengar juga tidak dihapus atau pengontrol dibuang.

Dan Mobx kemungkinan di luar topik. Kami tidak membahas bagaimana menerapkan keadaan sekitar / ValueListenable vs Stores vs Streams, melainkan bagaimana menangani keadaan lokal / Pembangun bersarang - yang tidak diselesaikan oleh Mobx dengan cara apa pun

––––

Juga, ingatlah bahwa dalam contoh kait, useAnimatedInt dapat/harus diekstraksi ke dalam sebuah paket dan tidak ada duplikat durasi/kurva antara animasi teks individual dan total.

Untuk performa, dengan Hooks kami hanya membangun kembali satu Elemen, sedangkan dengan Builder dengan membangun kembali 2-4 Builder.
Jadi Hooks bisa jadi lebih cepat.

Perilaku bidang "total" rusak di cuplikan Anda. Itu tidak cocok dengan contoh, di mana kedua penghitung dapat bernyawa bersama tetapi menyelesaikan animasi pada waktu yang berbeda dan dengan kurva yang berbeda.

Anda jelas bahkan tidak mencoba menjalankan contoh. Ini berperilaku persis seperti kode Anda.

Adapun inti terakhir Anda, TickerProvider rusak karena tidak mendukung TickerMode.

Saya tidak tahu apa yang Anda maksud dengan ini. Saya memfaktorkan ulang contoh _your_, yang tidak menggunakan TickerMode. Anda lagi mengubah persyaratan.

Untuk performa, dengan Hooks kami hanya membangun kembali satu Elemen, sedangkan dengan Builder dengan membangun kembali 2-4 Builder. Jadi Hooks bisa jadi lebih cepat.

Tidak hanya tidak. Widget hook Anda terus-menerus melakukan polling untuk perubahan pada setiap build. Pembangun berdasarkan nilai yang dapat didengar adalah "reaktif".

Demikian pula, saya tidak yakin apa yang ditambahkan contoh ini ke varian ValueListenableBuilder .

Dan Mobx kemungkinan di luar topik. Kami tidak membahas bagaimana menerapkan keadaan sekitar / ValueListenable vs Stores vs Streams, melainkan bagaimana menangani keadaan lokal / Pembangun bersarang - yang tidak diselesaikan oleh Mobx dengan cara apa pun

Kamu pasti bercanda. Saya mengambil contoh Anda dan "menangani" dengan ValueListenableBuilders * dan tween builder bersarang?! Poin yang secara khusus Anda kemukakan sebagai masalah.
Tapi kalimat ini di sini, menggambarkan seluruh sikap Anda terhadap diskusi ini. Jika bukan kait, itu "di luar topik", tetapi Anda mengatakan bahwa Anda tidak peduli apakah itu kait yang akan digunakan sebagai solusi.
Beri aku istirahat.

Anda jelas bahkan tidak mencoba menjalankan contoh. Ini berperilaku persis seperti kode Anda.

Tidak. Penghitung pertama bergerak lebih dari 5 detik, dan yang kedua lebih dari 2 detik – dan keduanya juga menggunakan Kurva yang berbeda.

Dengan kedua cuplikan yang saya berikan, Anda dapat meningkatkan kedua penghitung secara bersamaan, dan selama setiap bingkai animasi, "total" akan benar. Bahkan ketika penghitung kedua berhenti bergerak saat penghitung pertama masih bergerak

Di sisi lain, implementasi Anda tidak mempertimbangkan kasus ini, karena menggabungkan 2 TweenAnimationBuilders menjadi satu.
Untuk memperbaikinya, kita harus menulis:

Observer(
  builder: (context) {
    return TweenAnimationBuilder<int>(
      duration: const Duration(seconds: 5),
      curve: Curves.easeOut,
      tween: IntTween(end: counters.firstCounter),
      builder: (context) {
        return Observer(
          valueListenable: secondCounter,
          builder: (context, value2, child) {
            return TweenAnimationBuilder<int>(
              duration: const Duration(seconds: 2),
              curve: Curves.easeInOut,
              tween: IntTween(end: counters.secondCounter),
              builder: (context, value2, child) {
                return Text('${value + value2}');
              },
            );
          },
        );
      },
    );
  },
)

Kedua TweenAnimationBuilders diperlukan untuk menghormati fakta bahwa kedua penghitung dapat dianimasikan secara individual. Dan kedua Observer diperlukan karena Observer tidak dapat mengamati counters.secondCounter


Tidak hanya tidak. Widget hook Anda terus-menerus melakukan polling untuk perubahan pada setiap build. Pembangun berdasarkan nilai yang dapat didengar adalah "reaktif".

Anda mengabaikan apa yang dilakukan Element , yang kebetulan sama dengan apa yang dilakukan kait: membandingkan runtimeType dan kunci, dan memutuskan apakah akan membuat Elemen baru atau memperbarui yang sudah ada

Saya mengambil contoh Anda dan "menangani" dengan ValueListenableBuilders * dan tween builder bersarang

Dengan asumsi bahwa masalah dengan jumlah total sudah diperbaiki, sarang apa yang dihapus?

Observer(
  builder: (context) => TweenAnimationBuilder<int>(
    duration: const Duration(seconds: 5),
    curve: Curves.easeOut,
    tween: IntTween(end: counters.firstCounter),
    builder: (context, value, child) {
      return Text('$value');
    },
  ),
),

tidak berbeda dengan:

ValueListenableBuilder<int>(
  valueListenable: firstCounter,
  builder: (context, value, child) => TweenAnimationBuilder<int>(
    duration: const Duration(seconds: 5),
    curve: Curves.easeOut,
    tween: IntTween(end: value),
    builder: (context, value, child) {
      return Text('$value');
    },
  ),
),

dalam hal bersarang.

Jika Anda mengacu pada inti Anda, maka seperti yang saya sebutkan sebelumnya, pendekatan ini merusak TickerProvider / TickerMode . vsync perlu diperoleh menggunakan SingleTickerProviderClientStateMixin atau jika tidak, tidak mendukung logika mematikan, yang dapat menyebabkan masalah kinerja.
Saya telah menjelaskan ini dalam artikel saya: https://dash-overflow.net/articles/why_vsync/

Dan dengan pendekatan ini, kita harus mengimplementasikan kembali logika Tween di setiap lokasi yang awalnya menginginkan TweenAnimationBuilder. Itu mengarah ke duplikat yang signifikan, terutama mengingat logikanya tidak begitu sepele

Dengan kedua cuplikan yang saya berikan, Anda dapat meningkatkan kedua penghitung secara bersamaan, dan selama setiap bingkai animasi, "total" akan benar. Bahkan ketika penghitung kedua berhenti bergerak saat penghitung pertama masih bergerak

Di sisi lain, implementasi Anda tidak mempertimbangkan kasus ini, karena menggabungkan 2 TweenAnimationBuilders menjadi satu.

Ya, itu trade-off yang ingin saya lakukan. Saya dapat dengan mudah membayangkan kasus di mana animasi hanya akan menjadi umpan balik visual untuk perubahan yang terjadi dan akurasinya tidak penting. Itu semua tergantung pada persyaratan.

Saya curiga Anda akan keberatan, karenanya versi kedua yang memecahkan masalah ini, sambil membuat kode lebih bersih.

Jika Anda mengacu pada inti Anda, maka seperti yang saya sebutkan sebelumnya, pendekatan ini merusak TickerProvider/TickerMode. Vsync perlu diperoleh menggunakan SingleTickerProviderClientStateMixin atau jika tidak, tidak mendukung logika mematikan, yang dapat menyebabkan masalah kinerja.

Jadi Anda membuat tickerprovider di widget dan meneruskannya ke Counter. Saya juga tidak membuang pengontrol animasi. Detail ini sangat sepele untuk diterapkan, saya tidak merasa mereka akan menambahkan apa pun ke dalam contoh. Tapi di sini kita nitpicking pada mereka.

Saya menerapkan kelas Counter() untuk melakukan apa yang dilakukan contoh Anda dan tidak lebih.

Dan dengan pendekatan ini, kita harus mengimplementasikan kembali logika Tween di setiap lokasi yang awalnya menginginkan TweenAnimationBuilder. Itu mengarah ke duplikat yang signifikan, terutama mengingat logikanya tidak begitu sepele

Apa? mengapa? Tolong jelaskan, mengapa saya tidak bisa membuat lebih dari satu instance dari kelas Counter dan menggunakannya di berbagai widget?

@Rudiksz Saya tidak yakin solusi Anda benar-benar menyelesaikan masalah yang ditetapkan. Kamu bilang

Saya menerapkan kelas Counter() untuk melakukan apa yang dilakukan contoh Anda dan tidak lebih.

dan lagi

Ya, itu trade-off yang ingin saya lakukan. Saya dapat dengan mudah membayangkan kasus di mana animasi hanya akan menjadi umpan balik visual untuk perubahan yang terjadi dan akurasinya tidak penting. Itu semua tergantung pada persyaratan.

Jadi Anda membuat tickerprovider di widget dan meneruskannya ke Counter. Saya juga tidak membuang pengontrol animasi. Detail ini sangat sepele untuk diterapkan, saya tidak merasa mereka akan menambahkan apa pun ke dalam contoh. Tapi di sini kita nitpicking pada mereka.

Anda memberikan kode yang seolah-olah hanya setara dengan versi hook @rrousselGit , namun sebenarnya tidak setara, karena Anda @TimWhiting . Anda dapat mengirimkan solusi Anda di sana jika Anda merasa telah memenuhi semua persyaratan.

Anda jelas bahkan tidak mencoba menjalankan contoh. Ini berperilaku persis seperti kode Anda.

Tidak hanya tidak.

Kamu pasti bercanda. Saya mengambil contoh Anda dan "menangani" dengan ValueListenableBuilders * dan tween builder bersarang

Harap menahan diri dari tuduhan seperti ini, mereka hanya berfungsi untuk membuat utas ini pedas tanpa menyelesaikan masalah yang mendasarinya. Anda dapat menyampaikan pendapat Anda tanpa menuduh, menghina, atau marah pada pihak lain, yang merupakan efek dari komentar yang saya lihat di utas ini, saya tidak melihat perilaku seperti itu dari orang lain terhadap Anda. Saya juga tidak yakin apa efek emoji-bereaksi terhadap posting Anda sendiri.

@rrousselGit

Saya dapat membantu Anda membuat contoh berdasarkan cuplikan yang ditautkan, tetapi saya perlu tahu mengapa cuplikan ini tidak cukup baik
Jika tidak, satu-satunya hal yang dapat saya lakukan adalah membuat cuplikan ini dikompilasi, tetapi saya ragu itu yang Anda inginkan.

Alasan saya meminta aplikasi yang lebih rumit daripada sekadar cuplikan adalah karena ketika saya memposting contoh yang menangani salah satu cuplikan Anda, Anda mengatakan bahwa itu tidak cukup baik karena itu juga tidak menangani beberapa kasus lain yang tidak 't di cuplikan Anda (mis. tidak menangani didUpdateWidget, atau tidak menangani dua cuplikan ini secara berdampingan, atau hal-hal wajar lainnya yang ingin Anda tangani). Saya berharap dengan aplikasi yang lebih rumit kita bisa membuatnya cukup rumit sehingga begitu kita memiliki solusi, tidak ada momen "gotcha" di mana beberapa masalah baru muncul yang perlu ditangani juga. Jelas masih mungkin bahwa kita semua akan kehilangan sesuatu yang perlu ditangani, tetapi idenya adalah untuk meminimalkan peluang.

Mengenai posting baru-baru ini yang kurang ramah, tolong fokus pada membuat contoh di repo @TimWhiting daripada melawan bolak-balik dengan contoh-contoh kecil. Seperti yang telah kita bahas, contoh-contoh kecil tidak akan pernah cukup rumit untuk menjadi cukup representatif untuk membuktikan alternatif benar-benar bekerja dengan baik.

Anda dapat mengirimkan solusi Anda di sana jika Anda merasa telah memenuhi semua persyaratan.

Persyaratan diubah setelah fakta. Saya memberikan dua solusi. Salah satu yang membuat kompromi (yang sangat masuk akal) dan yang mengimplementasikan perilaku yang tepat seperti contoh yang diberikan.

Anda memberikan kode yang seolah-olah hanya setara dengan versi kait @rrousselGit , namun sebenarnya tidak setara,

Saya tidak menerapkan "solusi kait", saya menerapkan contoh ValueListenableBuilder, yang secara khusus berfokus pada "masalah bersarang". Itu tidak melakukan setiap hal yang dilakukan kait, saya hanya menunjukkan bagaimana satu item dalam daftar keluhan dapat disederhanakan menggunakan solusi alternatif.

Jika Anda diizinkan membawa paket eksternal ke dalam diskusi, maka saya juga.

Dapat digunakan kembali: lihat contoh di repo di bawah ini
https://github.com/Rudiksz/cbl_example

Catatan:

  • itu dimaksudkan sebagai karya tentang bagaimana Anda dapat merangkum "logika" di luar widget dan memiliki widget yang ramping, hampir terlihat seperti html
  • itu tidak mencakup semua penutup kait. Itu bukan intinya. Saya pikir kita sedang mendiskusikan alternatif untuk kerangka kerja Flutter stok, dan bukan paket kait.
  • Itu tidak mencakup _setiap_ kasus penggunaan, setiap tim pemasaran dapat muncul
  • tetapi, objek Penghitung itu sendiri cukup fleksibel, dapat digunakan secara mandiri (lihat judul AppBar), sebagai bagian dari widget kompleks yang perlu menghitung total penghitung yang berbeda, dibuat reaktif, atau menanggapi input pengguna.
  • Terserah widget konsumsi untuk menyesuaikan jumlah, nilai awal, durasi, jenis animasi penghitung yang ingin mereka gunakan.
  • rincian cara animasi ditangani mungkin memiliki kelemahan. Jika tim Flutter mengatakan ya, menggunakan pengontrol Animasi dan remaja di luar widget, entah bagaimana merusak kerangka kerja, saya akan mempertimbangkan kembali. Pasti ada hal-hal yang perlu ditingkatkan. Mengganti penyedia ticker khusus yang saya gunakan dengan yang dibuat oleh mixin widget konsumsi adalah hal yang sepele. Saya belum melakukannya.
  • Ini hanyalah satu lagi solusi alternatif untuk pola "Pembangun". Jika Anda mengatakan Anda perlu atau ingin menggunakan Builder, maka semua ini tidak berlaku.
  • Namun, faktanya ada cara untuk menyederhanakan kode, tanpa fitur tambahan. Jika Anda tidak menyukainya, jangan membelinya. Saya tidak menganjurkan semua ini untuk membuatnya menjadi kerangka kerja.

Sunting: Contoh ini memiliki bug, jika Anda memulai "kenaikan" saat perubahan dianimasikan, penghitung diatur ulang dan bertambah dari nilai saat ini. Saya tidak memperbaikinya dengan sengaja, karena saya tidak tahu persyaratan pasti yang mungkin Anda miliki untuk "penghitung" ini. Sekali lagi itu sepele untuk mengubah metode kenaikan/penurunan, untuk memperbaiki masalah ini.

Itu semuanya.

@Hixie Haruskah saya menafsirkan komentar Anda sebagai cara untuk mengatakan bahwa contoh saya (https://github.com/TimWhiting/local_widget_state_approaches/blob/master/lib/hooks/animated_counter.dart) tidak cukup baik?

Juga, bisakah kami melakukan panggilan rapat Zoom/Google?

Saya juga tidak yakin apa efek emoji-bereaksi terhadap posting Anda sendiri.

Tidak. Itu sama sekali tidak relevan dengan apa pun. Mengapa Anda mengangkatnya?

@rrousselGit Hanya Anda yang tahu apakah itu cukup baik. Jika kami menemukan cara untuk memperbaiki contoh itu sehingga bersih dan pendek dan tidak memiliki kode duplikat, apakah Anda akan puas? Atau adakah hal-hal yang menurut Anda harus kami dukung yang tidak ditangani oleh contoh itu yang perlu kami tangani untuk mengatasi bug ini?

Hanya Anda yang tahu apakah itu cukup baik

Saya tidak bisa menjadi hakim untuk itu. Untuk memulainya, saya tidak percaya bahwa kita dapat menangkap masalah menggunakan kumpulan aplikasi yang terbatas.

Saya tidak keberatan bekerja seperti yang Anda inginkan, tetapi saya membutuhkan bimbingan karena saya tidak mengerti bagaimana hal itu akan membuat kita maju.

Saya pikir kami telah menyediakan banyak cuplikan kode yang menunjukkan masalah dari sudut pandang kami. Saya benar-benar tidak berpikir akan menjadi lebih jelas melalui lebih banyak contoh kode, jika yang ditampilkan tidak melakukannya.

Misalnya, jika melihat beberapa pembuat bersarang yang mengerikan untuk dibaca, atau 50 baris boilerplate murni yang memiliki banyak peluang untuk bug, tidak menunjukkan masalah dengan cukup kuat, tidak ada tempat untuk pergi.

Sangat aneh untuk menawarkan mixin dan fungsi adalah solusi di sini, ketika seluruh ask dienkapsulasi state , yang dapat digunakan kembali. Fungsi tidak dapat mempertahankan status. Mixin tidak dienkapsulasi. Saran ini meleset dari seluruh inti dari semua contoh dan alasan yang diberikan dan masih menunjukkan kesalahpahaman yang mendalam tentang pertanyaan tersebut.

Bagi saya, saya pikir kami telah mengalahkan 2 poin ke bumi, dan saya pikir kami juga tidak bisa berdebat dengan serius.

  1. Pembangun bersarang secara inheren sulit dibaca
  2. Selain pembangun bersarang, ada _tidak ada cara_ untuk merangkum dan membagikan status yang memiliki kait siklus hidup widget.

Seperti yang dinyatakan berkali-kali, dan bahkan dalam jajak pendapat Remi, sederhananya: Kami ingin kemampuan builder, tanpa verbositas dan penutupan bersarang dari builder. Apakah itu tidak sepenuhnya meringkasnya? Saya benar-benar bingung mengapa contoh kode lebih lanjut diperlukan untuk melanjutkan di sini.

Jajak pendapat Remi menunjukkan kepada kita bahwa ~80% pengembang Flutter lebih memilih semacam kemampuan untuk menghindari pembuat bersarang dalam kode mereka jika memungkinkan. Ini benar-benar berbicara sendiri imo. Anda tidak perlu mengambilnya dari kami di utas ini, ketika sentimen komunitas begitu jelas.

Dari sudut pandang saya, masalahnya jelas, dan menjadi lebih jelas ketika Anda melihat kerangka kerja bersaing yang mendedikasikan paragraf untuk menjelaskan alasannya di sini. Vue, React, Flutter mereka semua sepupu, mereka semua berasal dari React, dan mereka semua menghadapi masalah ini dengan status penggunaan kembali yang harus dikaitkan dengan siklus hidup widget. Mereka semua menjelaskan mengapa mereka menerapkan sesuatu seperti ini secara rinci. Tidak apa-apa di sana. Itu semua relevan.

@rrousselGit dapatkah Anda membuat contoh memiliki banyak kait ganda? Misalnya, saya membuat animasi dengan kemungkinan puluhan AnimationControllers. Dengan Flutter biasa, saya dapat melakukan:

List<AnimationController> controllers = [];
int numAnimationControllers = 50;

<strong i="7">@override</strong>
void initState() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers.add(AnimationController(...));
}

<strong i="8">@override</strong>
void dispose() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers[i].dispose();
}

Tetapi dengan kait saya tidak dapat memanggil useAnimationController dalam satu lingkaran. Saya kira ini adalah contoh sepele tetapi saya tidak dapat menemukan solusi di mana pun untuk jenis kasus penggunaan ini.

@satvikpendem

beberapa contoh dari aplikasi saya yang sedang dalam produksi (beberapa kait seperti mengirim permintaan dengan pagination dapat menggabungkan/memfaktorkan ulang menjadi satu kait tetapi itu tidak relevan di sini):

pengambilan data sederhana dengan pagination:

    final selectedTab = useState(SelectedTab.Wallet);
    final isDrawerOpen = useValueNotifier(false);
    final pageNo = useValueNotifier(0);
    final generalData = useValueNotifier(initialData);
    final services = useXApi();
    final isLoading = useValueNotifier(false);
    final waybillData = useValueNotifier<List<WaybillResponseModel>>([]);
    final theme = useTheme();
    final router = useRouter();

    fetchNextPage() async {
      if (isLoading.value || selectedTab.value != SelectedTab.Wallet) return;
      isLoading.value = true;
      final request = WaybillRequestModel()..pageNo = pageNo.value;
      final result = await services.waybillGetList(model: request);
      if (result.isOk && result.data.length > 0) {
        pageNo.value += 1;
        waybillData.value = [...waybillData.value, ...result.data];
      }
      isLoading.value = false;
    }

    // first fetch
    useEffect(() {
      fetchNextPage();
      return () {};
    }, []);

logika formulir (formulir login dengan verifikasi nomor telepon dan kirim ulang timer):

    final theme = useTheme();
    final loginState = useValueNotifier(LoginState.EnteringNumber);
    final error = useValueNotifier<String>(null);
    final phoneNumberController = useTextEditingController(text: "");
    final phoneNumberFocusNode = useMemoized(() => FocusNode(), []);
    final otpFocusNode = useMemoized(() => FocusNode(), []);
    final otpController = useTextEditingController(text: "");
    final appState = Provider.of<AppStateController>(context);
    final services = useXApi();
    final router = useRouter();
    final resendTimerValue = useValueNotifier(0);
    useEffect(() {
      var timer = Timer.periodic(Duration(seconds: 1), (t) async {
        if (resendTimerValue.value > 0) resendTimerValue.value--;
      });
      return () => timer.cancel();
    }, []);

    final doLogin = () async {
      // login
      loginState.value = LoginState.LoggingIn;
      final loginResult = await services.authLoginOrRegister(
        mobileNumber: phoneNumberController.text,
      );
      if (loginResult.isOk) {
        loginState.value = LoginState.EnteringOtpCode;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          FocusScope.of(context).requestFocus(otpFocusNode);
        });
        resendTimerValue.value = 30;
      } else {
        error.value = loginResult.errorMessage;
        loginState.value = LoginState.EnteringNumber;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          FocusScope.of(context).requestFocus(phoneNumberFocusNode);
        });
      }
    };

untuk animasi, saya rasa @rrousselGit sudah cukup memberikan contoh.

Saya tidak ingin berbicara tentang bagaimana sifat hook yang dapat dikomposisi dapat membuat refactoring kode di atas lebih mudah, dapat digunakan kembali dan lebih bersih tetapi jika Anda mau, saya juga dapat memposting versi refactored.

Seperti yang dinyatakan berkali-kali, dan bahkan dalam jajak pendapat Remi, sederhananya: Kami ingin kemampuan builder, tanpa verbositas dan penutupan bersarang dari builder. Apakah itu tidak sepenuhnya meringkasnya? Saya benar-benar bingung mengapa contoh kode lebih lanjut diperlukan untuk melanjutkan di sini.

Saya benar-benar memberikan contoh bagaimana Anda dapat mengurangi verbositas dan menghindari penumpukan pembangun menggunakan contoh yang diberikan Remi.
Saya mengambil kodenya, memasukkannya ke dalam aplikasi saya, menjalankannya dan menulis ulang. Hasil akhirnya sejauh menyangkut fungsi hampir identik - sebanyak yang saya dapat peroleh dari menjalankan kode saja, karena kode tidak disertai dengan persyaratan. Tentu kita bisa mendiskusikan kasus tepi dan masalah potensial, tetapi itu disebut di luar topik.

Untuk kasus penggunaan sederhana saya menggunakan Builder, untuk kasus penggunaan kompleks saya tidak menggunakan Builder. Argumennya di sini adalah bahwa tanpa menggunakan Builder, tidak ada cara mudah untuk menulis kode yang ringkas dan dapat digunakan kembali. Secara implisit, itu juga berarti Builder harus dimiliki, dan satu-satunya cara untuk mengembangkan aplikasi Flutter. Itu terbukti salah.

Saya baru saja menunjukkan kode konsep yang berfungsi, bukti yang menunjukkannya. Itu tidak menggunakan Pembangun atau pengait, dan itu tidak mencakup 100% dari "serangkaian masalah tak terbatas" yang tampaknya ingin dipecahkan oleh masalah github khusus ini. Itu disebut di luar topik.
Sidenote, ini juga sangat efisien, bahkan tanpa benchmark apapun saya kira bahkan mengalahkan widget Builder. Saya senang berubah pikiran jika terbukti salah, dan jika saya menemukan Mobx menjadi penghambat kinerja, saya akan membuang Mobx dan beralih ke pembuat vanilla dalam sekejap.

Hixie bekerja untuk Google, dia harus sabar dan sopan dengan Anda dan tidak dapat menghubungi Anda karena kurangnya keterlibatan Anda. Yang terbaik yang bisa dia lakukan adalah mendorong lebih banyak contoh.

Saya tidak menyebut nama siapa pun, atau membawa serangan pribadi. Saya hanya pernah bereaksi terhadap argumen yang disajikan di sini, membagikan pendapat saya
(yang saya tahu tidak populer dan minoritas) dan bahkan mencoba menyajikan contoh penghitung aktual dengan kode. Saya bisa berbuat lebih banyak, saya bersedia mendiskusikan di mana contoh saya gagal dan melihat cara kami dapat memperbaikinya, tapi ya, disebut di luar topik agak tidak menyenangkan.

Saya tidak akan rugi apa-apa, selain mungkin dilarang, jadi saya tidak peduli memanggil Anda keluar.
Jelas bahwa Anda berdua sudah mati bahwa kait adalah satu-satunya solusi ("karena Bereaksi melakukannya") untuk apa pun masalah yang Anda alami dan bahwa kecuali jika alternatif memenuhi 100% dari "kumpulan masalah tak terbatas" yang Anda bayangkan, Anda bahkan tidak akan mempertimbangkan untuk terlibat.

Itu tidak masuk akal, dan menunjukkan kurangnya keinginan untuk benar-benar terlibat.


Tentu saja, semua yang di atas "hanya pendapat saya".

Saya melihat kegunaan kait dalam contoh itu tetapi saya kira saya tidak mengerti cara kerjanya dalam kasus saya di mana sepertinya Anda ingin menginisialisasi banyak objek sekaligus, dalam hal ini AnimationController s tetapi pada kenyataannya itu bisa apa saja. Bagaimana hook menangani kasus ini?

Pada dasarnya apakah ada cara kait untuk memutar ini

var x1 = useState(1);
var x2 = useState(2);
var x3 = useState(3);

Ke dalam

var xs = []
for (int i = 0; i < 3; i++)
     xs[i] = useState(i);

Tanpa melanggar aturan hook? Karena saya mencantumkan yang setara di Flutter normal. Saya tidak terlalu berpengalaman dengan kait di Flutter jadi bersabarlah di sana.

Saya hanya ingin membuat array objek kait (AnimationControllers misalnya) sesuai permintaan dengan semua initState dan buangnya sudah dipakai, saya hanya tidak yakin cara kerjanya di kait.

@satvikpendem pikirkan tentang kait seperti properti di kelas. apakah Anda mendefinisikannya dalam satu lingkaran atau secara manual menamainya satu per satu?

dalam contoh Anda mendefinisikan seperti ini

var x1 = useState(1);
var x2 = useState(2);
var x3 = useState(3);

berguna untuk kasus penggunaan ini:

var isLoading = useState(1);
var selectedTab = useState(2);
var username = useState(3); // text field

apakah Anda melihat bagaimana setiap useState terkait dengan bagian bernama dari logika negara Anda? (seperti useState isLoading terhubung saat aplikasi dalam status memuat)

di cuplikan kedua Anda, Anda memanggil useState dalam satu lingkaran. Anda memikirkan useState sebagai pemegang nilai bukan bagian dari logika negara Anda. apakah daftar ini diperlukan untuk menampilkan banyak item dalam ListView ? jika ya maka Anda harus menganggap setiap item dalam daftar sebagai keadaan bukan secara individual.

final listData = useState([]);

ini hanya untuk useState dan saya dapat melihat beberapa kasus penggunaan (yang menurut saya sangat jarang) untuk memanggil beberapa kait dalam suatu kondisi atau dalam satu lingkaran. untuk kait semacam itu harus ada kait lain untuk menangani daftar data alih-alih satu. Misalnya:

var single = useTest("data");
var list = useTests(["data1", "data2"]);
// which is equivalent to
var single1 = useTest("data1");
var single2 = useTest("data2");

Begitu, jadi dengan kait sepertinya kita perlu membuat kait terpisah untuk menangani kasus dengan array item, seperti beberapa AnimationControllers.

Inilah yang saya miliki pada awalnya yang sepertinya tidak berhasil:

  final animationControllers = useState<List<AnimationController>>([]);

  animationControllers.value = List<AnimationController>.generate(
    50,
    (_) => useAnimationController(),
  );

tetapi saya kira jika saya menulis kait saya sendiri untuk menangani banyak item, ini akan berhasil, bukan?

class _MultipleAnimationControllerHook extends Hook<MultipleAnimationController> {
  const _MultipleAnimationControllerHook({
    this.numControllers,
    this.duration,
    this.debugLabel,
    this.initialValue,
    this.lowerBound,
    this.upperBound,
    this.vsync,
    this.animationBehavior,
    List<Object> keys,
  }) : super(keys: keys);

  /// Take in number of controllers wanted
  /// This hook assumes all `AnimationController`s will have the same parameters
  final int numControllers; 

  final Duration duration;
  final String debugLabel;
  final double initialValue;
  final double lowerBound;
  final double upperBound;
  final TickerProvider vsync;
  final AnimationBehavior animationBehavior;

  <strong i="10">@override</strong>
  _AnimationControllerHookState createState() =>
      _AnimationControllerHookState();
}

class _AnimationControllerHookState
    extends HookState<AnimationController, _AnimationControllerHook> {
  List<AnimationController> _multipleAnimationController; // return a list instead of a singular item

  <strong i="11">@override</strong>
  void initHook() {
    super.initHook();
    for (int i = 0; i < hook.numControllers) // just added loops 
        _multipleAnimationController[i] = AnimationController(
          vsync: hook.vsync,
          duration: hook.duration,
          debugLabel: hook.debugLabel,
          lowerBound: hook.lowerBound,
          upperBound: hook.upperBound,
          animationBehavior: hook.animationBehavior,
          value: hook.initialValue,
        );
  }

  <strong i="12">@override</strong>
  void didUpdateHook(_AnimationControllerHook oldHook) {
      for (int i = 0; i < numControllers; i++) {
        if (hook.vsync != oldHook[i].vsync) {
           _multipleAnimationController[i].resync(hook.vsync);
        }

        if (hook.duration != oldHook[i].duration) {
          _multipleAnimationController[i].duration = hook.duration;
        }
      }
  }

  <strong i="13">@override</strong>
  MultipleAnimationController build(BuildContext context) {
    return _multipleAnimationController;
  }

  <strong i="14">@override</strong>
  void dispose() {
    _multipleAnimationController.map((e) => e.dispose());
  }
}

Apakah ini berarti bahwa jika kita memiliki hook versi tunggal, kita tidak dapat menggunakannya untuk versi dengan banyak item dan sebagai gantinya harus menulis ulang logikanya? Atau adakah cara yang lebih baik untuk melakukan ini?

Jika ada yang juga ingin memberikan contoh non-kait yang ingin saya ketahui juga, saya bertanya-tanya tentang potongan teka-teki yang dapat digunakan kembali ini. Mungkin ada cara untuk merangkum perilaku ini di kelas, yang memiliki bidang AnimationController sendiri, tetapi jika itu dibuat di dalam satu lingkaran, maka kaitnya juga, melanggar aturan. Mungkin kita bisa mempertimbangkan bagaimana Vue melakukannya, yang tidak terpengaruh oleh conditional dan loop untuk implementasi hook-nya.

@satvikpendem

Saya rasa pernyataan saya tidak valid untuk AnimationController atau useAnimationController

karena meskipun Anda mungkin memiliki lebih dari satu AnimationController tetapi Anda tidak perlu menyimpannya dalam array untuk menggunakannya dalam metode kelas. Misalnya:

useSingleTickProvider();
final animation1 = useAnimationController();
final animation2 = useAnimationController();
final animation3 = useAnimationController();
// ...
useEffect(() async {
  await animation1.forward();
  await Future.sleep(100);
  await animation1.reverse();
  await animation2.forward();
  await animation3.forward();
}, []);

(Anda tidak membuat daftar dan mereferensikannya seperti animation[0] )

jujur ​​dalam pengalaman saya dalam bereaksi dan bergetar dengan kait, saya jarang perlu memanggil semacam kait dalam satu lingkaran. bahkan kemudian solusinya sangat mudah dan mudah diterapkan. sekarang saya memikirkannya, itu pasti bisa diselesaikan dengan cara yang lebih baik seperti membuat Komponen (widget) untuk masing-masing dari mereka yang IMO adalah solusi "lebih bersih".

untuk menjawab pertanyaan Anda jika ada cara yang lebih mudah untuk menangani beberapa AnimationController , ya ada:

final ticker = useTickerProvider();
final controllers = useMemo(() => [AnimationController(ticker), AnimationController(ticker)], []);

useEffect(() {
  controllers.forEach(x => x.resync(ticker));
  return () => controllers.forEach(x => x.dispose());
}, [ticker, controllers]);

  • anda juga dapat menggunakan useState jika AnimationController s dinamis.

(itu juga disinkronkan kembali ketika ticker diubah)

@rrousselGit dapatkah Anda membuat contoh memiliki banyak kait ganda? Misalnya, saya membuat animasi dengan kemungkinan puluhan AnimationControllers. Dengan Flutter biasa, saya dapat melakukan:

List<AnimationController> controllers = [];
int numAnimationControllers = 50;

<strong i="8">@override</strong>
void initState() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers.add(AnimationController(...));
}

<strong i="9">@override</strong>
void dispose() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers[i].dispose();
}

Tetapi dengan kait saya tidak dapat memanggil useAnimationController dalam satu lingkaran. Saya kira ini adalah contoh sepele tetapi saya tidak dapat menemukan solusi di mana pun untuk jenis kasus penggunaan ini.

Kait melakukannya secara berbeda.

Kami tidak membuat Daftar pengontrol lagi, melainkan kami memindahkan logika pengontrol ke item:

Widget build(context) {
  return ListView(
    children: [
      for (var i = 0; i < 50; i++)
        HookBuilder(
          builder: (context) {
            final controller = useAnimationController();
          },
        ),
    ],
  );
}

Kami masih membuat 50 pengontrol animasi kami, tetapi mereka dimiliki oleh widget yang berbeda.

Mungkin Anda dapat membagikan contoh mengapa Anda membutuhkannya, dan kami dapat mencoba dan mengonversi ke kait dan menambahkannya ke repo Tim?

Hixie bekerja untuk Google, dia harus sabar dan sopan dengan Anda dan tidak dapat menghubungi Anda karena kurangnya keterlibatan Anda. Yang terbaik yang bisa dia lakukan adalah mendorong lebih banyak contoh.

@Hixie , jika itu yang Anda rasakan, tolong katakan begitu (baik di sini atau hubungi saya secara pribadi).

Saya benar-benar memberikan contoh bagaimana Anda dapat mengurangi verbositas dan menghindari penumpukan pembangun menggunakan contoh yang diberikan Remi.

Terima kasih, tetapi tidak jelas bagi saya bagaimana Anda akan mengekstrak pola umum dari kode ini karena menerapkan logika ini ke kasus penggunaan yang berbeda.

Di OP, saya menyebutkan bahwa saat ini, kami memiliki 3 pilihan:

  • gunakan Pembangun dan memiliki kode bersarang
  • jangan memfaktorkan kode apa pun, yang tidak menskalakan ke logika keadaan yang lebih kompleks (saya berpendapat bahwa StreamBuilder dan AsyncSnapshot adalah logika keadaan yang kompleks).
  • coba dan buat beberapa arsitektur menggunakan mixins/oop/..., tetapi berakhir dengan solusi yang terlalu spesifik untuk masalah sehingga setiap kasus penggunaan yang sedikit _tiny_ berbeda akan memerlukan penulisan ulang.

Tampak bagi saya bahwa Anda menggunakan pilihan ke-3 (yang berada dalam kategori yang sama dengan iterasi awal dari proposal Property atau addDispose ).

Saya sebelumnya membuat kisi evaluasi untuk menilai polanya:

Bisakah Anda menjalankan varian Anda pada ini? Terutama komentar kedua tentang penerapan semua fitur StreamBuilder tanpa duplikat kode jika digunakan berkali-kali.

Rencana saya pada saat ini pada bug ini adalah:

  1. Ambil contoh dari https://github.com/flutter/flutter/issues/51752#issuecomment -675285066 dan buat aplikasi menggunakan Flutter murni yang menunjukkan berbagai kasus penggunaan tersebut bersama-sama.
  2. Cobalah untuk merancang solusi yang memungkinkan penggunaan kembali kode untuk contoh-contoh yang memenuhi berbagai persyaratan utama yang telah dibahas di sini dan yang sesuai dengan prinsip desain kami.

Jika ada yang ingin membantu dengan salah satu dari ini saya pasti senang untuk membantu. Saya tidak mungkin segera mencapai ini karena saya sedang mengerjakan transisi NNBD terlebih dahulu.

@rrousselGit Tentu, saya membuat aplikasi di mana banyak Widget dapat bergerak di sekitar layar (sebut saja mereka Box es), dan mereka harus dapat bergerak secara independen satu sama lain (jadi setidaknya harus ada satu AnimationController untuk setiap Box ). Inilah satu versi yang saya buat hanya dengan satu AnimationController yang dibagikan di antara beberapa Widget, tetapi di masa mendatang saya dapat menganimasikan setiap Widget secara independen, misalnya untuk melakukan Transformasi yang rumit seperti menerapkan CupertinoPicker , dengan efek roda gulir kustomnya .

Ada tiga kotak di Stack yang bergerak naik dan turun saat Anda mengklik FloatingActionButton.

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

void main(List<String> args) => runApp(const App());

class App extends HookWidget {
  const App({Key key});

  static const Duration duration = Duration(milliseconds: 500);
  static const Curve curve = Curves.easeOutBack;

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    final AnimationController controller =
        useAnimationController(duration: duration);
    final Animation<double> animation = Tween<double>(
      begin: 0,
      end: 300,
    )
        .chain(
          CurveTween(
            curve: curve,
          ),
        )
        .animate(controller);
    final ValueNotifier<bool> isDown = useState<bool>(false);
    final ValueNotifier<int> numBoxes = useState<int>(3);

    return MaterialApp(
      home: SafeArea(
        child: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              if (!isDown.value) {
                controller.forward();
                isDown.value = true;
              } else {
                controller.reverse();
                isDown.value = false;
              }
            },
          ),
          body: AnimatedBuilder(
            animation: animation,
            builder: (_, __) => Boxes(
              numBoxes: numBoxes.value,
              animation: animation,
            ),
          ),
        ),
      ),
    );
  }
}

class Boxes extends StatelessWidget {
  Boxes({
    <strong i="12">@required</strong> this.numBoxes,
    <strong i="13">@required</strong> this.animation,
  });

  final int numBoxes;
  final Animation<double> animation;

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Stack(
      children: List<Widget>.generate(
        numBoxes,
        (int index) => Positioned(
          top: (animation.value) + (index * (100 + 10)),
          left: (MediaQuery.of(context).size.width - 100) / 2,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
      ),
      // ],
    );
  }
}

Dalam hal ini, setiap kotak bergerak serempak, tetapi orang dapat membayangkan skenario yang lebih kompleks seperti membuat visualisasi untuk fungsi pengurutan misalnya, atau memindahkan elemen dalam daftar animasi, di mana widget induk mengetahui data tentang di mana setiap Box seharusnya dan harus dapat menganimasikan masing-masing sesuai keinginan.

Masalahnya tampaknya AnimationControllers dan Box es yang menggunakannya untuk menggerakkan gerakan mereka tidak berada di kelas yang sama, jadi seseorang harus melewati AnimationController dengan menyimpan array dari mereka untuk digunakan di Builder, atau minta masing-masing Box memelihara AnimationController-nya sendiri.

Dengan kait, mengingat Box es dan widget induk tidak berada di kelas yang sama, bagaimana saya membuat daftar AnimationControllers untuk kasus pertama di mana setiap Box dilewatkan dalam AnimationController? Ini sepertinya tidak diperlukan berdasarkan jawaban Anda di atas dengan HookBuilder, tetapi kemudian jika saya menurunkan status ke widget anak seperti yang Anda katakan, dan memilih untuk membuat setiap Box memiliki AnimationController sendiri melalui useAnimationController , saya mengalami masalah lain: bagaimana cara mengekspos AnimationController yang dibuat ke kelas induk agar dapat mengoordinasikan dan menjalankan animasi independen untuk setiap anak?

Di Vue Anda dapat memancarkan acara kembali ke induk melalui pola emit , jadi di Flutter apakah saya memerlukan beberapa solusi manajemen status yang lebih tinggi seperti Riverpod atau Rx di mana induk memperbarui status global dan anak mendengarkan global negara? Sepertinya saya tidak boleh, setidaknya untuk contoh sederhana seperti ini. Terima kasih telah menjernihkan kebingungan saya.

@satvikpendem Maaf saya tidak jelas. Bisakah Anda menunjukkan bagaimana Anda melakukannya tanpa kait, daripada masalah di mana Anda memblokir dengan kait?

Saya ingin memiliki pemahaman yang jelas tentang apa yang Anda coba lakukan daripada di mana Anda terjebak

Tapi sebagai tebakan cepat, saya pikir Anda mencari kurva Interval sebagai gantinya, dan memiliki pengontrol animasi tunggal.

@rrousselGit Tentu, ini dia

import 'package:flutter/material.dart';

void main(List<String> args) => runApp(const App());

class Animator {
  Animator({this.controller, this.animation});
  AnimationController controller;
  Animation<double> animation;
}

class App extends StatefulWidget {
  const App({Key key});

  static const Duration duration = Duration(milliseconds: 500);
  static const Curve curve = Curves.easeOutBack;

  <strong i="7">@override</strong>
  _AppState createState() => _AppState();
}

class _AppState extends State<App> with TickerProviderStateMixin {
  List<Animator> animators = [];
  bool isDown = false;
  int numBoxes = 3;

  <strong i="8">@override</strong>
  void initState() {
    for (int i = 0; i < numBoxes; i++) {
      final AnimationController c = AnimationController(
        duration: App.duration,
        vsync: this,
      );
      animators.add(
        Animator(
          controller: c,
          animation: Tween<double>(
            begin: 0,
            end: 300,
          )
              .chain(
                CurveTween(
                  curve: App.curve,
                ),
              )
              .animate(c),
        ),
      );
    }
    super.initState();
  }

  <strong i="9">@override</strong>
  void dispose() {
    for (int i = 0; i < numBoxes; i++) {
      animators[i].controller.dispose();
    }
    super.dispose();
  }

  <strong i="10">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SafeArea(
        child: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              if (!isDown) {
                for (final Animator animator in animators) {
                  animator.controller.forward();
                }
                setState(() {
                  isDown = true;
                });
              } else {
                for (final Animator animator in animators) {
                  animator.controller.reverse();
                }
                setState(() {
                  isDown = false;
                });
              }
            },
          ),
          body: Stack(
            children: List<Box>.generate(
              numBoxes,
              (int index) => Box(
                index: index,
                animation: animators[index].animation,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Box extends StatelessWidget {
  Box({
    <strong i="11">@required</strong> this.animation,
    <strong i="12">@required</strong> this.index,
  });

  final int index;
  final Animation<double> animation;

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return Positioned(
      top: (animation.value) + (index * (100 + 10)),
      left: (MediaQuery.of(context).size.width - 100) / 2,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.blue,
      ),
    );
  }
}

Saya sebenarnya menginginkan beberapa pengontrol animasi, satu untuk setiap widget, karena mereka dapat bergerak secara independen satu sama lain, dengan durasi, kurva, dll. Perhatikan bahwa kode di atas tampaknya memiliki bug yang tidak dapat saya ketahui, di mana itu harus bernyawa dengan bersih, tetapi pada dasarnya itu harus menghidupkan 3 kotak ke atas dan ke bawah dengan satu klik tombol. Kita bisa membayangkan skenario di mana alih-alih masing-masing memiliki kurva yang sama, saya memberi masing-masing kurva yang berbeda, atau saya membuat 100 kotak, masing-masing dengan durasi lebih lama atau lebih pendek dari yang sebelumnya, atau saya membuat yang genap naik dan yang aneh turun, dan seterusnya.

Dengan Flutter normal, initState dan dispose keduanya dapat memiliki loop tetapi tidak demikian halnya dengan kait, jadi saya hanya ingin tahu bagaimana seseorang dapat melawannya. Selain itu, saya tidak ingin menempatkan kelas Box di dalam widget induk, karena saya tidak ingin merangkum keduanya secara ketat; Saya harus dapat menjaga logika induk tetap sama tetapi menukar Box dengan Box2 misalnya.

Terima kasih!
Saya telah mendorong contoh Anda ke repo @TimWhiting , dengan kail yang setara

TL;DR, dengan kait (atau pembangun), kami berpikir secara deklaratif bukan imperatif. Jadi daripada memiliki daftar pengontrol di satu widget, lalu mengemudikannya secara imperatif – yang memindahkan pengontrol ke item dan mengimplementasikan animasi implisit.

Terima kasih @rrousselGit! Saya berjuang dengan jenis implementasi ini untuk beberapa saat setelah mulai menggunakan kait, tetapi saya sekarang mengerti cara kerjanya. Saya baru saja membuka PR untuk versi dengan target berbeda untuk setiap pengontrol animasi karena mungkin lebih menarik untuk memahami mengapa kait berguna seperti yang saya katakan di atas:

Kita bisa membayangkan skenario di mana alih-alih masing-masing memiliki kurva yang sama, saya memberi masing-masing kurva yang berbeda, atau saya membuat 100 kotak, masing-masing dengan durasi lebih lama atau lebih pendek dari yang sebelumnya, atau saya membuat yang genap naik dan yang aneh turun, dan seterusnya.

Saya telah mencoba membuat versi deklaratif tetapi saya kira yang tidak saya mengerti adalah metode siklus hidup didUpdateWidget/Hook , jadi saya tidak tahu cara menggerakkan animasi ketika prop anak diubah dari induknya, tetapi kode Anda membersihkannya.

Menemukan contoh dunia nyata di basis kode saya hari ini, jadi saya pikir sebaiknya saya membagikannya.

Jadi dalam skenario ini, saya bekerja dengan Firestore, dan memiliki beberapa boilerplate yang ingin saya lakukan dengan setiap StreamBuilder, jadi saya membuat pembuat kustom saya sendiri. Saya juga perlu bekerja dengan ValueListenableyang memungkinkan pengguna untuk memesan ulang daftar. Untuk alasan biaya moneter yang terkait dengan Firestore, ini memerlukan implementasi yang sangat spesifik (setiap item tidak dapat menyimpan pesanannya sendiri, sebaliknya daftar harus menyimpannya sebagai bidang id gabungan), ini karena biaya firestore untuk setiap penulisan, jadi Anda berpotensi dapat menghemat banyak uang dengan cara ini. Itu akhirnya membaca sesuatu seperti ini:

return ClipRRect(
      borderRadius: BorderRadius.circular(CornerStyles.dialog),
      child: Scaffold(
        backgroundColor: state.theme.scaffoldBackgroundColor,
        body: FamilyStreamBuilder<DocumentSnapshot>(
          stream: state.listRef.snapshots(),
          builder: (context, AsyncSnapshot<DocumentSnapshot> documentSnapshot) {
            //When a list is updated, we need to update the listOrder
            state.updateListOrderFromSnapshot(documentSnapshot);
            return FamilyStreamBuilder<QuerySnapshot>(
                stream: state.itemsCollection.snapshots(),
                builder: (_, AsyncSnapshot<QuerySnapshot> itemsSnapshot) {
                  //Sort the list items by the idField on the list-doc
                  List<DocumentSnapshot> items = itemsSnapshot.data.documents;
                  state.handleDocsSync(items);
                  return ValueListenableBuilder(
                    valueListenable: state.listOrderNotifier,
                    builder: (_, List<String> listOrder, __) {
                      List<DocumentSnapshot> ordered = state.sortItems(items, listOrder);
                     //Set the firstCompleted item if we have one
                      state.firstCompletedItem = ordered
                          ?.firstWhere((element) => element.data[FireParams.kIsComplete] == true, orElse: () => null)
                          ?.documentID;
                      return _buildItemList(items, ordered);
                    },
                  );
                });
          },
        ),
      ),
    );

Rasanya akan jauh lebih mudah untuk dipikirkan, jika saya bisa menulisnya lebih seperti:

    DocumentSnapshot list = useFamilyStream(state.listRef.snapshots());
    List<DocumentSnapshot> items = useFamilyStream(state.itemsCollection.snapshots()).data.documents;
    List<String> listOrder = useValueListenable(state.listOrderNotifier);

    //When a list is updated, we need to update the listOrder
    state.updateListOrderFromSnapshot(list);

   //Sort the list items by the idField on the list-doc
    state.handleDocsSync(items);
    List<DocumentSnapshot> ordered = state.sortItems(items, listOrder);

    //Set the firstCompleted item if we have one
    state.firstCompletedItem = ordered
        ?.firstWhere((element) => element.data[FireParams.kIsComplete] == true, orElse: () => null)
        ?.documentID;

    return ClipRRect(
      borderRadius: BorderRadius.circular(CornerStyles.dialog),
      child: Scaffold(
          backgroundColor: state.theme.scaffoldBackgroundColor,
          body: _buildItemList(items, ordered)
      ));

Ini memang kehilangan pengoptimalan terkait pembuatan ulang granular, tetapi itu tidak akan membuat IRL berbeda karena semua elemen visual berada di simpul daun paling bawah, semua pembungkus adalah keadaan murni.

Seperti banyak skenario dunia nyata, saran "Jangan gunakan X" tidak realistis, karena Firebase hanya memiliki satu metode koneksi yaitu Streams, dan setiap kali saya menginginkan perilaku seperti soket ini, saya tidak punya pilihan selain melakukannya menggunakan Aliran. C'est la vie.

Ini memang kehilangan pengoptimalan terkait pembuatan ulang granular, tetapi itu tidak akan membuat IRL berbeda karena semua elemen visual berada di simpul daun paling bawah, semua pembungkus adalah keadaan murni.

Itu masih membuat perbedaan. Apakah sebuah node visual atau tidak, tidak mempengaruhi apakah perlu biaya untuk membangun kembali.

Saya mungkin akan memasukkan contoh itu ke dalam widget yang berbeda (IDE memiliki alat refactoring sekali klik untuk membuatnya sangat mudah). _buildItemList mungkin harus berupa widget, seperti halnya bagian yang di-root pada FamilyStreamBuilder .

Kami tidak benar-benar kehilangan rekondisi granular.
Sebenarnya kait meningkatkan aspek itu, dengan memungkinkan untuk dengan mudah men-cache instance widget menggunakan useMemoized .

Ada beberapa contoh di repo Tim yang melakukan itu.

Saya mungkin akan memasukkan contoh itu ke dalam widget yang berbeda (IDE memiliki alat refactoring sekali klik untuk membuatnya sangat mudah). _buildItemList mungkin harus berupa widget, seperti halnya bagian yang di-root pada FamilyStreamBuilder .

Masalahnya, saya tidak benar-benar ingin melakukan ini, karena saya tidak memiliki masalah kinerja sama sekali dalam pandangan ini. Jadi 100% dari waktu saya akan menyukai lokalitas kode dan koherensi daripada optimasi mikro seperti ini. Tampilan ini hanya dibangun kembali ketika pengguna memulai tindakan (~ rata-rata sekali per 10 detik), atau ketika data backend berubah dan mereka menatap daftar terbuka (hampir tidak pernah terjadi). Ini juga merupakan tampilan sederhana yang terutama berupa daftar, dan daftar tersebut memiliki banyak pengoptimalan sendiri yang terjadi secara internal. Saya menyadari build() secara teknis dapat menyala kapan saja, tetapi dalam praktiknya setiap pembangunan kembali acak sangat jarang.

imo jauh lebih mudah untuk mengerjakan dan men-debug tampilan ini jika semua logika ini dikelompokkan dalam satu widget, terutama sebagai upaya untuk membuat hidup saya lebih mudah ketika saya kembali lagi nanti :)

Hal lain yang perlu diperhatikan adalah bahwa bersarang pada dasarnya "memaksa saya keluar" dari metode build, karena tidak mungkin saya bisa mulai membangun pohon saya di dalam 3 penutup dan 16 ruang di dalam lubang.

Dan ya, bisa dibilang masuk akal untuk kemudian pindah saja ke widget terpisah. Tetapi mengapa tidak tetap menggunakan metode build? Jika kita dapat mengurangi boilerplate menjadi apa yang sebenarnya _dibutuhkan_, maka tidak perlu memiliki keterbacaan dan kerumitan pemeliharaan untuk membagi hal-hal menjadi 2 file. (Dengan asumsi kinerja bukan masalah, yang seringkali tidak begitu)

Ingat dalam skenario ini saya sudah membuat widget khusus untuk menangani Pembuat Aliran saya. Sekarang saya perlu membuat yang lain untuk menangani komposisi pembangun ini?? Tampaknya sedikit di atas.

karena saya tidak memiliki masalah kinerja sama sekali dalam tampilan ini

Oh, saya tidak akan mengubahnya menjadi widget untuk kinerja, pembuatnya harus sudah mengurusnya. Saya akan refactor untuk keterbacaan dan penggunaan kembali. Saya tidak mengatakan itu cara yang "benar" untuk melakukannya, hanya mengatakan bagaimana saya akan menyusun kodenya. Lagi pula, itu tidak di sini atau di sana.

tidak mungkin saya bisa mulai membangun pohon saya di dalam 3 penutup dan 16 ruang di dalam lubang

Saya mungkin hanya memiliki monitor yang lebih lebar dari Anda... :-/

maka tidak perlu memiliki kerumitan keterbacaan dan pemeliharaan untuk membagi hal-hal di 2 file

Saya akan meletakkan widget di file yang sama, FWIW.

Bagaimanapun, ini adalah contoh yang bagus, dan saya yakin Anda lebih suka menggunakan satu widget dengan sintaks yang berbeda daripada menggunakan lebih banyak widget.

Saya mungkin hanya memiliki monitor yang lebih lebar dari Anda... :-/

Saya punya ultrawide :D, tapi dartfmt jelas membatasi kita semua sampai 80. Jadi kehilangan 16 itu signifikan. Masalah utama adalah akhir dari pernyataan saya adalah ini },);});},),),); tidak terlalu menyenangkan ketika sesuatu menjadi kacau. Saya harus sangat berhati-hati setiap kali saya mengedit hierarki ini, dan pembantu IDE umum seperti swap with parent berhenti bekerja.

Saya akan meletakkan widget di file yang sama, FWIW.

100%, tetapi saya masih menemukan bahwa melompat-lompat secara vertikal dalam satu file lebih sulit untuk dipertahankan. Tentu saja tidak dapat dihindari, tetapi kami mencoba untuk mengurangi jika memungkinkan dan 'menjaga semuanya tetap bersama'.

Yang terpenting, meskipun saya melakukan refactor daftar utama ke dalam widgetnya sendiri (yang saya setuju, lebih mudah dibaca daripada metode build bersarang), itu masih jauh lebih mudah dibaca tanpa bersarang di widget induk. Saya bisa masuk, memahami semua logika dengan cepat, melihat widget _MyListView() dengan jelas, dan langsung masuk ke dalamnya, yakin saya memahami konteks di sekitarnya. Saya juga dapat menambah/menghapus dependensi tambahan dengan relatif mudah, sehingga skalanya sangat baik.

dartfmt jelas membatasi kita semua hingga 80

Maksud saya, itulah salah satu alasan saya biasanya tidak menggunakan dartfmt, dan ketika saya melakukannya, saya menyetelnya menjadi 120 atau 180 karakter...

Pengalaman Anda di sini benar-benar valid.

Saya juga sebenarnya, 120 sepanjang hari :) Tapi pub.dev secara aktif menurunkan harga plugin yang tidak diformat pada 80, dan saya mendapat kesan bahwa saya (kami) berada di minoritas ketika kami mengubah nilai ini.

Itu tidak masuk akal, kita harus memperbaikinya.

pub.dev tidak menurunkan peringkat plugin yang tidak menghormati dartfmt. Itu hanya menampilkan komentar di halaman skor, tetapi skornya tidak terpengaruh
Tapi bisa dibilang, ada lebih banyak masalah dengan dartfmt daripada hanya panjang garis.

Panjang garis yang terlalu besar menyebabkan hal-hal yang lebih mudah dibaca di banyak baris menjadi satu baris, seperti:

object
  ..method()
  ..method2();

yang mungkin menjadi:

object..method()..method2();

Saya melihat ini?
image
Paket yang dimaksud: https://pub.dev/packages/sized_context/score

Menarik – sebelumnya tidak seperti itu, karena provider sudah lama tidak menggunakan dartfmt.
Saya berdiri dikoreksi.

Yup itu pasti perilaku baru, ketika saya awalnya menerbitkan musim semi lalu saya memastikan saya mencentang semua kotak, dan dartfmt tidak diperlukan.

Setelah semua diskusi ini, saya harap kita melihat dukungan asli untuk solusi seperti kait di flutter. baik useHook atau use Hook atau apa pun yang dapat dirasakan oleh tim flutter fitur mereka tidak seperti React 😁🤷‍♂️

kami menggunakan kait dengan cara seperti final controller = useAnimationController(duration: Duration(milliseconds: 800));
Bukankah lebih baik menggunakan fitur program baru Darts _Extension_ yang disalin dari kotlin/Swift ke sintaks yang indah itu?

sesuatu seperti: final controller = AnimationController.use(duration: Duration(milliseconds: 800));
dengan pendekatan ini, ketika tim flutter/dart memutuskan untuk menambahkan use Hook alih-alih sintaks yang tersedia saat ini useHook , saya pikir Annotation ke fungsi ekstensi itu membuatnya dibaca untuk digunakan sebagai
final controller = use AnimationController(duration: Duration(milliseconds: 800));

juga dapat dimengerti/bermakna untuk menggunakan kata kunci use seperti const dan new :
new Something
const Something
use Something

sebagai bonus untuk rekomendasi itu, saya akhirnya bahkan fungsi konstruktor/generator dapat menggunakan/mendapatkan manfaat dari Annotation yang diusulkan itu. kemudian dart compiler dengan beberapa penyesuaian mengubahnya untuk mendukung kata kunci use .

Fitur khusus yang sangat indah dan bergetar/panah

Apakah saya benar dalam mengasumsikan bahwa contoh di https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful sekarang mewakili masalah yang ingin diselesaikan orang?

Saya tidak yakin bagaimana perasaan orang lain, tetapi saya pikir masalahnya agak terwakili di sana (artinya saya tidak yakin karena seseorang mungkin menunjukkan sesuatu yang tidak terwakili).

Saya telah mencoba solusi jalan tengah di repositori itu. Ini dapat dikomposisi seperti kait, tetapi tidak tergantung pada pemesanan panggilan fungsi atau tidak memungkinkan untuk loop dll. Ini menggunakan StatefulWidgets secara langsung. Ini melibatkan mixin, serta properti stateful yang diidentifikasi secara unik oleh kunci. Saya tidak mencoba untuk mempromosikan ini sebagai solusi akhir, tetapi sebagai jalan tengah antara dua pendekatan.

Saya menyebutnya pendekatan lifecycleMixin, ini sangat mirip dengan pendekatan LateProperty yang telah dibahas di sini, tetapi perbedaan utamanya adalah ia memiliki lebih banyak siklus hidup yang diterapkan, dan dapat dengan mudah dibuat. (pada bagian siklus hidup, saya belum pernah menggunakan siklus hidup widget selain initState dan membuang banyak, jadi saya mungkin benar-benar kacau di sana).

Saya suka pendekatan ini karena:

  1. Ini memiliki hukuman runtime yang sangat kecil.
  2. Tidak ada logika/fungsi yang membuat atau mengelola status di jalur build (build bisa murni - hanya mengambil status).
  3. Manajemen siklus hidup lebih jelas saat mengoptimalkan pembangunan kembali melalui pembuat. (Tetapi Anda tidak mengorbankan penggunaan kembali dan komposisi bagian-bagian kecil negara).
  4. Karena Anda dapat menggunakan kembali pembuatan bit status, perpustakaan dapat dibuat dari bit status umum yang harus dibuat dan dibuang dengan cara tertentu, jadi ada lebih sedikit boilerplate dalam kode Anda sendiri.

Saya tidak suka pendekatan ini (dibandingkan dengan kait) karena alasan berikut:

  1. Saya tidak tahu apakah itu mencakup semua yang bisa dilakukan kait.
  2. Anda harus menggunakan kunci untuk mengidentifikasi properti secara unik. (Jadi, ketika menyusun potongan-potongan logika yang membangun beberapa keadaan, Anda harus menambahkan kunci untuk secara unik mengidentifikasi setiap bagian keadaan -- menjadikan kunci sebagai parameter posisi yang diperlukan membantu, tetapi saya ingin solusi tingkat bahasa untuk mengakses a id unik untuk variabel).
  3. Ini sangat menggunakan ekstensi untuk membuat fungsi yang dapat digunakan kembali untuk membuat bit status umum. Dan ekstensi tidak dapat diimpor secara otomatis oleh IDE.
  4. Anda dapat mengacaukan diri sendiri jika Anda mencampur siklus hidup widget yang berbeda / mengaksesnya di antara widget tanpa secara eksplisit mengelolanya dengan benar.
  5. Sintaks builder agak aneh sehingga status yang dibuat berada dalam cakupan fungsi build, tetapi membiarkan fungsi build murni.
  6. Saya belum menerapkan semua contoh, jadi mungkin ada kasus penggunaan yang tidak dapat saya bahas.

Contoh penghitung sederhana .
Contoh penghitung animasi

kerangka
bit umum dari logika penyusunan status yang dapat digunakan kembali

Saya tidak yakin berapa banyak waktu yang saya miliki, studi pascasarjana selalu membuat saya sibuk, tetapi saya ingin beberapa umpan balik. @rrousselGit Seberapa dekat ini dengan kait, dapatkah Anda melihat beberapa lubang yang jelas dalam penggunaan kembali atau komposisi?

Saya tidak mencoba untuk mempromosikan solusi saya, melainkan mendorong diskusi positif di jalan tengah. Jika kita bisa sepakat tentang apa yang hilang atau apa yang diberikan solusi ini kepada kita, saya pikir kita akan membuat kemajuan ke depan yang baik.

@TimWhiting Masalah utama yang saya miliki dengan pendekatan ini adalah kurangnya ketahanan. Penggerak besar di sini adalah kebutuhan akan keandalan pembangun, dalam bentuk yang ringkas. Id ajaib, dan kemampuan untuk berbenturan pada siklus hidup, keduanya membuat vektor baru untuk bug terjadi, dan saya akan terus merekomendasikan kepada tim saya mereka menggunakan pembangun, meskipun cukup buruk untuk dibaca, setidaknya kita tahu bahwa mereka 100 % bebas serangga.

Mengenai contoh, saya masih berpikir contoh sempurna hanya menggunakan AnimationController, dengan nilai durasi terikat pada widget. Tetap sederhana dan akrab. Tidak perlu menjadi lebih esoteris dari itu, ini adalah kasus penggunaan kecil yang sempurna untuk boilerplate yang dapat digunakan kembali, membutuhkan kait siklus hidup, dan semua solusi dapat dengan mudah dinilai berdasarkan kemampuan mereka untuk menggunakan beberapa animasi secara ringkas.

Yang lainnya hanyalah variasi dari use case 'Stateful Controller' yang sama ini. Saya ingin melakukan X di initState, dan Y dalam status buang, dan memperbarui Z ketika dependensi saya berubah. Tidak peduli apa X, Y dan Z.

Saya ingin tahu apakah @rrousselGit dapat memberikan beberapa wawasan di sini, atau memiliki data tentang kait mana yang paling banyak digunakan saat ini. Saya kira itu 80% Streaming dan Animasi, tetapi alangkah baiknya untuk benar-benar mengetahui apa yang paling banyak digunakan orang.

Mengenai membangun kembali bagian dari pohon, pembangun secara alami cocok untuk tugas ini, kita harus membiarkan mereka melakukannya. Pengontrol stateful dapat dengan mudah dihubungkan ke penyaji stateless jika itu yang Anda inginkan (halo setiap kelas Transisi).

Sama seperti yang mungkin kita lakukan:

var anim = get AnimationController();
return Column(
  _someExpensiveBuildMethod(),
  FadeTransition(opacity: anim, child: ...)
)

Kami selalu dapat melakukan:

var foo = get ComplicatedThingController();
return Column(
  _someExpensiveBuildMethod(),
  ComplicatedThing(controller: foo, child: ...)
)

@esDotDev Saya setuju, kunci, dan sintaks pembangun adalah kelemahan utama dari pendekatan lifecycleMixin. Saya tidak tahu apakah Anda bisa menyiasatinya kecuali dengan menggunakan pendekatan gaya kait dengan batasan terkaitnya, atau perubahan bahasa untuk dapat mengaitkan deklarasi variabel dengan bit status dengan siklus hidup. Inilah sebabnya saya akan terus menggunakan kait, dan membiarkan orang lain menggunakan widget stateful, kecuali ada solusi yang lebih baik. Namun, saya pikir ini adalah alternatif yang menarik bagi mereka yang tidak menyukai pembatasan kait, meskipun ia datang dengan pembatasannya sendiri.

Apakah saya benar dalam mengasumsikan bahwa contoh di https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful sekarang mewakili masalah yang ingin diselesaikan orang?

Sejujurnya saya tidak yakin.
Saya akan mengatakan _ya_. Tapi itu benar-benar tergantung pada bagaimana Anda akan menafsirkan contoh-contoh ini.

Di utas ini, kami memiliki sejarah tidak memahami satu sama lain, jadi saya tidak dapat menjamin ini tidak akan terjadi lagi.

Itu sebagian mengapa saya tidak suka menggunakan contoh kode dan menyarankan untuk mengekstrak seperangkat aturan sebagai gantinya.
Contohnya subjektif dan memiliki banyak solusi, beberapa di antaranya mungkin tidak menyelesaikan masalah yang lebih luas.

Saya ingin tahu apakah @rrousselGit dapat memberikan beberapa wawasan di sini, atau memiliki data tentang kait mana yang paling banyak digunakan saat ini. Saya kira itu 80% Streaming dan Animasi, tetapi alangkah baiknya untuk benar-benar mengetahui apa yang paling banyak digunakan orang.

Saya pikir itu sangat homogen.

Meskipun jika ada, useStream dan Animasi kemungkinan paling jarang digunakan:

  • useStream biasanya memiliki padanan yang lebih baik tergantung pada arsitektur Anda. Bisa menggunakan context.watch , useBloc , useProvider , ...
  • hanya sedikit orang yang meluangkan waktu untuk membuat animasi. Itu jarang menjadi prioritas, dan TweenAnimationBuilder widget animasi implisit lainnya mencakup sebagian besar kebutuhan.
    Mungkin itu akan berubah jika saya menambahkan kait useImplicitlyAnimatedInt di flutter_hooks.

@esDotDev Baru saja menghapus kebutuhan akan kunci/id dalam pendekatan lifecycleMixin. Masih agak canggung dalam sintaks builder. Tapi mungkin itu bisa membantu pada akhirnya juga. Satu-satunya masalah yang saya hadapi adalah dengan sistem tipe. Ia mencoba untuk melemparkan hal-hal dengan cara tertentu yang tidak bekerja. Tapi itu mungkin hanya membutuhkan beberapa casting hati-hati atau penguasaan sistem tipe. Sejauh mencampur siklus hidup, saya pikir itu dapat ditingkatkan dengan melemparkan beberapa pengecualian yang masuk akal ketika bagian tertentu dari status yang Anda coba akses tidak dapat diakses oleh siklus hidup widget itu. Atau lint yang dalam lifecyclebuilder Anda hanya boleh mengakses lifecycle builder.

Terima kasih Remi, itu mengejutkan saya, saya pikir orang akan sangat sering menggunakan Animasi untuk menggerakkan banyak koleksi widget Transisi di intinya, tetapi saya kira kebanyakan orang hanya menggunakan berbagai Implicit, karena cukup bagus untuk dibaca dan tidak memiliki sarang. .

Meskipun AnimatorController dilayani dengan sangat baik dengan rangkaian widget Implicit dan Explicit, saya masih berpikir ini adalah contoh yang bagus dari 'hal yang perlu mempertahankan status, dan mengikat ke params widget & siklus hidup`. Dan berfungsi sebagai contoh kecil yang sempurna dari masalah yang harus dipecahkan (faktanya adalah benar-benar diselesaikan di Flutter w/ seperti selusin widget meskipun), bahwa kita semua dapat mendiskusikan dan tetap fokus pada arsitektur dan bukan konten.

Misalnya, pertimbangkan bagaimana, jika var anim = AnimationController.use(context, duration: widget.duration ?? _duration); adalah warga negara kelas satu, hampir tidak ada animasi implisit atau eksplisit ini yang benar-benar perlu ada. Itu membuat mereka berlebihan karena semuanya dibuat untuk mengelola masalah inti: dengan mudah mengomposisi hal yang stateful (AnimationController) dalam konteks widget. TAB menjadi hampir tidak ada gunanya, karena Anda dapat melakukan hal yang sama dengan AnimatedBuilder + AnimatorController.use() .

Ini benar-benar menggambarkan perlunya kasus penggunaan umum jika Anda melihat banyak sekali widget yang bermunculan di sekitar animasi. Justru karena sangat rumit/rawan bug untuk menggunakan kembali logika pengaturan inti/penghancuran, kami memiliki 15+ widget yang semuanya menangani hal-hal yang sangat spesifik, tetapi sebagian besar masing-masing mengulangi boilerplate animasi yang sama hanya dengan beberapa baris kode yang unik dalam banyak kasus.

Ini berfungsi untuk menunjukkan, bahwa ya kita juga bisa melakukan hal ini untuk menggunakan kembali logika stateful kita sendiri: membuat widget untuk setiap permutasi penggunaan. Tapi apa yang merepotkan dan perawatannya sakit kepala! Jauh lebih baik untuk memiliki cara mudah untuk membuat objek stateful kecil, dengan kait lifceycle, dan jika kita ingin membuat widget khusus untuk rendering, atau pembangun yang dapat digunakan kembali, kita dapat dengan mudah melapisinya di atas.

Untuk apa nilainya, saya menggunakan sesuatu seperti useAnimation banyak di aplikasi saya daripada widget animasi normal. Ini karena saya menggunakan SpringAnimation yang tidak didukung dengan baik dengan widget seperti AnimatedContainer misalnya; mereka semua mengasumsikan animasi berbasis waktu, dengan curve dan duration daripada animasi berbasis simulasi, yang akan menerima argumen Simulation .

Saya membuat abstraksi lebih dari useAnimation tetapi dengan pegas, jadi saya menyebutnya useSpringAnimation . Widget pembungkus yang saya gunakan dengan kait ini mirip dengan AnimatedContainer tetapi jauh lebih mudah dibuat karena saya dapat menggunakan kembali semua kode animasi seperti yang Anda katakan @esDotDev , karena sebagian besar logikanya sama. Saya bahkan dapat membuat versi saya sendiri dari semua widget animasi dengan menggunakan useSpringAnimation tetapi saya tidak perlu melakukannya untuk proyek saya. Ini sekali lagi menunjukkan kekuatan penggunaan kembali logika siklus hidup yang disediakan oleh kait.

Misalnya, pertimbangkan bagaimana, jika var anim = AnimationController.use(context, duration: widget.duration ?? _duration); adalah warga negara kelas satu, hampir tidak ada animasi implisit atau eksplisit ini yang benar-benar perlu ada. Itu membuat mereka berlebihan karena semuanya dibuat untuk mengelola masalah inti: dengan mudah mengomposisi hal yang stateful (AnimationController) dalam konteks widget. TAB menjadi hampir tidak ada gunanya, karena Anda dapat melakukan hal yang sama dengan AnimatedBuilder + AnimatorController.use().

Membaca komentar saya di atas, ini pada dasarnya persis seperti yang saya lakukan dengan kait animasi pegas saya. Saya merangkum logika dan kemudian hanya menggunakan AnimatedBuilder. Untuk membuatnya implisit, sehingga ketika saya mengubah prop seperti yang dilakukan pada AnimatedContainer, itu akan bernyawa, saya baru saja menambahkan metode didUpdateWidget (disebut didUpdateHook dalam flutter_hooks ) ke jalankan animasi dari nilai lama ke nilai baru.

Apakah saya benar dalam mengasumsikan bahwa contoh di https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful sekarang mewakili masalah yang ingin diselesaikan orang?

Sejujurnya saya tidak yakin.
Saya akan mengatakan _ya_. Tapi itu benar-benar tergantung pada bagaimana Anda akan menafsirkan contoh-contoh ini.

Di utas ini, kami memiliki sejarah tidak memahami satu sama lain, jadi saya tidak dapat menjamin ini tidak akan terjadi lagi.

Itu sebagian mengapa saya tidak suka menggunakan contoh kode dan menyarankan untuk mengekstrak seperangkat aturan sebagai gantinya.
Contohnya subjektif dan memiliki banyak solusi, beberapa di antaranya mungkin tidak menyelesaikan masalah yang lebih luas.

Saya juga akan mengatakan bahwa kita harus memasukkan semua contoh kode dalam masalah yang telah dibahas ini, saya pikir ada daftar di atas suatu tempat yang dibuat oleh @rrousselGit . Saya bisa membuat PR menambahkannya ke repositori local_state tetapi tidak semuanya contoh kode lengkap sehingga mungkin tidak semuanya benar-benar dikompilasi dan dijalankan. Tapi setidaknya mereka menunjukkan potensi masalah.

Saya bisa membuat PR menambahkannya ke repositori local_state

Itu akan sangat berguna.

Saya ingin menunjukkan bahwa utas ini belum mendefinisikan penggunaan kembali atau seperti apa penggunaan kembali. Saya pikir kita harus sangat spesifik dalam mendefinisikan itu, jangan sampai percakapan kehilangan fokus.

Kami hanya menunjukkan apa yang _tidak_ digunakan kembali karena berkaitan dengan Flutter.

Ada beberapa contoh penggunaan, dan kait dengan jelas memberikan contoh total penggunaan kembali status widget. Saya tidak yakin dari mana kebingungan itu berasal karena kelihatannya langsung di wajahnya.

Penggunaan kembali secara sederhana dapat didefinisikan sebagai: _Apa pun yang dapat dilakukan oleh widget pembangun._

Permintaannya adalah untuk beberapa objek stateful yang bisa ada di dalam widget apa pun, yang:

  • Meringkas statusnya sendiri
  • Dapat mengatur/meruntuhkan sendiri sesuai dengan initState/membuang panggilan
  • Dapat bereaksi ketika ketergantungan berubah di widget

Dan melakukannya dengan cara yang ringkas dan mudah disiapkan, tanpa boilerplate, seperti:
AnimationController anim = AnimationController.stateful(duration: widget.duration);
Jika ini berfungsi di widget Stateless dan Stateful. Jika dibangun kembali ketika widget.something berubah, jika dapat menjalankan init() dan buang() sendiri, maka pada dasarnya Anda memiliki pemenang dan saya yakin semua orang akan menghargainya.

Hal utama yang saya perjuangkan adalah bagaimana melakukan ini dengan cara yang efisien. Misalnya, ValueListenableBuilder mengambil argumen turunan yang dapat digunakan untuk meningkatkan kinerja secara terukur. Saya tidak melihat cara untuk melakukannya dengan pendekatan Properti.

Saya cukup yakin ini bukan masalah. Kami akan melakukan ini dengan cara yang sama seperti widget XTransition bekerja sekarang. Jika saya memiliki beberapa keadaan yang kompleks, dan saya ingin memiliki anak yang mahal, saya hanya akan membuat Widget pembungkus kecil untuk itu. Sama seperti yang mungkin kita buat:
FadeTransition(opacity: anim, child: someChild)

Kita dapat dengan mudah melakukannya dengan hal apa pun yang ingin kita render, dengan meneruskan 'benda' itu ke dalam Widget untuk merender ulang.
MyThingRenderer(value: thing, child: someChild)

  • Ini tidak _require_ bersarang seperti builder, tetapi opsional mendukungnya (.child bisa menjadi build fxn)
  • Ini mempertahankan kemampuan untuk digunakan secara langsung tanpa widget pembungkus
  • Kami selalu dapat membuat builder dan menggunakan sintaks ini di dalam builder agar tetap bersih. Ini juga membuka pintu ke beberapa jenis pembangun, dibangun di sekitar objek inti yang sama, yang tidak melibatkan kode tempel di semua tempat.

Setuju dengan @esDotDev. Seperti yang saya sebutkan sebelumnya, judul alternatif untuk ini adalah "Sintaks gula untuk Pembangun".

Hal utama yang saya perjuangkan adalah bagaimana melakukan ini dengan cara yang efisien. Misalnya, ValueListenableBuilder mengambil argumen turunan yang dapat digunakan untuk meningkatkan kinerja secara terukur. Saya tidak melihat cara untuk melakukannya dengan pendekatan Properti.

Saya cukup yakin ini bukan masalah. Kami akan melakukan ini dengan cara yang sama seperti widget XTransition bekerja sekarang. Jika saya memiliki beberapa keadaan yang kompleks, dan saya ingin memiliki anak yang mahal, saya hanya akan membuat Widget pembungkus kecil untuk itu. Sama seperti yang mungkin kita buat:

Tidak perlu untuk itu.
Salah satu keunggulan fitur ini adalah, kita dapat memiliki state-logic yaitu "cache instance widget jika parameternya tidak berubah".

Dengan kait, itu akan menjadi useMemo di Bereaksi:

<insert whatever>
final myWidget = useMemo(() => MyWidget(pameter: value), [value]);

Dengan kode ini, myWidget akan membangun kembali _only_ ketika value berubah. Bahkan jika widget yang memanggil useMemo dibangun kembali karena alasan lain.

Itu mirip dengan konstruktor const untuk widget, tetapi memungkinkan parameter dinamis.

Ada contoh melakukan itu di repo Tim.

Permintaannya adalah untuk beberapa objek stateful yang bisa ada di dalam widget apa pun, yang:

  • Meringkas statusnya sendiri
  • Dapat mengatur/meruntuhkan sendiri sesuai dengan initState/membuang panggilan
  • Dapat bereaksi ketika ketergantungan berubah di widget

Saya kira saya kesulitan melihat mengapa dengan parameter itu, StatefulWidget tidak melakukan pekerjaan lebih baik daripada itu. Itulah sebabnya saya mengajukan pertanyaan tentang apa yang sebenarnya kita cari di sini dalam sebuah solusi. Sebagai seseorang yang menggunakan flutter_hooks saya menemukan mereka lebih menyenangkan untuk bekerja dengan dari StatefulWidget , tapi itu hanya untuk menghindari verbositas-- bukan karena saya pikir dalam hal kait. Saya sebenarnya menemukan alasan tentang pembaruan UI sulit dengan kait dibandingkan dengan Widget s.

  • Dapat bereaksi ketika ketergantungan berubah di widget

Maksud Anda ketergantungan yang dibuat/diperoleh di dalam widget? Atau ketergantungan jauh di bawah widget di pohon?

Saya tidak menyangkal bahwa ada masalah yang menyebabkan verbositas/kebingungan di Flutter, saya hanya ragu untuk mengandalkan semua orang yang benar-benar memiliki model mental yang sama tentang apa itu "penggunaan kembali". Saya sangat berterima kasih atas penjelasannya; dan ketika orang memiliki model yang berbeda, mereka menciptakan solusi yang berbeda.

Karena menggunakan SW untuk melakukan ini baik-baik saja untuk kasus penggunaan tertentu, tetapi tidak baik untuk mengabstraksi logika yang dapat digunakan kembali dari kasus penggunaan di banyak SW. Ambil setup/teardown untuk Animation sebagai contoh. Ini bukan SW itu sendiri, ini adalah sesuatu yang ingin kami gunakan di antara mereka. Tanpa dukungan kelas satu untuk berbagi status yang dienkapsulasi, Anda akhirnya harus membuat pembuat, yaitu TweenAnimationBuilder, atau membuat satu ton Widget tertentu, yaitu AnimatedContainer dll. Benar-benar jauh lebih elegan jika Anda dapat menggabungkan logika itu dan menggunakan kembali itu cara apapun yang Anda inginkan di dalam pohon.

Dalam hal ketergantungan Widget, maksud saya jika widget.foo berubah, stateful-thing mendapat kesempatan untuk melakukan pembaruan yang perlu dilakukan. Dalam kasus stateful AnimationController, itu akan memeriksa apakah durasi berubah, dan jika ya, perbarui instance AnimatorController internalnya. Ini menyelamatkan setiap pelaksana Animasi dari keharusan menangani perubahan properti.

<insert whatever>
final myWidget = useMemo(() => MyWidget(pameter: value), [value]);

Dengan kode ini, myWidget akan membangun kembali _only_ ketika value berubah. Bahkan jika widget yang memanggil useMemo dibangun kembali karena alasan lain.

Ah, begitu, Memoized mengembalikan Widget itu sendiri, dan kemudian Anda memasukkan [nilai] sebagai pemicu pembangunan kembali, rapi!

Kunci tentang AnimatedOpacity bukanlah orang tua atau anak yang membangun kembali. Faktanya, ketika Anda memicu animasi menggunakan AnimatedOpacity secara harfiah tidak ada yang dibangun kembali setelah frame pertama tempat Anda memicu animasi. Kami melewatkan fase build sepenuhnya dan melakukan semuanya di objek render (dan di pohon render, itu hanya mengecat ulang, bukan relayout, dan sebenarnya menggunakan Layer sehingga bahkan catnya sangat minim). Itu membuat perbedaan yang signifikan pada kinerja dan penggunaan baterai. Solusi apa pun yang kami dapatkan di sini harus dapat mempertahankan kinerja semacam itu jika kami ingin membangunnya ke dalam kerangka kerja inti.

Sayangnya saya belum punya waktu untuk menyusun contoh dalam masalah ini ke dalam repo negara bagian lokal, saya salah. Saya mungkin tidak dapat mencapainya dalam waktu dekat jadi jika ada orang lain yang ingin mengambilnya, saya akan baik-baik saja dengan itu.

Berkenaan dengan kinerja memiliki kait yang ditentukan di dalam metode build/render (yang saya pikir seseorang disebutkan sebelumnya dalam masalah ini), saya membaca dokumen React dan melihat FAQ ini, mungkin berguna. Pada dasarnya ia menanyakan apakah kait lambat karena membuat fungsi di setiap render, dan mereka mengatakan tidak karena beberapa alasan, salah satunya adalah dapat memoize fungsi menggunakan kait seperti useMemo atau useCallback .

https://reactjs.org/docs/hooks-faq.html#are -hooks-slow-because-of-creating-functions-in-render

Pada dasarnya ia menanyakan apakah kait lambat karena membuat fungsi di setiap render, dan mereka mengatakan tidak karena beberapa alasan, salah satunya adalah dapat memoize fungsi menggunakan kait seperti useMemo atau useCallback .

Kekhawatirannya bukan pada biaya pembuatan penutupan, itu memang relatif murah. Perbedaan antara menjalankan kode apa pun sama sekali dan tidak menjalankan kode apa pun adalah kunci kinerja yang ditunjukkan Flutter dalam kasus optimal saat ini. Kami telah menghabiskan banyak upaya untuk membuat algoritme yang benar-benar menghindari menjalankan jalur kode tertentu sama sekali (misalnya fase pembuatan dilewati sepenuhnya untuk AnimatedOpacity, atau cara kami menghindari berjalan di atas pohon untuk melakukan pembaruan tetapi hanya menargetkan node yang terpengaruh).

Saya setuju. Saya tidak terlalu berpengalaman tentang internal Flutter atau internal hook tetapi Anda benar bahwa hook perlu (jika belum) mencari tahu kapan mereka harus berjalan vs tidak, dan kinerja tidak boleh mundur.

Perbedaan antara menjalankan kode apa pun sama sekali dan tidak menjalankan kode apa pun adalah kunci kinerja yang ditunjukkan Flutter dalam kasus optimal saat ini

Seperti disebutkan sebelumnya beberapa kali, kait meningkatkan itu.
Contoh animasi pada repo Tim adalah buktinya. Varian kait lebih jarang dibangun kembali daripada varian StatefulWidget berkat useMemo

Karena sedang dibahas tentang solusi untuk masalah ini di suatu tempat di utas ini, saya juga melabelinya sebagai proposal.

Saya benar-benar ingin melihat kait dimasukkan ke dalam flutter seperti yang dilakukan dengan reaksi. Saya melihat keadaan dalam flutter dengan cara yang sama seperti ketika saya pertama kali menggunakan reaksi. Sejak menggunakan kait, saya pribadi tidak akan pernah kembali.

Ini jauh lebih mudah dibaca IMO. Saat ini Anda harus mendeklarasikan dua kelas dengan widget stateful versus kait di mana Anda baru saja memasukkan usestate.

Ini juga akan membawa beberapa keakraban pada flutter yang sering kali tidak dimiliki oleh pengembang reaksi ketika mereka melihat kode flutter. Jelas membandingkan flutter dengan reaksi adalah jalan yang berbahaya untuk dilalui, tetapi saya benar-benar berpikir pengalaman pengembang saya dengan kait lebih baik daripada pengalaman saya tanpanya.

Saya tidak membenci flutter btw, ini sebenarnya kerangka kerja favorit saya, tetapi saya pikir ini adalah kesempatan yang sangat bagus untuk meningkatkan keterbacaan dan pengalaman dev.

Saya pikir pasti ada peluang untuk meningkatkan konvensi penamaan dan membuatnya lebih bergetar.

Hal-hal seperti UseMemoized dan UseEffect terdengar cukup asing, dan sepertinya kami ingin beberapa cara untuk tidak harus menjalankan kode init() di build fxn.

Saat ini menginisialisasi dengan kait seperti ini (saya pikir?):

Widget build(){
   useEffect(
      (){
          // Do init stuff
         return (){  //Do dispose stuff };
      }, [ ] ) //<-- pass an empty list of rebuild triggers, so this can only fire once. Passing null here would let it fire every time.
   );
}

Saya menghargai singkatnya kode ini, tetapi tentu saja jauh kurang ideal dari sudut pandang keterbacaan dan "kode dokumentasi diri". Ada banyak keajaiban implisit yang terjadi di sini. Idealnya kami memiliki sesuatu yang eksplisit tentang kait init/buang, dan tidak memaksakan dirinya untuk membangun saat digunakan dengan Widget Tanpa Kewarganegaraan.

Hal-hal seperti useMemoized dan useEffect mungkin bisa lebih baik dinamai lebih eksplisit hook ComputedValue() dan hook SideEffect()

Widget build(BuildContext context){
   List<int> volumes = hook ComputedValue(
        execute: ()=>_getVolumeFromAudioSamples(widget.bytes), 
        dependencies: [ widget.bytes ]);

   hook SideEffect(
       execute: ()=>_recordSongChangedAnalytics()
       dependencies: [ widget.songUrl ]);
   )

   return SongVisualizer(volumes: volumes);
}

Saya suka itu, tidak yakin bagaimana perasaan saya tentang penggunaan kata kunci hook , dan saya rasa itu tidak menyelesaikan masalah konsep asing. Memperkenalkan kata kunci baru tidak terasa seperti pendekatan terbaik dalam pikiran saya, withSideEffect atau withComputedValue ? Saya bukan perancang bahasa jadi kata-kata saya tidak berguna.

Saya merasa fungsionalitas seperti kait di flutter akan sangat membantu dalam memperlancar kurva pembelajaran untuk pengembang React, yang benar-benar merupakan target audiens ketika perusahaan membuat keputusan antara ReactNative dan Flutter.

Menggemakan @lemusthelroy , Flutter sejauh ini adalah kerangka kerja favorit saya dan saya sangat bersemangat untuk melihat arah yang diperlukan. Tapi saya merasa konsep pemrograman fungsional bisa sangat membantu dalam menumbuhkan kerangka kerja ke arah yang belum dijelajahi. Saya pikir beberapa orang menolak gagasan itu dengan tujuan untuk menjauhkan diri dari React, yang sangat disayangkan, tetapi dapat dimengerti.

Ya, saya pikir ada dua sisi dari koin itu. Kata kunci baru adalah peristiwa besar, jadi penyebaran pengetahuan akan sangat cepat, tetapi sisi lain tentu saja sekarang sesuatu yang baru bagi _semua orang_. Jika mungkin tanpa itu juga keren! Hanya tidak yakin itu... setidaknya tidak elegan.

Opini: Kecenderungan masyarakat untuk menyebut kait sebagai solusi de-facto untuk masalah ini berakar dari bias fungsi. Fungsi lebih sederhana untuk ditulis daripada objek, terutama dalam bahasa yang diketik secara statis. Saya pikir model mental Widget untuk banyak pengembang secara efektif hanyalah metode build .

Saya pikir jika Anda membingkai masalah dalam hal dasar-dasarnya, Anda cenderung merancang solusi yang bekerja dengan baik di perpustakaan lainnya.

Adapun kata kunci hook dalam hal dasar-dasar; orang dapat melihatnya sebagai mendeklarasikan dan mendefinisikan fungsi dari beberapa jenis templat (makro), dan awalan hook benar-benar hanya memanggil bahwa fungsi bawaan memiliki status internal (statis gaya-c. )

Saya ingin tahu apakah tidak ada semacam seni sebelumnya di Swift FunctionBuilders.

Saat kita sedang bermimpi, saya akan mengklarifikasi tebakan saya tentang apa yang akan menjadi kode yang diperlukan:

Hook SideEffect(void Function() execute, List<Object> dependencies) {
  // Whatever happens each build.
}

Widget build(BuildContext context){
   List<int> volumes = hook ComputedValue(
        execute: ()=>_getVolumeFromAudioSamples(widget.bytes), 
        dependencies: [ widget.bytes ]);

   SideEffect(
       execute: ()=>_recordSongChangedAnalytics()
       dependencies: [ widget.songUrl ]);
   )

   return SongVisualizer(volumes: volumes);
}

Di mana Hook adalah peretasan tingkat sistem tipe yang membantu menganalisis secara statis bahwa kait yang dihasilkan dipanggil sesuai dengan apa yang dikenal pengembang kait sebagai hukum kait. Sebagai hal semacam itu, tipe Hook dapat didokumentasikan sebagai sesuatu yang sangat mirip dengan fungsi, tetapi memiliki keadaan internal yang dapat berubah statis.

Saya sedikit ngeri saat menulis ini karena ini sangat aneh dari sudut pandang bahasa. Kemudian lagi, Dart adalah bahasa yang lahir untuk menulis antarmuka pengguna. Jika keanehan semacam ini harus ada di mana saja, mungkin ini tempatnya. Tidak hanya keanehan ini pada khususnya.

Opini: Kecenderungan masyarakat untuk menyebut kait sebagai solusi de-facto untuk masalah ini berakar dari bias fungsi. Fungsi lebih sederhana untuk ditulis daripada objek, terutama dalam bahasa yang diketik secara statis. Saya pikir model mental Widget untuk banyak pengembang secara efektif hanyalah metode pembuatan.

Saya tidak yakin apa yang ingin Anda katakan dengan itu. Pendekatan kait yang juga saya gunakan dengan get_it_mixin saya hanya membuat pohon widget lebih mudah dibaca daripada menggunakan Builder.

Artikel menarik tentang React hooks

@nt4f04uNd Semua poin Anda telah dibahas sebelumnya, termasuk kinerja, mengapa itu perlu menjadi fitur inti, widget gaya fungsional vs kelas, dan mengapa hal-hal selain kait tampaknya tidak berfungsi. Saya sarankan Anda membaca seluruh percakapan untuk memahami berbagai poin.

Saya sarankan Anda membaca seluruh percakapan untuk memahami berbagai poin.

Ini adil untuk dikatakan mengingat mereka tidak membaca seluruh utas, tetapi saya tidak yakin itu membuat segalanya lebih jelas untuk membaca sisa utas. Ada orang-orang yang memprioritaskan untuk menjaga Widget apa adanya, dan kelompok lain yang ingin melakukan sesuatu yang lain sepenuhnya atau membuat Widget lebih modular.

Meskipun itu mungkin benar, masalah ini menunjukkan bahwa ada masalah yang tidak dapat diselesaikan dengan widget seperti saat ini, jadi jika kita ingin menyelesaikan masalah, kita tidak punya pilihan selain membuat sesuatu yang baru. Ini adalah konsep yang sama dengan memiliki Future s dan kemudian memperkenalkan sintaks async/await , yang terakhir memungkinkan hal-hal yang tidak mungkin terjadi tanpa sintaks baru.

Orang _are_ menyarankan agar kami menjadikannya bagian dari kerangka kerja. React tidak dapat menambahkan sintaks baru ke Javascript karena itu bukan satu-satunya kerangka kerja yang tersedia (baik, itu bisa melalui transformasi Babel), tetapi Dart dirancang khusus untuk bekerja dengan Flutter (setidaknya Dart 2, bukan versi aslinya) jadi kami memiliki lebih banyak kemampuan untuk membuat kait bekerja sama dengan bahasa yang mendasarinya. React, misalnya, membutuhkan Babel untuk JSX, dan harus menggunakan linter untuk kesalahan useEffect , sementara kita bisa membuatnya menjadi kesalahan waktu kompilasi. Memiliki sebuah paket membuat adopsi menjadi jauh lebih sulit, karena Anda dapat membayangkan daya tarik yang tidak akan didapatkan oleh kait React jika itu adalah paket pihak ketiga.

Tidak akan ada masalah jika ada jenis widget ketiga, yaitu HookWidget, selain widget Stateless dan Stateful saat ini. Biarkan komunitas memutuskan mana yang akan digunakan. Sudah ada paket dari Remi tapi pasti ada batasannya. Saya mencobanya dan secara signifikan mengurangi boilerplate tetapi saya harus menjatuhkannya karena keterbatasan. Saya harus membuat widget stateful hanya untuk menggunakan metode init. Mungkin ada manfaat besar tambahan jika itu adalah bagian dari kerangka inti dengan dukungan bahasa. Selain itu, HookWidget dapat memungkinkan komunitas untuk membuat aplikasi yang lebih optimal dan berkinerja lebih baik.

Saya harus membuat widget stateful hanya untuk menggunakan metode init.

Anda sebenarnya tidak harus melakukan ini, useEffect() mampu melakukan initCall di dalam build. Dokumen tidak berusaha sama sekali untuk menjelaskan hal ini, dan pada dasarnya menganggap Anda adalah pengembang Bereaksi yang sudah tahu cara kerja kait.

Saya menggunakan cara itu tetapi saya memiliki beberapa masalah lain dengan batasan paket dan saya tidak ingat persis apa itu.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat