การใช้งาน Stateless และ Stateful Widget ใน Flutter เบื้องต้น

เขียนเมื่อ 4 ปีก่อน โดย Ninenik Narkdee
statelesswidget statefulwidget flutter

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ statelesswidget statefulwidget flutter

ดูแล้ว 32,197 ครั้ง




เนื้อหาตอนที่แล้ว เราได้ทำการสร้าง App เริ่มต้น
โดยใช้งาน widget มาประกอบรวมเข้ากันเป็นโครงสร้าง
widget  และพบว่า ยิ่ง App ของเรามีความซับซ้อนมายิ่งขึ้น
จำนวน widget ก็จะมากตามขึ้นไปด้วย ทบทวนได้ที่บทความ
    เริ่มต้นสร้าง App แรกด้วย Flutter กับความเข้าใจเบื้องต้น http://niik.in/953 
 
    ในเนื้อหาต่อไปนี้ เราจะมาจัดรูปแบบการใช้งาน และการกำหนด โครงสร้าง widget ใหม่
ต่อจากตอนที่แล้ว แต่ก่อนอื่น ทำความเข้าใจเพิ่มเติมเล็กน้อย ก่อนไปสู่การปรับแต่งโค้ด
 
 

Widget คืออะไร

    ใน Flutter จะมองทุกอย่างเกือบทั้งหมดเป็น widget
    Widget คือ ส่วนที่ถูกใช้สร้างเป็นหน้าตาของ App หรือที่เรียกวา user interface (UI)  โดยนำมาประกอบเรียงกันเป็นลำดับขั้น
ขึ้นเป็นโครงสร้าง   แต่ละ widget จะถูกวางซ้อนอยู่ภายใน Parent widget และได้รับการส่งต่อสืบทอดคุณสมบัติต่างๆ จาก Parent 
อีกที แม้กระทั้ง application object ก็ถือเป็น widget ซึ่งเราเรียกว่า root widget 
    MaterialApp คือ root widget
 
    เราอาจจำแนก Widget ตามการใช้งาน ได้เป็น ดังนี้
    - ใช้กำหนดโครงสร้าง (Structural Element) เช่น ปุ่ม button หรือ menu
    - ใช้กำหนดลักษณะ หรือรูปแบบ (Stylistic Element) เข่น font หรือ color
    - ใช้จัดวาง และกำหนดมุมมองเลเอาท์ (Aspect of Layout) เช่น padding หรือ alignment
 
 

StatelessWidget และ StatefulWidget คืออะไร

    ใน App ของเราจะมี widget อยู่ 2 ประเภทหลัก ที่ใช้งานคือ stateless และ stateful widget โดย state ก็คือสภาวะ ของสิ่งนั้นๆ 
stateless จึงหมายถึง widget ที่ไม่มี state หรือไม่มีสภาวะการเปลี่ยนแปลง หรือไม่จำเป็นต้องใช้งานการเปลี่ยนแปลง จึงใช้งาน widget
นี้ ส่วน stateful หมายถึง widget ที่มี state หรือมีสภาวะการเปลี่ยนแปลง ไปตามข้อมูลที่ได้รับหรือจากการกำหนดจากผู้ใช้
    ข้อแตกต่างที่สำคัญของทั้งสองส่วนนี้คือ stateful widget จะมี State object ที่ใช้ในการเก็บข้อมูล state และ ทำการส่งต่อสำหรับใช้งาน
ในกระบวนการสร้าง widget ใหม่เมื่อมีการเปลี่ยนแปลง ทำให้ค่า state ไม่ได้หายไปไหน
 

    การใช้งาน StatelessWidget

    Stateless widget ใน Flutter เป็น widget ที่ไม่จำเป็นที่ต้องมีการเปลี่ยนแปลง state เกิดขึ้น โดยเราจะใช้ stateless widget 
สำหรับสร้าง widget แบบคงที่ เหมาะสำหรับใช้ในการสร้าง และกำหนดส่วนของ UI ซึ่งจะปรับแต่งเฉพาะค่าข้อมูลของ ตัว widget เท่านั้น
เช่น Text widget ก็ถือเป็น stateless widget ที่เป็น subclass ของ StatelessWidget 
    ดูตัวอย่างการใช้งาน stateless widget ตามโค้ดด้านล่าง
 
import 'package:flutter/material.dart';

void main(){
    runApp(
        MyStatelessWidget(text: 'StatelessWidget Example to show immutable data')
    );
}

class MyStatelessWidget extends StatelessWidget {
    final String text;
    // constuctor
    MyStatelessWidget({Key? key, this.text = ''}) : super(key: key);

