Kaitou kid code

Khi siêu đạo chích đi code dạo :)

Flutter_State

Bài viết sưu tầm

1. Nhắc lại về State

Xin nhắc lại phần kiến thức quan trọng nhất về State: Thay vì thực hiện cập nhật nội dung một giao diện (UI)View bằng cách gọi hàm trực tiếp từ chính (UI)View đó, với State, bạn thay đổi trực tiếp phần nội dung luôn. Chẳng hạn, để thay đổi phần text của một (UI)TextView, bạn gọi hàm setText (.text setter) từ (UI)TextView đó, còn với Flutter Text(data), bạn chỉ cần nhẹ nhàng thay đổi data trong setState mà thôi.

Quan trọng hơn hết, tất cả những thay đổi phải nằm trong setState. Chẳng hạn, setState(() => data = “New Text”). Nếu bạn chỉ gán data = “New Text” không nằm trong setState, sẽ không có gì xảy ra trên giao diện cả. Hàm setState có nhiệm vụ thông báo với lớp giao diện là cần phải render lại (ở những nơi nào cần). Hôm trước, có một bạn muốn đổi trang của PageView với các nút nhấn, bạn tăng/giảm trực tiếp PageView.controller.initialPage và kết quả là không có gì xảy ra cả. Dĩ nhiên, bạn ấy mang đi hỏi khắp chợ xem ai có giúp đỡ cho bạn được không. Ở đây, tôi chỉ ra hai cái sai của bạn, mà xuất phát điểm có lẽ là do bạn lười đọc hướng dẫn:

  • PageController.initialPage đúng như tên gọi của nó, là trang mặc định khi PageView được khởi tạo. Chi tiết về nó, xin bạn tham khảo bài viết về PageController. Điều đáng nói ở đây, là giá trị này chỉ sử dụng một lần duy nhất khi PageView được khởi tạo mà thôi. Về sau, bạn có tăng giảm initialPage cả trăm lần thì PageController và PageView cũng có gì thay đổi, vì có ai truy cập tới nó nữa đâu.
  • Giả sử bạn có thể cuộn trang với initialPage, thì để giao diện (tức Widget tree) cập nhật lại, bao gồm thao tác chuyển trang, thì bạn phải thực hiện tăng giảm trong setState. Rõ ràng, PageController không có State và bản thân nó cũng không phải là Widget. Vậy, đào đâu ra setState để bạn gọi?

Tóm lại, nếu bạn muốn học và làm việc với Flutter, State là phần lí thuyết bạn không thể không hiểu. Vì vậy, bạn vui lòng học lại lí thuyết nếu cảm thấy mình chưa hiểu rõ về nó.

2. StatelessWidget và StatefulWidget

Cái tên nói lên tất cả. StatelessWidget là Widget không có State, còn StatefulWidget là Widget có State. Điểm chung của hai Widget này, là bản thân chúng có hàm build(BuildContext) return Widget. Như vậy, bản thân StatelessWidget và StatefulWidget có vai trò chứa đựng tập hợp các Widgets khác bên trong, và dĩ nhiên chúng được tổ chức theo hướng Widget tree. Vai trò này thể hiện rõ nhất với StatelessWidget. Chẳng hạn, bạn có một tổ hợp Widget khá phức tạp như sau, và cần được tái sử dụng nhiều lần:

