การทำงานแบบรีแอคทีฟ

บทแทรก 6.5

แปลไปแล้ว

ในบทนี้ คุณจะได้

  • เรียนรู้การทำงานแบบรีแอคทีฟของ Meteor
  • เข้าใจว่าทำไมต้องใช้วิธี declarative
  • รู้วิธีเขียนโค้ดกับข้อมูลชนิดรีแอคทีฟ
  • ถ้าคอลเลคชั่นเป็นแกนคุณสมบัติหลักของ Meteor แล้ว การทำงานแบบรีแอคทีฟ (reactivity) ก็คือ เปลือกหุ้มที่ทำให้แกนนั้นใช้ประโยชน์ได้นั่นเอง

    คอลเลคชั่นได้เปลี่ยนแปลงวิธีการทำงานของแอปพลิเคชันเมื่อมีการแก้ไขข้อมูลไปอย่างสิ้นเชิง โดยแทนที่แอพจะต้องตรวจสอบการเปลี่ยนแปลงของข้อมูล (เช่น ด้วยการเรียกใช้ AJAX) แล้วปรับแก้ไขไปที่หน้า HTML เอง แต่ Meteor ทำให้หน้าจอการใช้งานปรับเปลี่ยนไปตามข้อมูลที่ถูกเปลี่ยนแปลงตลอดเวลา

    ลองหยุดคิดกันซักนิดจะพบว่า เบื้องหลังการทำงานนั้น Meteor สามารถที่จะเปลี่ยนแปลง ส่วนหนึ่งส่วนใด ของหน้าจอเมื่อมีการเปลี่ยนแปลงของคอลเลคชั่นเกิดขึ้นได้

    วิธีการ เขียนโค้ด (imperative) เพื่อทำงานแบบนี้ เราต้องเรียกใช้ .observe() ที่เป็นฟังก์ชันของเคอร์เซอร์ ผูกเข้ากับเอกสารที่เราสนใจ เมื่อใดก็ตามที่เอกสารนั้นมีการเปลี่ยนแปลง เคอร์เซอร์ก็จะเรียกฟังก์ชัน callback ให้ทำงาน โดยเราสามารถแก้ไข DOM (โค้ด HTML ของหน้าเว็บ) ในฟังก์ชัน callback ได้ ซึ่งโค้ดที่เขียนออกมาก็จะดูคล้ายๆแบบนี้

    Posts.find().observe({
      added: function(post) {
        // when 'added' callback fires, add HTML element
        $('ul').append('<li id="' + post._id + '">' + post.title + '</li>');
      },
      changed: function(post) {
        // when 'changed' callback fires, modify HTML element's text
        $('ul li#' + post._id).text(post.title);
      },
      removed: function(post) {
        // when 'removed' callback fires, remove HTML element
        $('ul li#' + post._id).remove();
      }
    });
    

    คุณก็พอจะมองออกได้ว่าโค้ดที่ใช้จะซับซ้อนขึ้นอย่างรวดเร็วแค่ไหน ลองคิดดูว่าถ้าต้องเปลี่ยนค่าแต่ละ ฟิลด์ ของข่าว และยังต้องปรับแก้ไข HTML ที่ซับซ้อนในแท็ก <li> ของข่าวนั้นด้วย นี่ยังไม่ได้พูดถึงในกรณีที่มันซับซ้อนสุดๆ เมื่อเราเริ่มผูกแหล่งข้อมูลหลายๆตัวที่เปลี่ยนแปลงค่าได้แบบเรียลไทม์เลยนะ

    เรา ควร ใช้ observe() ตอนไหน

    การใช้รูปแบบการโค้ดข้างบนนั้นบางทีก็จำเป็น โดยเฉพาะเมื่อต้องเกี่ยวข้องกับโค้ดจากที่อื่น (third-party widgets) ตัวอย่างเช่น ถ้าเราต้องการปักหรือถอนหมุดบนแผนที่แบบเรียลไทม์ด้วยข้อมูลจากคอลเลคชั่น (เพื่อแสดงตำแหน่งที่ผู้ใช้ล็อกอินเข้ามา)

    ในกรณีนี้ เราจำเป็นต้องใช้ observe() เพื่อทำให้แผนที่ “คุย” กับคอลเลคชั่นใน Meteor ได้ และรู้ว่าจะทำอย่างไรเมื่อข้อมูลเปลี่ยนไป เช่น คุณอาจจะใช้ฟังก์ชัน callback added และ removed เพื่อเรียกเมธอด dropPin() หรือ removePin() จาก API ของแผนที่

    วิธีการแบบ Declarative

    Meteor มีวิธีการที่ดีกว่านั้นให้เราก็คือ การทำงานแบบรีแอคทีฟ ซึ่งแกนหลักของมันก็คือ วิธีการแบบ declarative โดยวิธีนี้จะให้เรากำหนดความสัมพันธ์ระหว่างวัตถุในครั้งแรก และเราแค่รู้ว่าพวกมันจะมีค่าสอดคล้องกันอยู่เสมอ แทนที่จะต้องกำหนดว่าจะต้องทำอะไรเมื่อมีการเปลี่ยนแปลงเกิดขึ้น

    วิธีการนี้เป็นแนวคิดที่ทรงพลัง เพราะว่าระบบงานแบบเรียลไทม์จะมีข้อมูลเข้ามามากมายที่เปลี่ยนแปลงค่าได้ตลอดไม่จำกัดเวลา โดยการประกาศว่า เราจะสร้าง HTML จากแหล่งข้อมูลชนิดรีแอกทีฟตัวไหนก็ได้ที่เราสนใจ Meteor ก็จะจัดการติดตามแหล่งข้อมูลพวกนั้น และทำงานยุ่งๆที่เหลือเพื่อปรับเปลี่ยนหน้าจอการใช้งานให้ทันสมัยอยู่เสมอ

    ทั้งหมดนี้ก็เพื่อจะบอกว่า แทนที่จะคิดถึงการใช้งาน observe แต่ Meteor ให้เราเขียนแค่นี้

    <template name="postsList">
      <ul>
        {{#each posts}}
          <li>{{title}}</li>
        {{/each}}
      </ul>
    </template>
    

    และดึงข้อมูลข่าวออกมาด้วยโค้ด

    Template.postsList.helpers({
      posts: function() {
        return Posts.find();
      }
    });
    

    สิ่งที่เกิดขึ้นเบื้องหลังก็คือ Meteor จะทำหน้าที่เชื่อมฟังก์ชัน callback ต่างๆใน observe() ให้เรา และจัดการวาดส่วนต่างๆของ HTML เมื่อข้อมูลรีแอกทีฟมีการเปลี่ยนแปลง

    ส่วนประมวลผล

    แม้ว่า Meteor จะเป็นเฟรมเวิร์กแบบเรียลไทม์และรีแอกทีฟ แต่ก็ไม่ใช่ทั้งหมดในแอพ Meteor ที่ทำงานแบบรีแอกทีฟ เพราะถ้าเป็นแบบนั้น ทุกส่วนในแอพของคุณก็จะต้องทำงานใหม่ทุกครั้งที่มีบางอย่างเปลี่ยนแปลง การทำงานแบบรีแอกทีฟจึงถูกจำกัดให้อยู่ในพื้นที่เฉพาะของโค้ดคุณ และเราเรียกพื้นที่นี้ว่า ส่วนประมวลผล (computations)

    ในอีกความหมายหนึ่ง ส่วนประมวลผล ก็คือ บล็อกของโค้ดซึ่งทำงานทุกๆครั้งเมื่อแหล่งข้อมูลชนิดรีแอกทีฟที่ผูกกันอยู่มีการเปลี่ยนแปลง ถ้าคุณมีแหล่งข้อมูลชนิดรีแอกทีฟ (เช่น ตัวแปรเซสชัน) และอยากให้มีการตอบสนองแบบรีแอกทีฟจากมัน คุณก็จำเป็นต้องสร้างส่วนประมวลผลให้

    จำไว้ว่าปกติคุณไม่จำเป็นต้องทำแบบนี้ เพราะ Meteor กำหนดให้เทมเพลตและตัวช่วยมีส่วนประมวลผลพิเศษของตัวเองอยู่แล้ว (ความหมายคือ คุณมั่นใจได้เลยว่าเทมเพลตของคุณจะตอบสนองแบบรีแอกทีฟต่อแหล่งข้อมูลของมันแน่นอน)

    แหล่งข้อมูลชนิดรีแอกทีฟทุกตัวจะติดตามส่วนประมวลผลทั้งหมดที่เรียกใช้มัน โดยมันจะแจ้งไปที่ส่วนประมวลผลเหล่านั้นเมื่อตัวมันเองมีค่าเปลี่ยนแปลงไป ด้วยการเรียกใช้ฟังก์ชัน invalidate() กับส่วนประมวลผล

    โดยทั่วไปแล้ว ส่วนประมวลผลทั้งหมดถูกกำหนดให้ทำงานแค่ประมวลผลใหม่อีกครั้ง เมื่อข้อมูลข้างในถูกทำให้ใช้การไม่ได้ (invalidation) และนั่นก็คือสิ่งที่เกิดขึ้นกับส่วนประมวลผลของเทมเพลตทั้งหมด (ถึงแม้ว่าส่วนประมวลผลของเทมเพลตจะใช้วิธีการพิเศษบางอย่างเพื่อวาดหน้าเพจขึ้นใหม่อย่างมีประสิทธิภาพด้วยก็ตาม) แม้คุณจะสามารถควบคุมได้ว่าจะให้ส่วนประมวลผลของคุณทำอะไรได้บ้างในเวลานั้นถ้าคุณจำเป็น แต่ในทางปฏิบัติแล้ว การประมวลผลใหม่อีกครั้ง มักเป็นวิธีที่คุณนำไปใช้อยู่เสมอ

    การสร้างส่วนประมวลผล

    เมื่อเราเข้าใจทฤษฎีเบื้องหลังการทำงานของส่วนประมวลผลแล้ว เพื่อให้เข้าใจมันดีขึ้นเราจะสร้างขึ้นมาซักตัว โดยนำฟังก์ชัน Tracker.autorun มาหุ้มที่บล็อกของโค้ดตรงส่วนที่เราจะใช้เป็นพื้นที่ประมวลผล และทำให้มันรีแอกทีฟดังนี้

    Meteor.startup(function() {
      Tracker.autorun(function() {
        console.log('There are ' + Posts.find().count() + ' posts');
      });
    });
    

    สังเกตุว่า เราจำเป็นต้องหุ้มบล็อก Tracker ด้วยบล็อก Meteor.startup() เพื่อให้แน่ใจว่ามันจะทำงานหลังจากที่ Meteor ได้โหลดคอลเลคชั่น Posts เรียบร้อยแล้ว

    เบื้องหลังการทำงานก็คือ autorun จะสร้างส่วนประมวลผลขึ้น และผูกมันเข้าเพื่อให้มันทำงานใหม่ทุกครั้งเมื่อใดก็ตามที่แหล่งข้อมูลที่เชื่อมโยงกันมีการเปลี่ยนแปลง ที่เราได้สร้างไปก็คือ ส่วนประมวลผลแบบง่ายๆ ที่ทำงานแค่พิมพ์จำนวนข่าวออกไปที่คอนโซล และเนื่องจาก Posts.find() เป็นแหล่งข้อมูลชนิดรีแอกทีฟ มันก็จะคอยแจ้งส่วนประมวลผลให้ทำการประมวลผลใหม่ทุกครั้งเมื่อจำนวนข่าวเปลี่ยนแปลงไป

    > Posts.insert({title: 'New Post'});
    There are 4 posts.
    

    ที่เราได้รู้มาทั้งหมดนี้ ทำให้เราสามารถเขียนโค้ดที่ใช้ข้อมูลชนิดรีแอกทีฟได้อย่างเป็นธรรมชาติ และมั่นใจได้ว่าเบื้องหลังการทำงานนั้น ระบบติดตามความเชื่อมโยงจะช่วยจัดการให้มีการทำงานใหม่ทุกครั้งในเวลาที่เหมาะสมได้