프레임 재설계

지난 몇 년 동안 사이클의 핵심 팀.js(나와)는 줄곧 프레임워크의 구조와 개발자 체험을 재구성해 왔다.올해 2월에 우리는 마침내 문제를 해결할 방안을 찾았는데, 여전히 틀에 부합되는 핵심 사상을 찾았다.
이 블로그 문장은 일련의 새로운 디자인과 그 발전을 보도한 문장 중의 첫 번째 문장이다.이번 호에서 나는 모든 사람을 같은 페이지에 서게 하고 싶다.내가 이전에 묘사한 문제가 어디에 있는지, 그리고 새로운 디자인이 이 문제들을 어떻게 해결하는지.다음 글에서는 새로운 run 함수(프레임워크의 핵심)와 새로운 HTTP 드라이버, 특히 이 함수를 실현할 때 겪는 문제점을 소개합니다.*기침

현상


순환에 익숙한 모든 사람들.js는 이 부분을 넘어갈 수 있습니다. 나머지 부분은 현재 버전의 프레임워크의 작업 방식이기 때문입니다. 응용 프로그램의 모든 내용은 흐름의 개념을 바탕으로 합니다.RxJS에서 유행하는 흐름 유형.응용 프로그램 코드가 하는 일은 외부에서 이벤트 흐름(즉 DOM에서 이벤트나 HTTP 요청의 응답을 클릭)을 읽고 변환하고 조합한 다음에 명령 흐름을 외부로 되돌려주는 것이다(즉, DOM에서 새로운 가상 DOM을 보여주거나 HTTP 요청을 실행하는 것이다).
구체적인 예를 들어 간단한 카운터를 살펴보겠습니다.
function main(sources) {
    const incrementStream = sources.DOM.select(".increment")
        .events("click")
        .mapTo(1);

    const decrementStream = sources.DOM.select(".decrement")
        .events("click")
        .mapTo(-1);

    const valueStream = xs
        .merge(incrementStream, decrementStream)
        .fold((sum, current) => sum + current, 0);

    const domStream = valueStream.map(x =>
        div([
            h2(`The current value is ${x}`),
            button(".increment", "Increment"),
            button(".decrement", "Decrement")
        ])
    );

    return {
        DOM: domStream
    };
}
보시다시피 두 단추의 클릭 이벤트를 열거하였으며, 이 이벤트들을 +1-1으로 변환하였습니다.그리고 우리는 merge 이 두 흐름을 사용하고 fold을 사용하여 모든 숫자를 구화한다(foldarray.fold과 유사하지만 fold은 입력한 모든 숫자를 입력한 후에 현재 값을 보낼 것이지 한 번의 계산 값이 아니다).그리고 모든 흐름을 가져와 가상dom트리로 변환하고 외부에 렌더링합니다.
이런 흐름 중심의 디자인은 약간의 장점이 있다.우선, 모든 응용 프로그램의 논리는 순수 함수이다.DOM API에 직접 액세스하지 않고 타사에 HTTP 요청을 보내지 않으며 외부 세계와 어떠한 상호 작용도 하지 않습니다.모든 것은 원본과 외환을 통해 발생한다(즉 main 함수의 입력과 출력).이것은 우리가 JsDOM 같은 것으로 실제 API를 모의할 필요가 없다는 것을 의미한다. 우리는 프로그램에 입력을 제공하고 출력에서 단언하기만 하면 된다.그 다음으로 비동기 행위를 추가하는 것은 어떠한 복잡성을 증가시키지 않으며 동기 코드는 비동기 코드와 똑같아 보인다.셋째, 맨 위에서 우리는 차원 구조에서 모든 구성 요소가 보내는 명령을 차단하고 수정할 수 있다.이것은 구성 요소가 실행하는 모든 HTTP 요청을 차단하고 머리에 API 영패를 추가할 수 있는 좋은 예이다.또한 타사 API에서 데이터를 가져오지 않도록 속도 제한을 추가할 수도 있습니다.이 기능을 라이브러리에 넣을 수도 있습니다. 이 라이브러리는 프로그램을 포장하고 로그 기록이 있는 새로운 프로그램으로 돌아가는 함수를 제공합니다.이런 모델은 지역사회에서 발전된 것으로 몇 개의 라이브러리에서 이런'메인 패키지'를 제공한다.마지막으로 단방향 데이터 흐름만 있다.모든 데이터는 데이터 원본에서 왔으며, 변환을 거친 후에 송금을 통해 떠난다.명령을 그들의 데이터나 사건으로 거슬러 올라가기 쉽다.