12345678910111213141516Container(     child: Column(          children: <Widget>[                     Text(“Sample text”, style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),                     Text(“Subtitle”),                     Container(…),                     ListView(                          children: <Widget>[                               Row(children: <Widget>[ Text(“Another text”), Container(…) ],                               PageView(…),                               Align(…)                          ]                     ),          ],     ),),

Và mỗi lần sử dụng, bạn sẽ phải mất công cóp pát lại. Ngoài ra, có thể một số thành phần trong một Widget con có thể thay đổi, chẳng hạn như TextStyle bên trên, có thể có trường hợp màu chữ phải là màu xanh lá, xanh dương hay vàng, và mỗi lần như vậy là mỗi lần vô cùng vất vả. Để đơn giản hóa, bạn có thể cho chúng vào một StatelessWidget, với phần Widget tree phức tạp trên nằm trong build(BuildContext) của StatelessWidget vừa tạo là được. Do đó, StatefulWidget và StatelessWidget có thể nằm bất kì nơi nào trong Widget tree của bạn, không nhất thiết phải là một “màn hình” mới.

Với StatefulWidget thì mọi chuyện hơi khác một chút. Bản thân StatefulWidget không có hàm build như trên, mà hàm build lại nằm trong một <StateImpl extends State<W extends StatefulWidget>>, chẳng hạn:

1234567891011121314151617// StatelessWidgetclass SampleStatelessWidget extends StatelessWidget {     @override     Widget build(BuildContext)}  // StatefulWidgetclass SampleStatefulWidget extends StatefulWidget {     @override     SampleStatefulWidgetState createState() => new SampleStatefulWidgetState();} class SampleStatefulWidgetState extends State<SampleStatefulWidget> {     @override     Widget build(BuildContext)}

Vì sao lại cần phải tạo riêng một class State như vậy? Vì StatefulWidget đóng vai trò “giữ chỗ”, và nội dung của nó do phần State này đảm nhận. Khi cần render lại nội dung, được gọi từ State.setState(VoidCallback), thì phần State sẽ báo cho StatefulWidget những nơi nào cần được thay đổi, và StatefulWidget chỉ thay đổi ở những nơi đó mà thôi.

Vậy, khi nào dùng StatelessWidget và khi nào dùng StatefulWidget? Câu hỏi này khiến nhiều bạn với bắt tay vào Flutter mà không có kinh nghiệm với React và React Native khá khó trả lời. Trong bài trước, tôi có giải thích qua, nhưng có một số bạn vẫn chưa hiểu được ý, nên lần này tôi sẽ nói cụ thể hơn một chút. Và trước khi đọc tiếp, xin mời bạn “ghi lòng tạc dạ” là muốn cập nhật UI thì cần phải gọi hàm State.setState(VoidCallback), tức là nằm trong một class extends State.

Bạn chỉ sử dụng StatefulWidget khi một Widget trong Widget tree (sẽ được nằm ở State.build(BuildContext)) có phát ra (emit) một event để yêu cầu thay đổi giá trị của một biến. Và bao giờ cũng vậy, event đó phải gọi tới State.setState để thông báo cho class State là cần render lại nội dung, và từ đó StatefulWidget sẽ thay đổi. Còn trong những trường hợp không có event, thì bạn dùng StatelessWidget. Chẳng hạn, tôi có một Widget tree như sau:

1234567891011var data = “Text”; Widget build(BuildContext) => new Column(     children: <Widget>[          new Text(data),          /* new RaisedButton(               child: Text(“Toggle data”),               onPressed: () { toggleText(); },          ), */     ],),

Nếu bạn không có ý định thay đổi data từ “Text” sang một chuỗi khác thì bạn có thể đặt Widget tree trên vào một StatelessWidget. Tuy nhiên, nếu bạn muốn thay đổi giá trị của data và buộc Text(data) phải render lại, tức chuyển sang hiển thị nội dung data mới, thì bạn bắt buộc phải đặt Widget tree trên vào một State của một StatefulWidget. Chẳng hạn, tôi bỏ cặp dấu comment của RaisedButton, và định nghĩa hàm toggleText như sau:

1234567891011121314151617// Nội dung bên dưới thuộc một class WidgetState extends State<W extends StatefulWidget> var data = “Text”; Widget build(BuildContext) => new Column(     children: <Widget>[          new Text(data),          new RaisedButton(               child: Text(“Toggle data”),               onPressed: () { toggleData(); },          ),     ],), void toggleData() {    this.data = this.data == “Text” ? “New Text” : “Text”;}

Thì khi nhấn vào RaisedButton, trên giao diện không có gì xảy ra cả. Vì mặc dù data có thay đổi, nhưng bạn không yêu cầu State thông báo cho StatefulWidget chủ của nó phải render lại. Hệ quả là “trước sau như một” và nhiều bạn sẽ vò đầu bứt tai không hiểu tại sao lại không có gì xảy ra trên giao diện. Để giao diện thay đổi, thì bạn phải cho tất cả những thay đổi nằm trong thân của VoidCallback trong State.setState(VoidCallback) như sau:

123void toggleData() {    this.setState(() => this.data = this.data == “Text” ? “New Text” : “Text”);}

Như vậy, thì khi bạn nhấn vào RaisedButton, phần text của Text(data) hiển thị trên màn hình mới có thay đổi theo giá trị của data. Nếu không có setState thì không có thay đổi gì trên giao diện, dù giá trị của data có thay đổi như nào đi nữa.

3. Chia sẻ thêm về State

Trong đại đa số các ví dụ về State và setState, tôi thường đặt trường hợp gán giá trị mới cho một String, chẳng hạn như ví dụ bên trên. Tuy nhiên, không phải lúc nào bạn cũng phải thực hiện việc gán giá trị mới. Chẳng hạn, tôi muốn thêm vào một phần tử vào ListView:

123456789101112131415161718192021222324// Code trich ngang var list = new List<String>.generate(10, (i) => “Item $i”); Widget build(BuildContext) {     return new Column(          children: <Widget>[               new RaisedButton(                   child: new Text(“Add a new item”),                   onPressed: addNewItem,               ),               new ListView.builder(                   itemBuilder: (BuildContext context, int i) => new Text(this.list.get(i)),                   itemCount: this.list.size(),               )          ].     ),} void addNewItem() {     this.setState(() {          this.list.add(“Item ${this.list.size()}”);     });}

Hi vọng bài viết này giúp được các bạn bớt đi những khó khăn khi lựa chọn giữa StatefulWidget và StatelessWidget. Nếu bạn còn có thêm thắc mắc nào, hãy để lại bình luận bên dưới.

Sưu tầm:
http://eitguide.net/mot-so-chia-se-ve-flutter-stateless-va-statefulwidget/

Bình luận qua Facebook

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *