ในสถาปัตยกรรม Flux คุณจัดการวงจรชีวิตของ Store ได้อย่างไร


132

ฉันกำลังอ่านเกี่ยวกับFluxแต่ตัวอย่างแอป Todoนั้นง่ายเกินไปสำหรับฉันที่จะเข้าใจประเด็นสำคัญบางประการ

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

ในสถาปัตยกรรม Flux สิ่งนี้จะสอดคล้องกับ Stores และ Dispatchers อย่างไร

เราจะใช้หนึ่งคนPostStoreต่อผู้ใช้หรือเราจะมีร้านค้าระดับโลกบ้างไหม? แล้วผู้มอบหมายงานเราจะสร้างผู้มอบหมายงานใหม่สำหรับ "หน้าผู้ใช้" แต่ละหน้าหรือจะใช้ซิงเกิลตัน สุดท้ายส่วนใดของสถาปัตยกรรมที่รับผิดชอบในการจัดการวงจรชีวิตของร้านค้า“ เฉพาะเพจ” เพื่อตอบสนองต่อการเปลี่ยนแปลงเส้นทาง

ยิ่งไปกว่านั้นหน้าหลอกหน้าเดียวอาจมีรายการข้อมูลประเภทเดียวกันหลายรายการ ยกตัวอย่างเช่นในหน้าโปรไฟล์ของผมต้องการที่จะแสดงทั้งติดตามและดังต่อไปนี้ Singleton สามารถUserStoreทำงานได้อย่างไรในกรณีนี้? จะUserPageStoreจัดการfollowedBy: UserStoreและfollows: UserStore?

คำตอบ:


124

ในแอป Flux ควรมี Dispatcher เพียงคนเดียว ข้อมูลทั้งหมดไหลผ่านศูนย์กลางนี้ การมี Singleton Dispatcher ทำให้สามารถจัดการร้านค้าทั้งหมดได้ สิ่งนี้มีความสำคัญเมื่อคุณต้องการการอัปเดต Store # 1 จากนั้นให้ Store # 2 อัปเดตตัวเองตามทั้งการดำเนินการและสถานะของ Store # 1 Flux ถือว่าสถานการณ์นี้เป็นเหตุการณ์ที่เกิดขึ้นในแอปพลิเคชันขนาดใหญ่ โดยหลักการแล้วสถานการณ์นี้ไม่จำเป็นต้องเกิดขึ้นและนักพัฒนาควรพยายามหลีกเลี่ยงความซับซ้อนนี้หากเป็นไปได้ แต่ Singleton Dispatcher ก็พร้อมที่จะรับมือเมื่อถึงเวลา

ร้านค้าเป็นเสื้อกล้ามเช่นกัน พวกเขาควรจะยังคงเป็นอิสระและแยกออกจากกันมากที่สุด - จักรวาลที่มีอยู่ในตัวเองซึ่งเราสามารถสืบค้นได้จาก Controller-View ถนนสายเดียวที่เข้าไปในร้านค้าคือการเรียกกลับที่ลงทะเบียนกับ Dispatcher ทางออกเดียวคือผ่านฟังก์ชัน getter นอกจากนี้ร้านค้ายังเผยแพร่เหตุการณ์เมื่อสถานะของพวกเขามีการเปลี่ยนแปลงดังนั้น Controller-Views จึงสามารถทราบได้ว่าเมื่อใดควรสอบถามสถานะใหม่โดยใช้ getters

ในแอปตัวอย่างของคุณจะมีไฟล์PostStore. ร้านค้าเดียวกันนี้สามารถจัดการโพสต์บน "เพจ" (เพจหลอก) ที่เหมือนกับ Newsfeed ของ FB ซึ่งโพสต์จะปรากฏจากผู้ใช้ที่แตกต่างกัน โลจิคัลโดเมนคือรายการโพสต์และสามารถจัดการกับรายการโพสต์ใด ๆ เมื่อเราย้ายจากเพจหลอกเป็นเพจหลอกเราต้องการเริ่มต้นสถานะของร้านค้าใหม่เพื่อให้สอดคล้องกับสถานะใหม่ เราอาจต้องการแคชสถานะก่อนหน้าใน localStorage เป็นการเพิ่มประสิทธิภาพสำหรับการเลื่อนไปมาระหว่างเพจหลอก แต่ความชอบของฉันคือการตั้งค่าPageStoreที่รอร้านค้าอื่น ๆ ทั้งหมดจัดการความสัมพันธ์กับ localStorage สำหรับร้านค้าทั้งหมดบน หน้าหลอกแล้วอัปเดตสถานะของตัวเอง โปรดทราบว่าสิ่งนี้PageStoreจะไม่เก็บข้อมูลใด ๆ เกี่ยวกับโพสต์นั่นคือโดเมนของไฟล์PostStore. มันก็จะรู้ว่าเพจหลอกเฉพาะถูกแคชหรือไม่เพราะเพจหลอกเป็นโดเมนของมัน