문제.


만약에 외부가 상호작용적이라면 흐르는 미디어의 생각은 매우 효과적이다. 예를 들어 사용자가 언제든지 상호작용을 할 수 있는 DOM에 있어 이것은 매우 좋은 방법이다.그러나 또 다른 외부 효과, 즉 퀴즈 스타일 효과도 있다.가장 간단한 예는 HTTP 요청을 실행하는 것입니다.일반적으로 요청을 보낼 때 결과를 기다리고 데이터를 처리해야 한다.그러나 현재로서는 다음과 같은 요청을 하고 있습니다.
function main(sources) {
    const responseStream = sources.HTTP.select("myRequest");

    const domStream = responseStream.startWith(initialData).map(view);

    const requestStream = sources.DOM.select(".requestButton")
        .events("click")
        .mapTo({
            url: myUrl,
            method: "GET",
            category: "myRequest"
        });

    return {
        DOM: domStream,
        HTTP: requestStream
    };
}
보시다시피 데이터 흐름은 여전히 원본에서 송금으로 엄격하지만 HTTP 부분의 코드는 읽기가 매우 어렵습니다.우선, 우리는 어떤 표시가 있는 응답 (본 예는 myRequest) 을 감청한 후에야 실제 이 응답을 보내는 코드를 볼 수 있다.그것들은 직접 연결된 것이 아니라 완전히 독립된 것이기 때문에, 어떤 요청이 어떤 응답에 속하는지 탭을 사용해야 한다.우리가 진정으로 원하는 것은 유사한 API이다.
function main(sources) {
    const domStream = sources.DOM.select(".requestButton")
        .events("click")
        .map(() => sources.HTTP.get(myUrl))
        .flatten()
        .startWith(initialData)
        .map(view);

    return {
        DOM: domStream
    };
}
이 코드는 이전의 코드와 완전히 같지만, 위에서 시작해서 단계적으로 아래로 내려갈 수 있기 때문에, 읽기 쉽다.그것은 분명히 말했다. "감청 요청 단추의 모든 '클릭' 이벤트를 감청하고, 매번 클릭할 때마다 myUrl에 get 요청을 보냅니다. 일부 초기 데이터부터view 함수를 사용하여 모든 응답을 DOM에 나타냅니다."
그러나 만약 우리가 이렇게 실현한다면, 우리는 흐르는 모든 명령을 검사하고 수정하는 능력을 잃게 될 것이다.보시다시피 HTTP 수신기가 어떤 내용도 되돌려주지 않았기 때문에 위쪽이라도 이 요청을 차단할 수 없습니다.

솔루션


현재 우리가 정한 해결 방안은 명령을 분해하고 이벤트를 제공하는 드라이버입니다.현재 드라이버는 명령 흐름을 입력으로 하고 이벤트 흐름을 되돌려주거나 HTTP와 DOM 등 더 복잡한 드라이버에 대해 되돌려주는 방법을 제공하는 대상을 되돌려줍니다.예를 들어 DOM 드라이버는 DOMSource 대상을 되돌려주고 이 대상은 select()events() 방법을 제공하며 후자는 이벤트 흐름을 되돌려준다.
매우 간단한 예는 다음과 같다.
class DOMSource {
    events(type) {
        return fromEvent(type);
    }
}