    @override
    Widget build(BuildContext context) {
        return Center(
            child: Text(
                text,
                textDirection: TextDirection.ltr,
            ),
        );
    }
}
    จากโค้ดจะเห็นว่า เราใช้งาน construcor ของ MyStatelessWidget class โดยส่ง text parameter เข้าไป และเป็นตัวแปร final
ไม่สามารถแก้ไขค่าจากตัวแปรนี้ได้อีก โดย MyStatelessWidget class ทำการสืบทอดมาจาก StatelessWidget ที่มีข้อมูลภายใน
หรือก็คือ text ที่ไม่สามารถแก้ไขได้
    คำสั่ง build() จะถูกเรียกใช้งานและทำการเพิ่ม widget ที่ถูก return ค่าออกมาเข้าไปในโครงสร้างของ widget หรือ widget tree
 
 

    การใช้งาน StatefulWidget

    StatefulWidget เป็น widget ที่มีการเปลี่ยนแปลงของ state  โดยจะมีการใช้งานคำสั่ง setState() เพื่อกำหนดการเปลี่ยนแปลง
โดยการเรียกใช้คำสั่ง setState() เป็นการบอกให้ flutter รู้ว่ามีบางอย่างเปลี่ยนแปลงเกิดขึ้นกับ state และ App ต้องทำการ rerun หรือ
ทำคำสั่ง build() ใหม่ ดังนั้นตัว App จึงได้รับผลจากการเปลี่ยนแปลงที่เกิดขึ้น
    State เป็นข้อมูลที่สามารถนำมาใช้งานได้ต่อเนื่องในขณะที่ widget ถูกสร้าง และอาจะมีการเปลี่ยนแปลงในทุกช่วงเวลาที่มีการใช้งาน
ของ widget ดังนั้นจำเป็นต้องกำหนดการทำงานรองรับเมื่้อมีการเปลี่ยนแปลงของ state เกิดขึ้น  เราจะใช้งาน StatefulWidget เมื่อต้อง
การให้ widget รองรับการเปลี่ยนแปลงที่เกิดขึ้นอัตโนมัติ ยกตัวอย่างเช่น state มีการเปลี่ยนแปลงเมื่อทำการพิมพ์ข้อความลงไปในฟอร์ม
หรือ state มีการเปลี่ยนแปลงจากข้อมูลที่ได้รับมาหรือมีการอัพเดท เป็นต้น
    ตัวอย่าง widget ที่เป็น stateful widget ที่เราน่าจะคุ้นกับรูปแบบการใช้งานก็เช่น Checkbox, Radio, Slider, Form และ TextField 
เหล่านี้ ล้วนเป็น subclass ของ StatefulWidget
    ดูตัวอย่างการใช้งาน stateful widget ตามโค้ดด้านล่าง
 
import 'package:flutter/material.dart';

void main(){
    runApp(
        MyStatefulWidget(title: 'StatefulWidget Example')
    );
}

class MyStatefulWidget extends StatefulWidget {
    MyStatefulWidget({Key? key, this.title = ''}) : super(key: key);
    final String title;

    @override
    _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
    int _counter = 0;

    void _incrementCounter() {
        setState(() {
            _counter++;
        });
    }

    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title:'Flutter App',
            home: Scaffold(
                appBar: AppBar(
                    title: Text(widget.title),
                ),
                body: Center(
                    child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                            Text(
                                'You have pushed the button this many times:',
                            ),
                            Text(
                                '$_counter',
                                style: Theme.of(context).textTheme.displayMedium,
                            ),
                        ],
                    ),
                ),
                floatingActionButton: FloatingActionButton(
                    onPressed: _incrementCounter,
                    tooltip: 'Increment',
                    child: Icon(Icons.add),
                ),
            ),
        );
    }
}
    โค้ดตัวอย่างข้างต้น เรามีการประกาศ StatefulWidget ซึ่งมีการใช้งานคำสั่ง createState() โดยคำสั่งนี้จะทำการสร้าง State object
เพื่อใช้ในการจัดการกับ state ของ widget ที่ชื่อ  _MyStatefulWidgetState
    state class ที่ชื่อ _MyStatefulWidgetState เรียกใช้งานคำสั่ง build() เพื่อสร้าง widget  เมื่อมีการเปลี่ยนแปลงของ state เกิดขึ้น