PostStoreจะมีinitialize()วิธีการ วิธีนี้จะล้างสถานะเก่าเสมอแม้ว่านี่จะเป็นการเริ่มต้นครั้งแรกจากนั้นสร้างสถานะตามข้อมูลที่ได้รับผ่านการดำเนินการผ่าน Dispatcher การย้ายจากหน้าหลอกไปยังอีกหน้าหนึ่งอาจเกี่ยวข้องกับการPAGE_UPDATEกระทำซึ่งจะทำให้เกิดการเรียกinitialize()ใช้ มีรายละเอียดที่ต้องแก้ไขเกี่ยวกับการดึงข้อมูลจากแคชภายในเครื่องการดึงข้อมูลจากเซิร์ฟเวอร์การแสดงผลในแง่ดีและสถานะข้อผิดพลาด XHR แต่นี่เป็นแนวคิดทั่วไป

หากเพจหลอกเฉพาะไม่จำเป็นต้องมีร้านค้าทั้งหมดในแอปพลิเคชันฉันไม่แน่ใจว่ามีเหตุผลใดที่จะทำลายสิ่งที่ไม่ได้ใช้นอกจากข้อ จำกัด ของหน่วยความจำ แต่ร้านค้ามักไม่ใช้หน่วยความจำมากนัก คุณต้องแน่ใจว่าได้ลบผู้ฟังเหตุการณ์ใน Controller-Views ที่คุณกำลังทำลาย สิ่งนี้ทำได้ในcomponentWillUnmount()วิธีการของ React


5
มีแนวทางที่แตกต่างกันเล็กน้อยสำหรับสิ่งที่คุณต้องการทำและฉันคิดว่ามันขึ้นอยู่กับสิ่งที่คุณพยายามสร้าง แนวทางหนึ่งคือ a UserListStoreกับผู้ใช้ที่เกี่ยวข้องทั้งหมดในนั้น และผู้ใช้แต่ละคนจะมีแฟล็กบูลีนสองสามอันที่อธิบายความสัมพันธ์กับโปรไฟล์ผู้ใช้ปัจจุบัน บางอย่าง{ follower: true, followed: false }เช่น วิธีการgetFolloweds()และgetFollowers()จะดึงชุดผู้ใช้ต่างๆที่คุณต้องการสำหรับ UI
fisherwebdev

4
หรือคุณสามารถมี FollowUserListStore และ FollowerUserListStore ที่ทั้งสองสืบทอดมาจาก UserListStore นามธรรม
fisherwebdev

ฉันมีคำถามเล็กน้อย - ทำไมไม่ใช้ pub sub เพื่อส่งข้อมูลจากร้านค้าโดยตรงแทนที่จะต้องการให้สมาชิกดึงข้อมูล
sunwukung

2
@sunwukung สิ่งนี้ต้องการให้ร้านค้าติดตามว่ามุมมองของคอนโทรลเลอร์ต้องการข้อมูลอะไร เป็นการดีกว่าที่จะให้ร้านค้าเผยแพร่ข้อเท็จจริงที่ว่าพวกเขามีการเปลี่ยนแปลงไม่ทางใดก็ทางหนึ่งแล้วปล่อยให้มุมมองของผู้ควบคุมที่สนใจดึงข้อมูลส่วนที่ต้องการ
fisherwebdev

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

79

(หมายเหตุ: ฉันใช้ไวยากรณ์ ES6 โดยใช้ตัวเลือก JSX Harmony)

ในฐานะที่เป็นแบบฝึกหัดฉันได้เขียนแอป Flux ตัวอย่างที่อนุญาตให้เรียกดูGithub usersและทำซ้ำ
มันขึ้นอยู่กับคำตอบของ fisherwebdevแต่ยังสะท้อนให้เห็นถึงวิธีการที่ฉันใช้สำหรับการตอบสนอง API ที่เป็นมาตรฐาน

