นากสีมิ้นต์ต้นฉบับกำลังปรับกรอบตรวจการ์ดในสายพานรายการบนโต๊ะสว่าง

React Native FlatList: ทำ viewability config ให้อ้างอิงคงที่

พี่โชว์Engineering

analytics ของ feed มักต้องรู้ว่าการ์ดใดอยู่บนจอนานพอจึงนับ impression ครับ FlatList มี viewabilityConfig และ onViewableItemsChanged ให้ใช้ แต่ถ้าสร้าง object หรือ callback ใหม่ทุก render เราจะผูกนโยบายการวัดเข้ากับจังหวะวาด UI จนเกิด event สั่นหรือ error เรื่อง config เปลี่ยนระหว่างทำงาน

WHY: นโยบายควรนิ่ง แม้ state จะขยับ

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

callback ก็ต้องมี identity คงที่ แต่ยังอ่าน state ล่าสุดได้ วิธีหนึ่งคือเก็บ handler ปัจจุบันใน ref แล้วให้ callback คงที่เรียกผ่าน ref อีกชั้น ทำให้ FlatList เห็น wiring เดิม ขณะที่ business logic ปรับได้

HOW: แยก config, callback และ state

import React, { useRef } from "react";
import { FlatList, Text } from "react-native";

type Row = { id: string; title: string };

export function Feed({ rows }: { rows: Row[] }) {
  const config = useRef({
    itemVisiblePercentThreshold: 70,
    minimumViewTime: 500,
  }).current;

  const onChanged = useRef(({ viewableItems }) => {
    const ids = viewableItems.map((item) => item.item.id);
    queueImpressions(ids);
  }).current;

  return (
    <FlatList
      data={rows}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <Text>{item.title}</Text>}
      viewabilityConfig={config}
      onViewableItemsChanged={onChanged}
    />
  );
}

function queueImpressions(ids: string[]) {
  void ids;
}

ตัวอย่างผ่าน TypeScript syntax check เท่านั้น ไม่ได้อ้างว่ารัน native runtime แล้ว ใน production ควร deduplicate id ต่อ session และ batch network write นอก callback เพราะ scroll event อาจถี่ อย่าเรียก setState หนัก ๆ สำหรับทุกการเปลี่ยน viewability

ถ้าต้องการหลายกติกา เช่น autoplay ที่ 90% และ analytics ที่ 50% ให้ใช้ viewabilityConfigCallbackPairs ซึ่งจับคู่ config กับ callback อย่างชัดเจน และคง array identity เช่นกัน การยัดเงื่อนไขทุกอย่างลง callback เดียวทำให้ยากต่อการทดสอบ

สิ่งที่ต้องตัดสินใจก่อนตั้งค่า

เลือก percent threshold จากความหมายของ content ไม่ใช่ตัวเลขสวย วิดีโออาจต้องเห็นเกือบเต็ม ส่วนการ์ดข่าวอาจนับเมื่อส่วนสำคัญปรากฏ minimumViewTime ช่วยกัน flick ผ่านเร็ว แต่ทำให้ event มาช้ากว่า อย่าลืมทดสอบ item สูงกว่าหน้าจอ, orientation change, list ว่าง และ navigation กลับมา

Recap

  • เก็บ viewabilityConfig ด้วย ref เพื่อให้ identity คงที่
  • แยก callback wiring ออกจาก state ที่เปลี่ยนบ่อย
  • batch และ deduplicate analytics นอกเส้น scroll
  • ใช้ callback pairs เมื่อมีหลายความหมายของคำว่าเห็น
  • ทดสอบบนอุปกรณ์จริงและทั้งสองแพลตฟอร์มก่อนสรุป metric

เริ่มจากเขียนนิยาม impression เป็นประโยคเดียว แล้วค่อยแปลงเป็น threshold กับเวลา จะลดการปรับเลขแบบเดาได้มากครับ