ในตัวอย่าง เมื่อผู้ใช้ทำการกดที่ปุ่ม FloatingActionButton ก็จะทำการเรียกใช้ฟังก์ชั่น _incrementCounter โดยคำสั่งนี้จะทำการเรียกใช้
คำสั่ง setState() และทำการเพิ่มค่าจำนวนการกดในตัวแปร _counter ทำให้เกิดการเปลี่ยนแปลงของ state เกิดขึ้น และทำการ rerun 
คำสั่ง buid() เพื่อสร้าง widget ใหม่เข้ามาใน UI ซึ่งจากรูปแบบตัวอย่างข้างตัน ก็จะเป็นการไปสร้างตั้งแต่ root widget คือ MaterialApp
แต่เวลาใช้งานจริงๆ เราจะกำหนดเฉพาะส่วนที่ต้องการ ข้างต้นเป็นเพียงต้วอย่างเท่านั้น
 
    ตอนนี้เราพอเข้าใจเกี่ยวกับ Stateless และ Stateful widget เบื้องต้นไปแล้ว ต่อไป เราจะมาปรับใช้กับโค้ดในตัวอย่างตอนที่ผ่านมา
สิ่งที่เราพิจารณาได้คือ จะเห็นว่า ส่วนของ MaterialApp widget ควรถูกกำหนดสำหรับ UI หรือหน้าตา App ดังนั้น เราจะใช้ส่วนนี้เป็น
ส่วนที่ใช้งาน Stateless Widget 
    สำหรับในส่วนของ Scaffold widget เราจะใช้เป็น Stateful widget ที่รองรับการเปลี่ยนของ state ที่อาจจะมีหรือเกิดขึ้เนในอนาคต
 
    โค้ดไฟล์ main.dart จากตอนที่แล้ว 
import 'package:flutter/material.dart';

runApp(
    MaterialApp(
        title: 'First Flutter App',
        home: Scaffold(
            appBar: AppBar(
                title: Text('Welcome to Flutter'),
                backgroundColor: Colors.green
            ),
            body: Material(
                color: Colors.lightGreen,
                child: Center(
                    child: Text(
                        'Hello World',
                        style: TextStyle(
                            color: Colors.white,
                            fontSize: 20.0
                        )
                    )
                )
            )
        )
    )
);
    จัดรูปแบบ และใช้งาน Stateless และ Stateful widget จะได้เป็นดังนี้
 
import 'package:flutter/material.dart';

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

// ส่วนของ Stateless widget
class MyApp extends StatelessWidget{
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'First Flutter App',
            home: FirstScreen()
        );
    }
}

// ส่วนของ Stateful widget

class FirstScreen extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return _FirstScreen();
  }
}

class _FirstScreen extends State<FirstScreen>{
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Welcome to Flutter'),
                backgroundColor: Colors.green
            ),
            body: Material(
                color: Colors.lightGreen,
                child: Center(
                    child: Text(
                        'Hello World',
                        style: TextStyle(
                            color: Colors.white,
                            fontSize: 20.0
                        )
                    )
                )
            )
        );
    }

}
    จากโค้ด เราพอจะเห็นแล้วว่า แทนที่จะทำการสร้าง widget วางซ้อนกันจำนวนมากๆ ไว้ในฟังก์ชั่น runApp() เราสามรถสร้างแยกเป็น
widget ตามแต่ละประเภท สำหรับใช้งาน ได้ ในตัวอย่าง เรายังไม่มีการกำหนด การทำคำสั่งสำหรับใช้งานกรณี มีการเปลี่ยนแปลงของ
state เกิดขึ้นใน _FirstScreen State widget หรือก็คือยังไม่มีการเรียกใช้งานคำสั่ง setState() แต่อย่างไร ซึ่งในตัวอย่างเพียงต้องการ
ให้เห็นการกำหนดประเภทของ widget ที่จะใช้งาน โดยในส่วนของ MyApp เราใช้งานเป็น stateless นั่นคือไม่รองรับการเปลี่ยนแปลง
ของ state แต่เราสามารถเปลี่ยนแปลงการตั้งค่าเพิ่มเติมได้ เช่น อาจจะกำหนดรูปแบบ theme หรือสีสำหรับ App เหล่านี้ เป็นเพียงการ
ปรับแต่งค่าภายในตัว widget ไม่มีการสร้าง widget ใหม่ใดๆ
    แต่สำหรับ Scaffold ที่เรากำหนดไว้ใน _FirstScreen State  เราอาจจะมีแผนหรืออาจจะมีการเปลี่ยนแปลงภายในเกิดขึ้น จึงใช้งานเป็น
stateful และมีการสร้าง state object ไว้ใช้งาน
 
    ตอนนี้ถ้าเรามองที่ไฟล์ main.dart ถึงแม้เราจะกำหนดแยกส่วนต่างๆ เป็น widget แล้ว แต่ก็เป็นไปได้ที่ App ของเรามีมากกว่าหนึ่งหน้า
และเราคงไม่เขียนทั้งหมดไว้ในไฟล์ main.dart  อย่างสมมติเช่น เรามีหน้าที่สอง มีชื่อใหม่เป็น SecondScreen เป็นต้น เราสามารถแยก
ส่วนเหล่านี้ไปไว้อีกไฟล์ หรือที่เรียกว่า package ได้ 
    ดังนั้นเราจะทำการแยก FirstScreen class เป็นอีก package หนึ่ง แล้วค่อย import มาใช้งานในไฟล์ main.dart อีกที โดยให้ทำการคลิก
