플럭스 아키텍처에서는 스토어 라이프 사이클을 어떻게 관리합니까?
Flux에 대해서 읽고 있습니다만, Todo 앱의 예는 너무 단순해서 요점을 이해할 수 없습니다.
사용자 프로필 페이지가 있는 Facebook과 같은 단일 페이지 앱을 상상해 보십시오.각 사용자 프로파일페이지에서 사용자 정보와 마지막 투고를 무한 스크롤로 표시하려고 합니다.사용자 프로파일 간에 이동할 수 있습니다.
플럭스 아키텍처에서는 이것이 스토어 및 디스패처와 어떻게 대응합니까?
요?PostStore
니면 글글 토토 토토 토? ???스패패? 츠요시사용자 페이지마다 디스패처를 새로 만들까요?글톤톤? 츠요시마지막으로 루트 변경에 대응하여 "페이지별" 스토어의 라이프 사이클을 관리하는 아키텍처는 무엇입니까?
또한 하나의 의사 페이지는 동일한 유형의 데이터 목록을 여러 개 가질 수 있습니다.예를 들어, 프로필 페이지에서 팔로잉과 팔로잉을 모두 표시하고 싶습니다.싱글톤이 어떻게UserStore
과가있??? ?? wouldUserPageStore
해내다followedBy: UserStore
★★★★★★★★★★★★★★★★★」follows: UserStore
Flux 앱에는 디스패처가 1개만 있어야 합니다.모든 데이터는 이 중앙 허브를 통해 흐릅니다.싱글톤 디스패처가 있으면 모든 스토어를 관리할 수 있습니다.이것은 Store #1 업데이트 자체를 필요로 하고 Store #2가 Action과 Store #1의 상태를 기반으로 자체 업데이트를 수행하는 경우에 중요합니다.플럭스는 이 상황이 대규모 응용 프로그램에서 발생할 수 있는 상황이라고 가정합니다.이상적으로는 이러한 상황이 발생하지 않아도 되며, 개발자는 가능하면 이러한 복잡성을 피하기 위해 노력해야 합니다.하지만 싱글톤 디스패처는 때가 되면 그것을 처리할 준비가 되어 있다.
가게도 싱글톤입니다.가능한 한 독립적이고 분리된 상태를 유지해야 합니다. Controller-View에서 쿼리할 수 있는 자기포함형 우주입니다.스토어로 들어가는 유일한 길은 콜백을 통해 디스패처에 등록하는 것입니다.유일한 출구는 getter 기능을 사용하는 것입니다.또한 스토어는 상태가 변경되었을 때 이벤트를 퍼블리시하기 때문에 컨트롤러 뷰는 게터를 사용하여 새로운 상태를 쿼리할 시기를 알 수 있습니다.
앱이 .PostStore
이 스토어는, FB의 뉴스 피드에 가까운 「페이지」(의사 페이지)의 투고를 관리할 수 있어 다른 유저로부터의 투고가 표시됩니다.논리 도메인은 투고 목록이며 모든 투고 목록을 처리할 수 있습니다.의사 페이지에서 의사 페이지로 이동할 때 스토어 상태를 재초기화하여 새로운 상태를 반영하고 싶습니다.의사 , 는 LocalStorage를 합니다.PageStore
다른 모든 저장소를 대기하고 의사 페이지의 모든 저장소에 대해 localStorage와의 관계를 관리한 다음 자체 상태를 업데이트합니다.해 주세요.PageStore
대해 바로 이 입니다.그것은 그 도메인입니다.PostStore
특정 의사 페이지가 캐시되었는지 아닌지는 단순히 알 수 있습니다. 의사 페이지는 해당 도메인이기 때문입니다.
PostStore
initialize()
Dispatcher를 .이 메서드는 첫 번째 초기화인 경우에도 항상 이전 상태를 클리어하고 Action을 통해 Dispatcher를 통해 수신한 데이터를 기반으로 상태를 만듭니다.의 의사 하려면 , 「이행하다」가 하게 될 것입니다.PAGE_UPDATE
으로써 '을 수 .initialize()
로컬 캐시로부터의 데이터 취득, 서버로부터의 데이터 취득, 낙관적인 렌더링, XHR 에러 상태 등에 대해 상세하게 설명합니다만, 이것이 일반적인 생각입니다.
특정 의사 페이지가 응용 프로그램의 모든 Stores를 필요로 하지 않는 경우, 메모리 제약 외에 사용하지 않는 Stores를 파기할 이유가 전혀 없습니다.그러나 상점들은 일반적으로 많은 메모리를 소비하지 않는다.파기하는 컨트롤러 뷰에서 이벤트청취자를 삭제하기만 하면 됩니다.에서 이루어집니다.componentWillUnmount()
★★★★★★ 。
(주의: JSX Harmony 옵션을 사용하여 ES6 구문을 사용하고 있습니다.)
연습삼아, 나는 브라우즈 할 수 있는 샘플 플럭스 앱을 작성했다.Github users
§ 저장소
fisherwebdev의 답변을 기반으로 하지만 API 응답을 정규화하기 위해 사용하는 접근 방식을 반영하고 있습니다.
Flux를 배우면서 시도한 몇 가지 방법을 문서화했습니다.
「로컬 스토리지 API」(「로컬 스토리지 API」).
여기서 특히 관심이 있었던 부분이 몇 가지 있습니다.
- 플럭스 아키텍처와 리액트 라우터를 사용합니다.
- 이동 중에 일부 알려진 정보와 로드 세부 정보가 포함된 사용자 페이지를 표시할 수 있습니다.
- 사용자와 저장소 모두에 대해 페이지 매김을 지원합니다.
- Github의 중첩된 JSON 응답을 정규화자와 구문 분석합니다.
- Content Store에는 액션이 있는 큰 것을 포함할 필요가 없습니다.
- "뒤로"는 즉시 표시됩니다(모든 데이터가 저장소에 있기 때문입니다).
스토어 분류 방법
다른 Flux의 예, 특히 Stores의 예에서 보았던 중복을 피하려고 했습니다.스토어를 논리적으로 3개의 카테고리로 분할하는 것이 도움이 되었습니다.
Content Store에는 모든 앱 엔티티가 저장됩니다.ID가 있는 모든 항목에는 자체 컨텐츠 저장소가 필요합니다.개별 항목을 렌더링하는 구성요소는 컨텐츠 저장소에 새 데이터를 요청합니다.
Content Store는 모든 서버 작업으로부터 개체를 수집합니다.예를들면,UserStore
는, 기동한 액션에 관계없이, 그것이 존재하는지 아닌지를 조사합니다.할 필요가 없다.switch
. Normalizr을 사용하면 API 응답을 이 형식으로 쉽게 정리할 수 있습니다.
// 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, ...]
색인화된 목록 저장소는 목록 저장소와 비슷하지만 일대다 관계를 정의합니다.예를 들어 "사용자 사용자", "저장소의 스타게이저", "사용자 저장소" 등이 있습니다.페이지 번호부여도 취급합니다.
몇 ,, 상예예예예예예예예예예)에도 합니다.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
보일러 플레이트 메서드 및 액션 처리를 제공하여 색인 목록 스토어를 최대한 간단하게 만듭니다.
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
수 「」 「」 「」 등).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;
}
따라서 Reflux에서는 Dispatcher의 개념이 삭제되고 작업 및 저장소를 통과하는 데이터 흐름의 관점에서만 생각하면 됩니다.예.
Actions <-- Store { <-- Another Store } <-- Components
여기서 각 화살표는 데이터 흐름의 청취 방법을 모델링합니다. 즉, 데이터가 반대 방향으로 흐른다는 것을 의미합니다.데이터 흐름의 실제 수치는 다음과 같습니다.
Actions --> Stores --> Components
^ | |
+----------+------------+
만약 가 제대로 했다면, 는 객객 a a a a a a in in in in in 。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
.
그리고 그게 거의 다일거야.
언급URL : https://stackoverflow.com/questions/23591325/in-flux-architecture-how-do-you-manage-store-lifecycle
'source' 카테고리의 다른 글
워드프레스에서 제목으로 투고를 받으려면 어떻게 해야 하나요? (0) | 2023.03.11 |
---|---|
하위 구성요소가 렌더링되었는지 테스트하려면 어떻게 해야 합니까? (0) | 2023.03.11 |
SpringBoot 오류: 드라이버 ClassName=driver.jdbc.driver에 등록된 드라이버입니다.OracleDriver를 찾을 수 없습니다. 직접 인스턴스화를 시도합니다. (0) | 2023.03.11 |
지정 시 패키지에 "프록시"가 있습니다.json은 문자열이어야 합니다. (0) | 2023.03.11 |
유휴 사용자에 따라 Angularjs를 사용한 자동 로그아웃 (0) | 2023.03.11 |