ฉันจัดทำเอกสารสองสามวิธีที่ฉันได้ลองใช้ในขณะที่เรียนรู้ Flux
ฉันพยายามทำให้มันใกล้เคียงกับโลกแห่งความเป็นจริง (การแบ่งหน้าไม่มี localStorage API ปลอม)

มีบางส่วนที่ฉันสนใจเป็นพิเศษที่นี่:

ฉันจัดประเภทร้านค้าอย่างไร

ฉันพยายามหลีกเลี่ยงการทำซ้ำบางอย่างที่ฉันเคยเห็นในตัวอย่าง Flux อื่น ๆ โดยเฉพาะในร้านค้า ฉันพบว่ามีประโยชน์ในการแบ่งร้านค้าออกเป็นสามประเภทอย่างมีเหตุผล:

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

ที่เก็บเนื้อหาเก็บเกี่ยวอ็อบเจ็กต์จากการกระทำของเซิร์ฟเวอร์ทั้งหมด ตัวอย่างเช่นUserStore มีลักษณะเป็นaction.response.entities.usersถ้ามันมีอยู่โดยไม่คำนึงถึงการที่การกระทำยิง ไม่จำเป็นต้องมีswitchไฟล์. Normalizrทำให้ง่ายต่อการแบน API repons ใด ๆ ให้เป็นรูปแบบนี้

// Content Stores keep their data like this
{
  7: {
    id: 7,
    name: 'Dan'
  },
  ...
}

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

พวกเขามักจะตอบสนองต่อการกระทำเพียงไม่กี่ (เช่นREQUEST_FEED, REQUEST_FEED_SUCCESS, REQUEST_FEED_ERROR)

// Paginated Stores keep their data like this
[7, 10, 5, ...]

Indexed List Storesเป็นเหมือน List Stores แต่กำหนดความสัมพันธ์แบบหนึ่งต่อกลุ่ม ตัวอย่างเช่น "ผู้ติดตามของผู้ใช้", "stargazers ของที่เก็บ", "ที่เก็บของผู้ใช้" นอกจากนี้ยังจัดการการแบ่งหน้า

พวกเขายังตอบสนองต่อการได้ตามปกติเพียงไม่กี่การกระทำ (เช่นREQUEST_USER_REPOS, REQUEST_USER_REPOS_SUCCESS, REQUEST_USER_REPOS_ERROR)

ในแอปโซเชียลส่วนใหญ่คุณจะมีแอปเหล่านี้มากมายและคุณต้องการสร้างแอปขึ้นมาใหม่ได้อย่างรวดเร็ว

// Indexed Paginated Stores keep their data like this
{
  2: [7, 10, 5, ...],
  6: [7, 1, 2, ...],
  ...
}

หมายเหตุ: นี่ไม่ใช่ชั้นเรียนจริงหรือบางอย่าง เป็นเพียงวิธีคิดเกี่ยวกับร้านค้า ฉันได้ให้ความช่วยเหลือบางอย่าง

StoreUtils

createStore

วิธีนี้ช่วยให้คุณมีร้านค้าขั้นพื้นฐานที่สุด:

createStore(spec) {
  var store = merge(EventEmitter.prototype, merge(spec, {
    emitChange() {
      this.emit(CHANGE_EVENT);
    },

    addChangeListener(callback) {
      this.on(CHANGE_EVENT, callback);
    },

    removeChangeListener(callback) {
      this.removeListener(CHANGE_EVENT, callback);
    }
  }));

  _.each(store, function (val, key) {
    if (_.isFunction(val)) {
      store[key] = store[key].bind(store);
    }
  });

  store.setMaxListeners(0);
  return store;
}

ฉันใช้มันเพื่อสร้างร้านค้าทั้งหมด

isInBag, mergeIntoBag

ตัวช่วยเล็ก ๆ ที่มีประโยชน์สำหรับที่เก็บเนื้อหา

isInBag(bag, id, fields) {
  var item = bag[id];
  if (!bag[id]) {
    return false;
  }

  if (fields) {
    return fields.every(field => item.hasOwnProperty(field));
  } else {
    return true;
  }
},

mergeIntoBag(bag, entities, transform) {
  if (!transform) {
    transform = (x) => x;
  }

  for (var key in entities) {
    if (!entities.hasOwnProperty(key)) {
      continue;
    }

    if (!bag.hasOwnProperty(key)) {
      bag[key] = transform(entities[key]);
    } else if (!shallowEqual(bag[key], entities[key])) {
      bag[key] = transform(merge(bag[key], entities[key]));
    }
  }
}

