[javascript] Flux 앱에서 Ajax 요청은 어디에서해야합니까?
플럭스 아키텍처로 react.js 응용 프로그램을 만들고 있는데 서버에서 데이터 요청을 언제 어디서 수행해야하는지 파악하려고합니다. 이에 대한 예가 있습니까? (TODO 앱이 아닙니다!)
답변
저는 액션 제작자에 비동기 쓰기 작업을 저장하고 상점에서 비동기 읽기 작업을 수행하는 데 큰 지지자입니다. 목표는 상점 상태 수정 코드를 완전히 동기식 조치 핸들러에 유지하는 것입니다. 따라서 추론하기 쉽고 단위 테스트가 간단합니다. 여러 개의 동시 요청을 동일한 엔드 포인트 (예 : 이중 판독)로 방지하기 위해 실제 요청 처리를 여러 요청을 방지하기 위해 약속을 사용하는 별도의 모듈로 옮길 것입니다. 예를 들면 다음과 같습니다.
class MyResourceDAO {
get(id) {
if (!this.promises[id]) {
this.promises[id] = new Promise((resolve, reject) => {
// ajax handling here...
});
}
return this.promises[id];
}
}
상점의 읽기에는 비동기 함수가 포함되지만 상점이 비동기 핸들러에서 자체적으로 업데이트하지 않고 대신 조치가 실행되고 응답이 도착할 때만 조치가 실행된다는 중요한 경고 가 있습니다. 이 조치에 대한 핸들러는 실제 상태 수정을 수행합니다.
예를 들어, 구성 요소는 다음을 수행 할 수 있습니다.
getInitialState() {
return { data: myStore.getSomeData(this.props.id) };
}
상점은 아마도 다음과 같은 방법을 구현했을 것입니다.
class Store {
getSomeData(id) {
if (!this.cache[id]) {
MyResurceDAO.get(id).then(this.updateFromServer);
this.cache[id] = LOADING_TOKEN;
// LOADING_TOKEN is a unique value of some kind
// that the component can use to know that the
// value is not yet available.
}
return this.cache[id];
}
updateFromServer(response) {
fluxDispatcher.dispatch({
type: "DATA_FROM_SERVER",
payload: {id: response.id, data: response}
});
}
// this handles the "DATA_FROM_SERVER" action
handleDataFromServer(action) {
this.cache[action.payload.id] = action.payload.data;
this.emit("change"); // or whatever you do to re-render your app
}
}
답변
Fluxxor에는 API와의 비동기 통신 예제 가 있습니다.
이 블로그 게시물 에 대해 이야기하고 React의 블로그에 실 렸습니다.
백엔드와의 프론트 엔드 소프트웨어 동기화가 여전히 고통스럽기 때문에 아직 명확하게 답변되지 않은 매우 중요하고 어려운 질문입니다.
JSX 컴포넌트에서 API 요청을해야합니까? 백화점? 다른 장소?
상점에서 요청을 수행한다는 것은 주어진 조치에 대해 2 개의 상점이 동일한 데이터를 필요로하는 경우 2 개의 유사한 요청을 발행한다는 것을 의미합니다 (상점 사이에 종속성을 도입하지 않는 한 실제로는 마음에 들지 않습니다 )
제 경우에는 Q 약속을 행동의 페이로드로 넣는 것이 매우 편리하다는 것을 알았습니다.
- 내 작업은 직렬화 할 필요가 없습니다 (이벤트 로그를 보관하지 않고 이벤트 소싱의 이벤트 재생 기능이 필요하지 않습니다)
- 다른 조치 / 이벤트 (요청 발생 / 요청 완료 / 요청 실패)가 없어야하며 동시 요청이 발생 될 수있을 때 상관 ID를 사용하여 일치해야합니다.
- 여러 상점이 상점간에 종속성을 유발하지 않고 동일한 요청의 완료를 청취 할 수 있습니다 (그러나 캐싱 계층을 도입하는 것이 더 나을 수 있습니까?).
Ajax는 EVIL입니다
나는 Ajax가 추론하기가 매우 어렵 기 때문에 가까운 미래에 점점 더 적게 사용될 것이라고 생각합니다. 옳은 길? 분산 시스템의 일부로 장치를 고려할 때이 아이디어를 처음 발견 한 곳을 알지 못합니다 (이 영감을주는 Chris Granger 비디오에서 ).
생각 해봐 이제 확장 성을 위해 스토리지 엔진으로서 최종 일관성을 갖춘 분산 시스템을 사용합니다 ( CAP 정리를 이길 수없고 종종 사용 가능하기를 원하기 때문에). 이러한 시스템은 서로의 폴링을 통해 동기화되지 않고 (합의 작업 제외) 분산 시스템의 모든 구성원을 일관되게 만들기 위해 CRDT 및 이벤트 로그와 같은 구조를 사용합니다 (구성원은 충분한 시간이 주어지면 동일한 데이터로 수렴됩니다) .
이제 모바일 장치 나 브라우저가 무엇인지 생각해보십시오. 네트워크 대기 시간 및 네트워크 파티셔닝이 발생할 수있는 분산 시스템의 구성원 일뿐입니다. (즉, 지하철에서 스마트 폰을 사용하고 있습니다)
네트워크 파티션 및 네트워크 속도 허용 데이터베이스를 구축 할 수 있다면 (여전히 고립 된 노드에 대한 쓰기 작업을 수행 할 수 있음) 이러한 개념에서 영감을 얻은 프런트 엔드 소프트웨어 (모바일 또는 데스크탑)를 구축 할 수 있습니다. 응용 프로그램 기능을 사용할 수없는 상자의.
데이터베이스가 프론트 엔드 응용 프로그램을 아키텍처 화하는 방법에 대해 영감을 얻어야합니다. 주목할 점은 이러한 앱이 서로에게 데이터를 전송하기 위해 POST 및 PUT 및 GET ajax 요청을 수행하지 않고 이벤트 로그 및 CRDT를 사용하여 최종 일관성을 유지한다는 것입니다.
왜 프론트 엔드에서 그렇게하지 않습니까? 백엔드가 이미 그 방향으로 움직이고 있으며 Kafka와 같은 도구는 큰 플레이어가 대규모로 채택하고 있습니다. 이것은 어떻게 든 이벤트 소싱 / CQRS / DDD와 관련이 있습니다.
Kafka 작가의 멋진 기사를 확인하여 자신을 확신 시키십시오.
어쩌면 Ajax 요청을 발생시키는 대신 서버에 명령을 보내고 서버 이벤트 스트림 (예 : 웹 소켓을 통해)을 수신하는 것으로 시작할 수 있습니다.
나는 Ajax 요청에 결코 익숙하지 않았다. 우리가 반응함에 따라 개발자는 기능적인 프로그래머 인 경향이 있습니다. 프론트 엔드 애플리케이션의 “진리 소스”인 로컬 데이터에 대해 추론하기는 어렵다고 생각하지만 실제 진실 소스는 실제로 서버 데이터베이스에 있으며 “로컬”진실 소스는 이미 구식 일 수 있습니다. 당신이 그것을받을 때, 당신이 절름발이 새로 고침 버튼을 누르지 않으면 진정한 진실 가치의 근원으로 수렴하지 않을 것입니다 …이 엔지니어링입니까?
그러나 몇 가지 명백한 이유로 이러한 것을 디자인하는 것은 여전히 어렵습니다.
- 모바일 / 브라우저 클라이언트는 리소스가 제한되어 있으며 모든 데이터를 로컬에 저장할 필요는 없습니다.
- 클라이언트는 분산 시스템의 모든 데이터를 볼 수 없으므로 보안상의 이유로 수신되는 이벤트를 필터링해야합니다.
답변
활동 작성자 또는 상점에서 데이터를 호출 할 수 있습니다. 중요한 것은 응답을 직접 처리하지 않고 오류 / 성공 콜백에서 조치를 작성하는 것입니다. 상점에서 직접 응답을 처리하면 취성 설계가 더 복잡해집니다.
답변
Fluxxor ajax example 에서 Binary Muse의 예제를 사용하고 있습니다. 다음은 동일한 접근법을 사용하는 매우 간단한 예입니다.
간단한 제품 저장소에 일부 제품 작업 과 제품 저장소의 변경 사항에 모두 응답하는 하위 구성 요소가있는 컨트롤러 뷰 구성 요소가 있습니다 . 예를 들어 product-slider , product-list 및 product-search components.
가짜 제품 클라이언트
다음은 실제 엔드 포인트 반품 제품 호출을 대체 할 수있는 가짜 클라이언트입니다.
var ProductClient = {
load: function(success, failure) {
setTimeout(function() {
var ITEMS = require('../data/product-data.js');
success(ITEMS);
}, 1000);
}
};
module.exports = ProductClient;
제품 판매점
여기에 제품 저장소가 있습니다. 분명히 이것은 매우 작은 저장소입니다.
var Fluxxor = require("fluxxor");
var store = Fluxxor.createStore({
initialize: function(options) {
this.productItems = [];
this.bindActions(
constants.LOAD_PRODUCTS_SUCCESS, this.onLoadSuccess,
constants.LOAD_PRODUCTS_FAIL, this.onLoadFail
);
},
onLoadSuccess: function(data) {
for(var i = 0; i < data.products.length; i++){
this.productItems.push(data.products[i]);
}
this.emit("change");
},
onLoadFail: function(error) {
console.log(error);
this.emit("change");
},
getState: function() {
return {
productItems: this.productItems
};
}
});
module.exports = store;
이제 AJAX 요청 및 성공시 LOAD_PRODUCTS_SUCCESS 조치가 상점으로 제품을 리턴하는 제품 조치를 실행합니다.
제품 조치
var ProductClient = require("../fake-clients/product-client");
var actions = {
loadProducts: function() {
ProductClient.load(function(products) {
this.dispatch(constants.LOAD_PRODUCTS_SUCCESS, {products: products});
}.bind(this), function(error) {
this.dispatch(constants.LOAD_PRODUCTS_FAIL, {error: error});
}.bind(this));
}
};
module.exports = actions;
따라서이 this.getFlux().actions.productActions.loadProducts()
상점을 청취하는 모든 컴포넌트에서 호출 하면 제품이로드됩니다.
addProduct(id)
removeProduct(id)
같은 패턴을 따르는 등의 사용자 상호 작용에 반응하는 다른 작업을 상상할 수 있습니다.
구현하기가 약간 까다로 웠지만 매장이 100 % 동기를 유지하는 데 도움이 되었기 때문에이 예제가 도움이 되길 바랍니다.
답변
관련 질문에 대답했습니다 : 중첩 된 API 호출을 플럭스로 처리하는 방법
행동은 변화를 일으키는 것으로 생각되지 않습니다. 그들은 외부 세계의 변화를 응용 프로그램에 알리는 신문과 같아야하며, 그런 다음 응용 프로그램이 그 뉴스에 응답합니다. 상점 자체가 변경됩니다. 행동은 그들에게 알려줍니다.
Flux 제작자 Bill Fisher https://stackoverflow.com/a/26581808/4258088
기본적으로해야 할 일은 작업을 통해 필요한 데이터를 명시하는 것입니다. 상점이 조치에 대한 정보를 받으면 일부 데이터를 가져와야하는지 여부를 결정해야합니다.
상점은 필요한 모든 데이터를 축적 / 페치해야합니다. 그러나 상점이 데이터를 요청하고 응답을 얻은 후에는 상점이 응답을 직접 처리 / 저장하는 것과 반대로 페치 된 데이터로 조치 자체를 트리거해야합니다.
상점은 다음과 같이 보일 수 있습니다.
class DataStore {
constructor() {
this.data = [];
this.bindListeners({
handleDataNeeded: Action.DATA_NEEDED,
handleNewData: Action.NEW_DATA
});
}
handleDataNeeded(id) {
if(neededDataNotThereYet){
api.data.fetch(id, (err, res) => {
//Code
if(success){
Action.newData(payLoad);
}
}
}
}
handleNewData(data) {
//code that saves data and emit change
}
}