ขวาที่โฟลเดอร์ lib แล้วเลือก สร้าง "New" > "Package" จากนั้นกำหนดชื่อ package โดยใช้เป็นรูปแบบตัวพิมพ์เล็ก มีเครื่องหมาย _ 
 
 

 
 
    ในที่นี้เรากำหนดเป็น app_screen
 
 

 
 
    จากนั้นสร้างไฟล์ first_screen.dart ไว้ใน package ข้างต้นอีกที โดยคลิกขวาที่ชื่อ package ที่เราสร้าง เลือก "New" > "Dart File"
 
 

 
 
    แล้วกำหนดชื่อไฟล์ดังรูป
 
 

 
 
    เสร็จแล้วเปิดไฟล์ขึ้นมาแก้ไข โดยทำการย้ายส่วนของ FirstScreen class ที่เป็น StatefulWidget รวมถึง _FirstScreen State มาไว้ใน
ไฟล์ first_screen.dart และทำการ import material package เข้ามาใช้งาน จะได้เป็นดังนี้
 
 

 
 
    ไฟล์ first_screen.dart
import 'package:flutter/material.dart';

// ส่วนของ Stateful widget
class FirstScreen extends StatefulWidget{
    @override
    State<StatefulWidget> createState() {
        return _FirstScreen();
    }
}
class _FirstScreen extends State<FirstScreen>{
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Welcome to Flutter'),
                backgroundColor: Colors.green
            ),
            body: Material(
                color: Colors.lightGreen,
                child: Center(
                    child: Text(
                        'Hello World',
                        style: TextStyle(
                            color: Colors.white,
                            fontSize: 20.0
                        )
                    )
                )
            )
        );
    }
}
    ในส่วนของ main.dart ไฟล์ ก็จะเหลือเฉพาะในส่วนของฟังก์ชั่น main() และ ส่วนของ MyApp ซึ่งเป็น StatelessWidget ที่ใช้งาน
เป็น MaterialApp widget เหมือนเป็นส่วนของ root widget ที่เราอาจจะปรับแต่ง เช่น tbeme ภายหลังได้ จะได้ไฟล์เป็นดังนี้
 
 

 
 
    ไฟล์ main.dart
import 'package:flutter/material.dart';
import './app_screen/first_screen.dart';

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

// ส่วนของ Stateless widget
class MyApp extends StatelessWidget{
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'First Flutter App',
            home: FirstScreen()
        );
    }
}
    ในบรรทัดที่ 14 เดิมเราเรียกใช้งาน FirstScreen() class ที่อยู่ในไฟล์ main.dart แต่เมื่อเราทำการแยกเป็นอีก package หนึ่ง หรือแยก
เป็นไฟล์ first_screen.dart เราจำเป็นต้อง import เข้ามาใช้งาน ดังที่เรากำหนดในบรรที่ 2 โดยใช้รูปแบบเป็น relative path
    เมื่อทดสอบรันโค้ด ผลลัพธ์ที่ได้ ก็จะเหมือนผลลัพธ์ที่ได้จากโค้ดในตอนที่ผ่านมา แต่การจัดการโครงสร้างของโปรแกรมเรา มีขั้นตอน
การกำหนดที่ชัดเจน เป็นสัดส่วน และรองรับการปรับแต่งโค้ดเพิ่มเติมได้ง่ายขึ้น
 
    ทบทวนเล็กน้อยก่อนจบเนื้อหา
    - เราทำการสืบทอด MyApp หรือ App ของเราจาก StatelessWidget ซึ่งทำให้ตัว App เองก็เป็น widget หนึ่งเช่นกัน  ดังคำพูดที่ว่า
ใน Flutter เกือบทั้งหมดล้วนเป็น widget รวมทั้ง alignment, padding และ layout
    - ใน widget จะมีหน้าที่หลักคือเรียกใช้คำสั่ง build() เพื่อบอกว่า ใช้ widget นี้เพื่อแสดง widget อื่น หรือ widget ที่อยู่ในระดับลำดับ
ขั้นโครงสร้างที่น้อยกว่า อย่าง MyApp เป็น widget ที่สร้างเพื่อแสดง MaterialApp widget นั่นเอง
 
    เนื้อหาและความเข้าใจเพิ่มเติม เกี่ยวกับ State ยังมีอีกมาก จะได้นำมาแนะนำ และทำความเข้าใจในลำดับต่อๆ ไป


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ



ทบทวนบทความที่แล้ว









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