PaginatedList

จัดเก็บสถานะการแบ่งหน้าและบังคับใช้การยืนยันบางอย่าง (ไม่สามารถดึงข้อมูลหน้าขณะดึงข้อมูล ฯลฯ )

class PaginatedList {
  constructor(ids) {
    this._ids = ids || [];
    this._pageCount = 0;
    this._nextPageUrl = null;
    this._isExpectingPage = false;
  }

  getIds() {
    return this._ids;
  }

  getPageCount() {
    return this._pageCount;
  }

  isExpectingPage() {
    return this._isExpectingPage;
  }

  getNextPageUrl() {
    return this._nextPageUrl;
  }

  isLastPage() {
    return this.getNextPageUrl() === null && this.getPageCount() > 0;
  }

  prepend(id) {
    this._ids = _.union([id], this._ids);
  }

  remove(id) {
    this._ids = _.without(this._ids, id);
  }

  expectPage() {
    invariant(!this._isExpectingPage, 'Cannot call expectPage twice without prior cancelPage or receivePage call.');
    this._isExpectingPage = true;
  }

  cancelPage() {
    invariant(this._isExpectingPage, 'Cannot call cancelPage without prior expectPage call.');
    this._isExpectingPage = false;
  }

  receivePage(newIds, nextPageUrl) {
    invariant(this._isExpectingPage, 'Cannot call receivePage without prior expectPage call.');

    if (newIds.length) {
      this._ids = _.union(this._ids, newIds);
    }

    this._isExpectingPage = false;
    this._nextPageUrl = nextPageUrl || null;
    this._pageCount++;
  }
}

PaginatedStoreUtils

createListStore, createIndexedListStore,createListActionHandler

ทำให้การสร้าง Indexed List Stores เป็นเรื่องง่ายที่สุดโดยจัดเตรียมวิธีการสำเร็จรูปและการจัดการการดำเนินการ:

var PROXIED_PAGINATED_LIST_METHODS = [
  'getIds', 'getPageCount', 'getNextPageUrl',
  'isExpectingPage', 'isLastPage'
];

function createListStoreSpec({ getList, callListMethod }) {
  var spec = {
    getList: getList
  };

  PROXIED_PAGINATED_LIST_METHODS.forEach(method => {
    spec[method] = function (...args) {
      return callListMethod(method, args);
    };
  });

  return spec;
}

/**
 * Creates a simple paginated store that represents a global list (e.g. feed).
 */
function createListStore(spec) {
  var list = new PaginatedList();

  function getList() {
    return list;
  }

  function callListMethod(method, args) {
    return list[method].call(list, args);
  }

  return createStore(
    merge(spec, createListStoreSpec({
      getList: getList,
      callListMethod: callListMethod
    }))
  );
}

/**
 * Creates an indexed paginated store that represents a one-many relationship
 * (e.g. user's posts). Expects foreign key ID to be passed as first parameter
 * to store methods.
 */
function createIndexedListStore(spec) {
  var lists = {};

  function getList(id) {
    if (!lists[id]) {
      lists[id] = new PaginatedList();
    }

    return lists[id];
  }

  function callListMethod(method, args) {
    var id = args.shift();
    if (typeof id ===  'undefined') {
      throw new Error('Indexed pagination store methods expect ID as first parameter.');
    }

    var list = getList(id);
    return list[method].call(list, args);
  }

  return createStore(
    merge(spec, createListStoreSpec({
      getList: getList,
      callListMethod: callListMethod
    }))
  );
}

/**
 * Creates a handler that responds to list store pagination actions.
 */
function createListActionHandler(actions) {
  var {
    request: requestAction,
    error: errorAction,
    success: successAction,
    preload: preloadAction
  } = actions;

  invariant(requestAction, 'Pass a valid request action.');
  invariant(errorAction, 'Pass a valid error action.');
  invariant(successAction, 'Pass a valid success action.');

  return function (action, list, emitChange) {
    switch (action.type) {
    case requestAction:
      list.expectPage();
      emitChange();
      break;

    case errorAction:
      list.cancelPage();
      emitChange();
      break;

    case successAction:
      list.receivePage(
        action.response.result,
        action.response.nextPageUrl
      );
      emitChange();
      break;
    }
  };
}

