(หมายเหตุ: ฉันใช้ไวยากรณ์ ES6 โดยใช้ตัวเลือก JSX Harmony)
ในฐานะที่เป็นแบบฝึกหัดฉันได้เขียนแอป Flux ตัวอย่างที่อนุญาตให้เรียกดูGithub users
และทำซ้ำ
มันขึ้นอยู่กับคำตอบของ fisherwebdevแต่ยังสะท้อนให้เห็นถึงวิธีการที่ฉันใช้สำหรับการตอบสนอง API ที่เป็นมาตรฐาน
ฉันจัดทำเอกสารสองสามวิธีที่ฉันได้ลองใช้ในขณะที่เรียนรู้ Flux
ฉันพยายามทำให้มันใกล้เคียงกับโลกแห่งความเป็นจริง (การแบ่งหน้าไม่มี localStorage API ปลอม)
มีบางส่วนที่ฉันสนใจเป็นพิเศษที่นี่:
ฉันจัดประเภทร้านค้าอย่างไร
ฉันพยายามหลีกเลี่ยงการทำซ้ำบางอย่างที่ฉันเคยเห็นในตัวอย่าง Flux อื่น ๆ โดยเฉพาะในร้านค้า ฉันพบว่ามีประโยชน์ในการแบ่งร้านค้าออกเป็นสามประเภทอย่างมีเหตุผล:
ที่เก็บเนื้อหาถือเอนทิตีแอปทั้งหมด ทุกสิ่งที่มี ID จำเป็นต้องมีที่เก็บเนื้อหาของตัวเอง คอมโพเนนต์ที่แสดงผลแต่ละรายการจะขอข้อมูลใหม่ที่เก็บเนื้อหา
ที่เก็บเนื้อหาเก็บเกี่ยวอ็อบเจ็กต์จากการกระทำของเซิร์ฟเวอร์ทั้งหมด ตัวอย่างเช่นUserStore
มีลักษณะเป็นaction.response.entities.users
ถ้ามันมีอยู่โดยไม่คำนึงถึงการที่การกระทำยิง ไม่จำเป็นต้องมีswitch
ไฟล์. Normalizrทำให้ง่ายต่อการแบน API repons ใด ๆ ให้เป็นรูปแบบนี้
{
7: {
id: 7,
name: 'Dan'
},
...
}
รายชื่อร้านค้าติดตาม ID ของเอนทิตีที่ปรากฏในรายการส่วนกลางบางรายการ (เช่น "ฟีด", "การแจ้งเตือนของคุณ") ในโครงการนี้ฉันไม่มีร้านค้าแบบนั้น แต่ฉันคิดว่าจะพูดถึงพวกเขาอยู่ดี พวกเขาจัดการกับเลขหน้า
พวกเขามักจะตอบสนองต่อการกระทำเพียงไม่กี่ (เช่นREQUEST_FEED
, REQUEST_FEED_SUCCESS
, REQUEST_FEED_ERROR
)
[7, 10, 5, ...]
Indexed List Storesเป็นเหมือน List Stores แต่กำหนดความสัมพันธ์แบบหนึ่งต่อกลุ่ม ตัวอย่างเช่น "ผู้ติดตามของผู้ใช้", "stargazers ของที่เก็บ", "ที่เก็บของผู้ใช้" นอกจากนี้ยังจัดการการแบ่งหน้า
พวกเขายังตอบสนองต่อการได้ตามปกติเพียงไม่กี่การกระทำ (เช่นREQUEST_USER_REPOS
, REQUEST_USER_REPOS_SUCCESS
, REQUEST_USER_REPOS_ERROR
)
ในแอปโซเชียลส่วนใหญ่คุณจะมีแอปเหล่านี้มากมายและคุณต้องการสร้างแอปขึ้นมาใหม่ได้อย่างรวดเร็ว
{
2: [7, 10, 5, ...],
6: [7, 1, 2, ...],
...
}
หมายเหตุ: นี่ไม่ใช่ชั้นเรียนจริงหรือบางอย่าง เป็นเพียงวิธีคิดเกี่ยวกับร้านค้า ฉันได้ให้ความช่วยเหลือบางอย่าง
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]));
}
}
}
จัดเก็บสถานะการแบ่งหน้าและบังคับใช้การยืนยันบางอย่าง (ไม่สามารถดึงข้อมูลหน้าขณะดึงข้อมูล ฯลฯ )
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++;
}
}
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;
}
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
}))
);
}
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
}))
);
}
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
};
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;
}
UserListStore
กับผู้ใช้ที่เกี่ยวข้องทั้งหมดในนั้น และผู้ใช้แต่ละคนจะมีแฟล็กบูลีนสองสามอันที่อธิบายความสัมพันธ์กับโปรไฟล์ผู้ใช้ปัจจุบัน บางอย่าง{ follower: true, followed: false }
เช่น วิธีการgetFolloweds()
และgetFollowers()
จะดึงชุดผู้ใช้ต่างๆที่คุณต้องการสำหรับ UI