function domDriver(commands) {
    commands.subscribe({
        next: renderDOM
    });

    return new DOMSource();
}
이 예에서 fromEvent은 이벤트 탐지기를 추가하고 이벤트 탐지기를 활성화할 때마다 새로운 이벤트를 보냅니다.
새로운 해결 방안은 이 점을 바꾸어 드라이버가 흐름을 입력으로 하고, 흐름을 출력으로 되돌려 달라고 요구했다.더 복잡한 드라이버가 더 좋은 API를 제공하려면 따로 제공할 수 있습니다.이러한 API는 사용자로부터 호출을 드라이버에 보낼 명령으로 변환하고 드라이버에서 이벤트를 가져와 필터링하는 작업을 수행합니다.DOM 예제의 경우 다음과 같습니다.
class DomApi {
    constructor(subject, driverEvents, idGenerator) {
        this.subject = subject;
        this.driverEvents = driverEvents;
        this.idGenerator = idGenerator;
    }

    events(type) {
        const id = this.idGenerator();
        this.subject.send({
            commandType: "attachEventListener",
            type,
            id
        });

        return this.driverEvents.filter(event => event.id === id);
    }
}

function domDriver(commands) {
    const subject = makeSubject();

    commands.subscribe({
        next: command => {
            if (command.commandType === "attachEventListener") {
                document.addEventListener(command.type, event => {
                    subject.send({ ...event, id: command.id });
                });
            } else {
                renderDOM();
            }
        }
    });

    return subject;
}
보시다시피, 드라이버는 API와 완전히 독립되어 있으며, API를 사용하여 드라이버에 명령을 직접 보낼 수도 없습니다.다른 한편, API는 외부 세계와 전혀 상호작용하지 않고 드라이버에 명령만 보내고 사용자가 진정으로 흥미를 느끼는 이벤트를 필터한다.주제가 흐름의 시작과 같다면 send()을 통해 수동으로 이벤트를 흐름에 넣을 수 있습니다.

전모


새로운 디자인이 있으면 너는 자전거를 탈 수 있다.js는 프로그램과 드라이버의 API를 받아들이고 새로운 주 함수를 되돌려줍니다. 이 함수는 이벤트 흐름만 입력하고 명령 흐름만 되돌려줍니다.드라이버의 API는 정확한 명령을 보내고 정확한 이벤트를 읽는 역할을 합니다.이제 HTTP 요청 코드를 검사하여 새 주 함수를 패키지화할 수 있습니다.하지만 이제 이런 코드는 DOM에 추가된 이벤트 탐지기를 차단하고 기록할 수 있습니다!이것은 이전에는 불가능했다.마스터main에 임의의 패키지 코드층을 추가한 후 makeMasterMain()에 건네주면main 함수와 드라이버를 받아들여 연결할 수 있습니다.이제 마스터 함수는 일반 스트림에만 적용되고 API는 더 이상 사용되지 않는다는 점에 유의하십시오.
이전 코드로 돌아가기
function main(sources) {
    const domStream = sources.DOM.select(".requestButton")
        .events("click")
        .map(() => sourcs.HTTP.get(myUrl))
        .flatten()
        .startWith(initialData)
        .map(view);

    return {
        DOM: domStream
    };
}
이것이 바로 다음 주요 버전의 Cycle에서 코드의 실제 모양입니다.js!이 동시에, 프로그램 코드 (즉 run()) 에서 현식으로 되돌아오지 않아도, 프로그램을 떠나는 모든 요청을 차단/수정/기록할 수 있습니다.이 점에 도달하는 데는 시간이 좀 걸리지만, 나는 최종 구조에 대해 매우 만족한다.사용자 코드는 쉽게 읽을 수 있고 프레임워크 코드도 좀 간단해졌다.
다음 부분에서 나는 HTTP: requestStreamrun() 함수, 그리고 동기화 코드를 어떻게 사용하여 경쟁 조건을 방지하는지 토론할 것이다.읽어 주셔서 감사합니다. 언제든지 질문해 주십시오.

좋은 웹페이지 즐겨찾기