var PaginatedStoreUtils = {
  createListStore: createListStore,
  createIndexedListStore: createIndexedListStore,
  createListActionHandler: createListActionHandler
};

createStoreMixin

mixin mixins: [createStoreMixin(UserStore)]ส่วนประกอบที่ช่วยให้การปรับแต่งในร้านค้าที่พวกเขาสนใจในกำลังเช่น

function createStoreMixin(...stores) {
  var StoreMixin = {
    getInitialState() {
      return this.getStateFromStores(this.props);
    },

    componentDidMount() {
      stores.forEach(store =>
        store.addChangeListener(this.handleStoresChanged)
      );

      this.setState(this.getStateFromStores(this.props));
    },

    componentWillUnmount() {
      stores.forEach(store =>
        store.removeChangeListener(this.handleStoresChanged)
      );
    },

    handleStoresChanged() {
      if (this.isMounted()) {
        this.setState(this.getStateFromStores(this.props));
      }
    }
  };

  return StoreMixin;
}

1
จากข้อเท็จจริงที่ว่าคุณได้เขียน Stampsy แล้วหากคุณจะเขียนแอปพลิเคชันฝั่งไคลเอ็นต์ใหม่ทั้งหมดคุณจะใช้ FLUX และวิธีการเดียวกับที่คุณใช้ในการสร้างแอปตัวอย่างนี้หรือไม่
eAbi

2
eAbi: นี่คือแนวทางที่เรากำลังใช้ในขณะที่เราเขียน Stampsy ใหม่ใน Flux (หวังว่าจะเผยแพร่ในเดือนหน้า) มันไม่เหมาะ แต่เหมาะสำหรับเรา เมื่อไหร่ / ถ้าเรารู้วิธีที่ดีกว่าในการทำสิ่งนั้นเราจะแบ่งปันให้
Dan Abramov

1
eAbi: อย่างไรก็ตามเราไม่ได้ใช้ normalizr อีกต่อไปเพราะผู้ชายคนหนึ่งในทีมของเราเขียนAPI ทั้งหมดของเราใหม่เพื่อส่งคืนการตอบสนองที่เป็นมาตรฐาน มันมีประโยชน์ก่อนที่จะทำแม้ว่า
Dan Abramov

ขอบคุณสำหรับข้อมูลของคุณ. ฉันได้ตรวจสอบ github repo ของคุณแล้วและฉันกำลังพยายามเริ่มโครงการ (สร้างใน YUI3) ด้วยแนวทางของคุณ แต่ฉันมีปัญหาในการรวบรวมรหัส (ถ้าคุณสามารถพูดได้) ฉันไม่ได้ใช้งานเซิร์ฟเวอร์ภายใต้โหนดดังนั้นฉันจึงต้องการคัดลอกซอร์สไปยังไดเร็กทอรีแบบคงที่ของฉัน แต่ฉันยังต้องทำงานบางอย่าง ... มันค่อนข้างยุ่งยากและฉันพบว่าไฟล์บางไฟล์มีไวยากรณ์ JS ที่แตกต่างกัน โดยเฉพาะในไฟล์ jsx
eAbi

2
@ ฌอน: ฉันไม่เห็นว่ามันเป็นปัญหาเลย การไหลของข้อมูลเป็นเรื่องเกี่ยวกับการเขียนข้อมูลที่ไม่ได้อ่านมัน แน่นอนว่าจะดีที่สุดหากการกระทำไม่เชื่อเรื่องพระเจ้า แต่สำหรับการเพิ่มประสิทธิภาพคำขอฉันคิดว่าการอ่านจากร้านค้าเป็นเรื่องที่ดี หลังจากที่ทุกส่วนประกอบอ่านจากร้านค้าและยิงการกระทำเหล่านั้น คุณสามารถใช้ตรรกะนี้ซ้ำได้ในทุกองค์ประกอบ แต่นั่นคือสิ่งที่ผู้สร้างแอ็คชั่นมีไว้เพื่อ ..
Dan Abramov

27

ดังนั้นในRefluxแนวคิดของ Dispatcher จะถูกลบออกและคุณจะต้องคิดในแง่ของการไหลของข้อมูลผ่านการดำเนินการและร้านค้า ได้แก่

Actions <-- Store { <-- Another Store } <-- Components

ลูกศรแต่ละลูกจะจำลองวิธีการรับฟังกระแสข้อมูลซึ่งหมายความว่าข้อมูลไหลไปในทิศทางตรงกันข้าม ตัวเลขที่แท้จริงสำหรับการไหลของข้อมูลคือ:

Actions --> Stores --> Components
   ^          |            |
   +----------+------------+

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

  • ที่เก็บข้อมูลเพจที่จัดการการสลับเพจ
  • ที่เก็บข้อมูลโปรไฟล์ผู้ใช้ที่โหลดโปรไฟล์ผู้ใช้เมื่อเปิดเพจ
  • ที่เก็บข้อมูลรายการโพสต์ที่โหลดและจัดการโพสต์ที่มองเห็นได้

ใน Reflux คุณต้องตั้งค่าดังนี้:

การกระทำ

// Set up the two actions we need for this use case.
var Actions = Reflux.createActions(['openUserProfile', 'loadUserProfile', 'loadInitialPosts', 'loadMorePosts']);

ที่เก็บเพจ

var currentPageStore = Reflux.createStore({
    init: function() {
        this.listenTo(openUserProfile, this.openUserProfileCallback);
    },
    // We are assuming that the action is invoked with a profileid
    openUserProfileCallback: function(userProfileId) {
        // Trigger to the page handling component to open the user profile
        this.trigger('user profile');

        // Invoke the following action with the loaded the user profile
        Actions.loadUserProfile(userProfileId);
    }
});

ที่เก็บโปรไฟล์ผู้ใช้

var currentUserProfileStore = Reflux.createStore({
    init: function() {
        this.listenTo(Actions.loadUserProfile, this.switchToUser);
    },
    switchToUser: function(userProfileId) {
        // Do some ajaxy stuff then with the loaded user profile
        // trigger the stores internal change event with it
        this.trigger(userProfile);
    }
});

ร้านค้าโพสต์

var currentPostsStore = Reflux.createStore({
    init: function() {
        // for initial posts loading by listening to when the 
        // user profile store changes
        this.listenTo(currentUserProfileStore, this.loadInitialPostsFor);
        // for infinite posts loading
        this.listenTo(Actions.loadMorePosts, this.loadMorePosts);
    },
    loadInitialPostsFor: function(userProfile) {
        this.currentUserProfile = userProfile;

        // Do some ajax stuff here to fetch the initial posts then send
        // them through the change event
        this.trigger(postData, 'initial');
    },
    loadMorePosts: function() {
        // Do some ajaxy stuff to fetch more posts then send them through
        // the change event
        this.trigger(postData, 'more');
    }
});

ส่วนประกอบ

ฉันสมมติว่าคุณมีส่วนประกอบสำหรับการดูหน้าทั้งหมดหน้าโปรไฟล์ผู้ใช้และรายการโพสต์ สิ่งต่อไปนี้จะต้องต่อสาย:

  • ปุ่มที่เปิดโปรไฟล์ผู้ใช้จะต้องเรียกใช้Action.openUserProfileด้วยรหัสที่ถูกต้องในระหว่างเหตุการณ์คลิก
  • ส่วนประกอบของหน้าควรจะฟังเพื่อcurrentPageStoreให้รู้ว่าควรเปลี่ยนไปที่หน้าใด
  • คอมโพเนนต์หน้าโปรไฟล์ผู้ใช้จำเป็นต้องฟังเพื่อcurrentUserProfileStoreให้รู้ว่าข้อมูลโปรไฟล์ผู้ใช้จะแสดงอะไร
  • รายการโพสต์ต้องฟังcurrentPostsStoreเพื่อรับโพสต์ที่โหลด
  • เหตุการณ์การเลื่อนที่ไม่มีที่สิ้นสุดจำเป็นต้องเรียกไฟล์Action.loadMorePosts.

และที่น่าจะสวยมาก ๆ


ขอบคุณสำหรับการเขียน!
Dan Abramov

2
สายไปนิดให้กับบุคคลที่อาจจะ แต่ที่นี่เป็นคนดีบทความอธิบายว่าทำไมเพื่อหลีกเลี่ยงการโทรหาคุณ API โดยตรงจากร้านค้า ฉันยังคงคิดว่าแนวทางปฏิบัติที่ดีที่สุดคืออะไร แต่ฉันคิดว่ามันอาจช่วยให้คนอื่นสะดุดในเรื่องนี้ มีวิธีการต่างๆมากมายเกี่ยวกับร้านค้า
Thijs Koerselman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.