1단계(설명)
TC39 제안 챔피언: Daniel Ehrenberg, Yehuda Katz, Jatin Ramanathan, Shay Lewis, Kristen Hewell Garrett, Dominic Gannaway, Preston Sego, Milo M, Rob Eisenberg
원저자: Rob Eisenberg 및 Daniel Ehrenberg
이 문서는 ES2015에서 TC39에 의해 표준화된 Promises 이전의 Promises/A+ 노력과 유사한 JavaScript의 신호에 대한 초기 공통 방향을 설명합니다. 폴리필을 사용해 직접 시도해 보세요.
Promises/A+와 마찬가지로 이 노력은 JavaScript 생태계를 조정하는 데 중점을 둡니다. 이러한 조정이 성공하면 해당 경험을 바탕으로 표준이 나타날 수 있습니다. 여러 프레임워크 작성자가 반응성 핵심을 뒷받침할 수 있는 공통 모델에 대해 협력하고 있습니다. 현재 초안은 Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz 등의 작성자/유지관리자의 디자인 입력을 기반으로 합니다.
Promises/A+와는 다르게, 우리는 일반적인 개발자용 표면 API를 해결하려고 하는 것이 아니라 기본 신호 그래프의 정확한 핵심 의미를 해결하려고 합니다. 이 제안에는 완전히 구체적인 API가 포함되어 있지만 API는 대부분의 애플리케이션 개발자를 대상으로 하지 않습니다. 대신, 여기서 신호 API는 공통 신호 그래프 및 자동 추적 메커니즘을 통해 상호 운용성을 제공하여 프레임워크를 구축하는 데 더 적합합니다.
이 제안의 계획은 1단계를 넘어 진행하기 전에 여러 프레임워크로의 통합을 포함하여 중요한 초기 프로토타입을 수행하는 것입니다. 우리는 신호가 여러 프레임워크에서 실제로 사용하기에 적합하고 프레임워크에 비해 실질적인 이점을 제공하는 경우에만 신호를 표준화하는 데 관심이 있습니다. 신호를 제공했습니다. 우리는 중요한 초기 프로토타입을 통해 이러한 정보를 얻을 수 있기를 바랍니다. 자세한 내용은 아래 "현황 및 개발 계획"을 참조하세요.
복잡한 사용자 인터페이스(UI)를 개발하려면 JavaScript 애플리케이션 개발자는 효율적인 방법으로 상태를 애플리케이션의 뷰 계층에 저장, 계산, 무효화, 동기화 및 푸시해야 합니다. UI에는 일반적으로 단순한 값을 관리하는 것 이상의 작업이 포함되지만, 다른 값의 복잡한 트리에 의존하는 계산된 상태를 렌더링하거나 자체적으로 계산되는 상태도 포함되는 경우가 많습니다. Signals의 목표는 개발자가 이러한 반복적인 세부 사항보다는 비즈니스 로직에 집중할 수 있도록 이러한 애플리케이션 상태를 관리하기 위한 인프라를 제공하는 것입니다.
신호와 유사한 구성은 UI가 아닌 컨텍스트, 특히 불필요한 재빌드를 피하기 위한 빌드 시스템에서도 유용한 것으로 독립적으로 밝혀졌습니다.
신호는 애플리케이션에서 업데이트를 관리할 필요성을 제거하기 위해 반응형 프로그래밍에 사용됩니다.
상태 변경에 따라 업데이트하기 위한 선언적 프로그래밍 모델입니다.
반응성이란 무엇입니까? .
counter
변수가 주어지면 카운터가 짝수인지 홀수인지 DOM에 렌더링하려고 합니다. counter
변경될 때마다 최신 패리티로 DOM을 업데이트하려고 합니다. Vanilla JS에서는 다음과 같은 내용이 있을 수 있습니다.
let counter = 0;const setCounter = (값) => { 카운터 = 값; render();};const isEven = () => (카운터 & 1) == 0;const parity = () => isEven() ? "even" : "odd";const render = () => element.innerText = parity();// 카운터에 대한 외부 업데이트 시뮬레이션...setInterval(() => setCounter(counter + 1), 1000);
이것은 여러 가지 문제가 있습니다 ...
counter
설정은 시끄럽고 상용구가 많습니다.
counter
상태는 렌더링 시스템과 긴밀하게 연결되어 있습니다.
counter
변경되지만 parity
변경되지 않는 경우(예: 카운터가 2에서 4로 변경되는 경우) 불필요한 패리티 계산과 불필요한 렌더링을 수행합니다.
counter
업데이트될 때 UI의 다른 부분이 렌더링되기를 원한다면 어떻게 될까요?
UI의 다른 부분이 isEven
또는 parity
에만 의존한다면 어떻게 될까요?
비교적 간단한 이 시나리오에서도 많은 문제가 빠르게 발생합니다. counter
에 pub/sub를 도입하여 이러한 문제를 해결하려고 할 수 있습니다. 이를 통해 counter
의 추가 소비자가 상태 변경에 대한 자신의 반응을 추가하도록 구독할 수 있습니다.
그러나 우리는 여전히 다음과 같은 문제에 봉착해 있습니다.
parity
에만 의존하는 렌더링 함수는 실제로 counter
를 구독해야 한다는 것을 "알아야" 합니다.
counter
와 직접 상호작용하지 않고 isEven
또는 parity
만을 기반으로 UI를 업데이트하는 것은 불가능합니다.
상용구를 늘렸습니다. 무언가를 사용할 때마다 단순히 함수를 호출하거나 변수를 읽는 것이 아니라 구독하고 업데이트를 수행하는 것이 중요합니다. 구독 취소 관리도 특히 복잡합니다.
이제 counter
뿐만 아니라 isEven
및 parity
에도 pub/sub를 추가하여 몇 가지 문제를 해결할 수 있습니다. 그런 다음 isEven
counter
로, parity
isEven
으로, render
parity
로 구독해야 합니다. 불행하게도 상용구 코드가 폭증했을 뿐만 아니라 엄청난 양의 구독 기록이 남아 있고 모든 것을 올바른 방법으로 정리하지 않으면 잠재적인 메모리 누수 재앙이 발생할 수 있습니다. 그래서 우리는 몇 가지 문제를 해결했지만 완전히 새로운 범주의 문제와 많은 코드를 만들었습니다. 설상가상으로 우리는 시스템의 모든 상태에 대해 이 전체 프로세스를 거쳐야 합니다.
모델 및 뷰에 대한 UI의 데이터 바인딩 추상화는 JS 또는 웹 플랫폼에 내장된 메커니즘이 없음에도 불구하고 오랫동안 여러 프로그래밍 언어에 걸쳐 UI 프레임워크의 핵심이었습니다. JS 프레임워크 및 라이브러리 내에서는 이 바인딩을 표현하기 위한 다양한 방법에 걸쳐 많은 양의 실험이 있었고 경험에 따르면 상태 또는 계산 셀을 나타내는 일류 데이터 유형과 결합하여 단방향 데이터 흐름의 힘이 나타났습니다. 현재는 종종 "신호"라고 불리는 다른 데이터에서 파생됩니다. 이 일류 반응형 가치 접근 방식은 2010년 Knockout을 통해 오픈 소스 JavaScript 웹 프레임워크에서 처음으로 대중적으로 등장한 것으로 보입니다. 그 이후 몇 년 동안 많은 변형과 구현이 만들어졌습니다. 지난 3~4년 동안 Signal 기본 및 관련 접근 방식은 거의 모든 최신 JavaScript 라이브러리 또는 프레임워크가 하나 또는 다른 이름으로 비슷한 것을 가지면서 더욱 주목을 받았습니다.
신호를 이해하기 위해 아래에 자세히 설명된 신호 API를 사용하여 다시 상상한 위의 예를 살펴보겠습니다.
const counter = new Signal.State(0);const isEven = new Signal.Compulated(() => (counter.get() & 1) == 0);const parity = new Signal.Compulated(() => isEven .get() ? "even" : "odd");// 라이브러리 또는 프레임워크는 다른 신호 기본 요소를 기반으로 효과를 정의합니다. 함수 선언 effect(cb: () => void): (() => void); effect(() => element.innerText = parity.get());// counter에 대한 외부 업데이트 시뮬레이션...setInterval(() => counter.set(counter.get() + 1), 1000 );
우리가 바로 볼 수 있는 몇 가지 사항이 있습니다:
이전 예에서 counter
변수 주변의 시끄러운 상용구를 제거했습니다.
값, 계산, 부작용을 처리하는 통합 API가 있습니다.
counter
와 render
사이에는 순환 참조 문제나 거꾸로 된 종속성이 없습니다.
수동으로 구독할 필요도 없고 장부가 필요하지도 않습니다.
부작용 타이밍/스케줄을 제어하는 방법이 있습니다.
신호는 API 표면에서 볼 수 있는 것보다 훨씬 더 많은 것을 제공합니다.
자동 종속성 추적 - 계산된 신호는 해당 신호가 단순한 값이든 다른 계산이든 관계없이 종속된 다른 신호를 자동으로 검색합니다.
지연 평가 - 계산은 선언될 때 열심히 평가되지 않으며 종속성이 변경될 때 즉시 평가되지도 않습니다. 해당 값이 명시적으로 요청된 경우에만 평가됩니다.
메모 - 계산된 신호는 마지막 값을 캐시하므로 종속성이 변경되지 않은 계산은 액세스 횟수에 관계없이 다시 평가할 필요가 없습니다.
각 신호 구현에는 계산된 신호를 평가할 때 발생하는 소스를 추적하기 위한 자체 자동 추적 메커니즘이 있습니다. 이로 인해 서로 다른 프레임워크 간에 모델, 구성 요소 및 라이브러리를 공유하기가 어렵습니다. 즉, 신호가 일반적으로 JS 프레임워크의 일부로 구현된다는 점을 고려하면 뷰 엔진에 잘못된 결합이 발생하는 경향이 있습니다.
이 제안의 목표는 렌더링 뷰에서 리액티브 모델을 완전히 분리하여 개발자가 UI가 아닌 코드를 다시 작성하지 않고도 새로운 렌더링 기술로 마이그레이션하거나 JS에서 공유 리액티브 모델을 개발하여 다양한 컨텍스트에 배포할 수 있도록 하는 것입니다. 불행하게도 버전 관리와 복제로 인해 JS 수준 라이브러리를 통해 강력한 공유 수준에 도달하는 것은 비현실적인 것으로 나타났습니다. 내장된 기능은 더 강력한 공유 보장을 제공합니다.
일반적으로 사용되는 라이브러리가 내장되어 있기 때문에 더 적은 코드를 제공하는 것은 항상 작은 잠재적 성능 향상이지만 신호 구현은 일반적으로 매우 작기 때문에 이 효과가 매우 클 것으로 예상하지 않습니다.
우리는 Signal 관련 데이터 구조 및 알고리즘의 기본 C++ 구현이 JS에서 달성 가능한 것보다 상수 요소에 의해 약간 더 효율적일 수 있다고 의심합니다. 그러나 폴리필에 존재할 내용에 비해 알고리즘 변경은 예상되지 않습니다. 여기서 엔진은 마술처럼 보일 것으로 예상되지 않으며 반응성 알고리즘 자체는 잘 정의되고 모호하지 않습니다.
챔피언 그룹은 다양한 신호 구현을 개발하고 이를 사용하여 이러한 성능 가능성을 조사할 것으로 기대합니다.
기존 JS 언어 Signal 라이브러리를 사용하면 다음과 같은 항목을 추적하기 어려울 수 있습니다.
오류의 원인 체인을 보여주는 계산된 신호 체인의 호출 스택
신호가 다른 신호에 의존하는 경우의 참조 그래프 - 메모리 사용량을 디버깅할 때 중요
내장된 신호를 사용하면 JS 런타임 및 DevTools가 브라우저에 내장되어 있든 공유 확장을 통해든 상관없이 특히 디버깅이나 성능 분석을 위해 신호 검사에 대한 향상된 지원을 잠재적으로 가질 수 있습니다. 요소 검사기, 성능 스냅샷 및 메모리 프로파일러와 같은 기존 도구를 업데이트하여 정보 표시에서 신호를 구체적으로 강조할 수 있습니다.
일반적으로 JavaScript에는 상당히 최소한의 표준 라이브러리가 있지만 TC39의 추세는 JS를 고품질의 내장 기능 세트를 사용하여 "배터리 포함" 언어에 더 가깝게 만드는 것이었습니다. 예를 들어 Temporal은 moment.js를 대체하고 있으며 Array.prototype.flat
및 Object.groupBy
와 같은 여러 작은 기능이 많은 lodash 사용 사례를 대체하고 있습니다. 이점에는 더 작은 번들 크기, 향상된 안정성 및 품질, 새 프로젝트에 참여할 때 배울 내용이 적고 JS 개발자 간에 일반적으로 공통되는 어휘가 포함됩니다.
W3C와 브라우저 구현자의 현재 작업은 기본 템플릿을 HTML(DOM 부품 및 템플릿 인스턴스화)로 가져오는 것입니다. 또한 W3C 웹 구성 요소 CG는 완전히 선언적인 HTML API를 제공하기 위해 웹 구성 요소를 확장할 가능성을 모색하고 있습니다. 이 두 가지 목표를 모두 달성하려면 결국 HTML에 반응형 기본 요소가 필요하게 됩니다. 또한 신호 통합을 통해 DOM에 대한 많은 인체공학적 개선을 상상할 수 있으며 커뮤니티에서 요청해 왔습니다.
이 통합은 이 제안 자체의 일부가 아니라 나중에 제공될 별도의 노력입니다.
표준화 노력은 브라우저를 변경하지 않고도 "커뮤니티" 수준에서만 도움이 될 수 있습니다. Signals의 노력은 반응성, 알고리즘 및 상호 운용성의 본질에 대한 심층적인 토론을 위해 다양한 프레임워크 작성자를 모으는 것입니다. 이는 이미 유용했으며 JS 엔진 및 브라우저에 포함하는 것을 정당화하지 않습니다. 활성화된 생태계 정보 교환 이상의 상당한 이점이 있는 경우에만 신호를 JavaScript 표준에 추가해야 합니다.
기존 Signal 라이브러리는 핵심적으로 서로 크게 다르지 않은 것으로 나타났습니다. 이 제안은 많은 라이브러리의 중요한 특성을 구현하여 성공을 기반으로 하는 것을 목표로 합니다.
상태를 나타내는 신호 유형, 즉 쓰기 가능한 신호입니다. 이는 다른 사람이 읽을 수 있는 값입니다.
다른 신호에 의존하고 느리게 계산되고 캐시되는 계산된/메모/파생된 신호 유형입니다.
계산은 게으릅니다. 즉, 계산된 신호는 종속성 중 하나가 변경될 때 기본적으로 다시 계산되지 않고 누군가가 실제로 신호를 읽는 경우에만 실행됩니다.
계산에는 "글리치가 없습니다". 즉, 불필요한 계산이 수행되지 않습니다. 이는 애플리케이션이 계산된 신호를 읽을 때 그래프에서 잠재적으로 더러운 부분을 토폴로지 정렬하여 중복을 제거한다는 것을 의미합니다.
계산은 캐시됩니다. 즉, 종속성이 마지막으로 변경된 후 종속성이 변경되지 않은 경우 계산된 신호는 액세스 시 다시 계산되지 않습니다 .
계산된 신호와 상태 신호에 대한 사용자 정의 비교가 가능하여 이에 의존하는 추가 계산 신호를 업데이트해야 하는 경우를 확인할 수 있습니다.
계산된 신호에 종속성(또는 중첩된 종속성) 중 하나가 있는 조건에 대한 반응은 "더러워지고" 변경되며, 이는 신호의 값이 오래되었을 수 있음을 의미합니다.
이 반응은 나중에 수행할 더 중요한 작업을 예약하기 위한 것입니다.
효과는 이러한 반응과 프레임워크 수준 스케줄링 측면에서 구현됩니다.
계산된 신호에는 이러한 반응 중 하나의 (중첩) 종속성으로 등록되었는지 여부에 반응하는 기능이 필요합니다.
JS 프레임워크를 활성화하여 자체 예약을 수행합니다. 약속 스타일 내장형 강제 예약이 없습니다.
프레임워크 논리를 기반으로 이후 작업을 예약하려면 동기식 반응이 필요합니다.
쓰기는 동기식이며 즉시 적용됩니다(일괄 쓰기를 수행하는 프레임워크가 이를 수행할 수 있음).
효과가 "더티"할 수 있는지 여부를 확인하는 것과 실제로 효과를 실행하는 것을 분리하는 것이 가능합니다(2단계 효과 스케줄러 활성화).
기록할 종속성을 트리거 하지 않고 신호를 읽는 기능( untrack
)
신호/반응성을 사용하는 다양한 코드베이스의 구성을 활성화합니다. 예:
추적/반응성 자체가 진행되는 한 여러 프레임워크를 함께 사용합니다(모듈로 생략, 아래 참조).
프레임워크 독립적 반응형 데이터 구조(예: 재귀적 반응형 저장소 프록시, 반응형 맵 및 집합 및 배열 등)
동기식 반응의 순진한 오용을 억제/금지합니다.
건전성 위험: 부적절하게 사용되면 "결함"이 노출될 수 있습니다. 신호가 설정될 때 렌더링이 즉시 수행되면 불완전한 애플리케이션 상태가 최종 사용자에게 노출될 수 있습니다. 따라서 이 기능은 애플리케이션 로직이 완료된 후 나중에 작업을 지능적으로 예약하는 데에만 사용해야 합니다.
해결책: 동기식 반응 콜백 내에서 신호를 읽고 쓰는 것을 허용하지 않습니다.
untrack
해제하고 건전하지 않은 특성을 표시하지 마세요.
건전성 위험: 다른 신호에 따라 값이 달라지지만 해당 신호가 변경될 때 업데이트되지 않는 계산된 신호를 생성할 수 있습니다. 추적되지 않은 액세스가 계산 결과를 변경하지 않을 때 사용해야 합니다.
해결 방법: API 이름에 "안전하지 않음"이 표시되어 있습니다.
참고: 이 제안은 건전성 위험에도 불구하고 읽기 이후의 쓰기를 제한하지 않고 계산된 신호와 효과 신호에서 신호를 읽고 쓸 수 있도록 허용합니다. 이 결정은 프레임워크와의 통합에서 유연성과 호환성을 유지하기 위해 취해졌습니다.
신호/반응성 메커니즘을 구현하려면 여러 프레임워크의 견고한 기반이 되어야 합니다.
재귀 저장소 프록시, 데코레이터 기반 클래스 필드 반응성, .value
및 [state, setState]
스타일 API를 위한 좋은 기반이 되어야 합니다.
의미론은 다양한 프레임워크에서 지원되는 유효한 패턴을 표현할 수 있습니다. 예를 들어, 이러한 신호는 즉시 반영된 쓰기 또는 나중에 일괄 처리되어 적용되는 쓰기의 기초가 될 수 있어야 합니다.
이 API를 JavaScript 개발자가 직접 사용할 수 있다면 좋을 것 같습니다.
아이디어: 모든 후크를 제공하되, 가능하면 오용 시 오류를 포함합니다.
아이디어: crypto.subtle
과 유사한 subtle
네임스페이스에 미묘한 API를 배치하여 프레임워크 구현이나 개발 도구 구축과 같은 고급 사용에 필요한 API와 사용할 신호 인스턴스화와 같은 일상적인 애플리케이션 개발 사용 사이에 경계를 표시합니다. 뼈대.
그러나 문자 그대로 똑같은 이름을 가리지 않는 것이 중요합니다!
기능이 생태계 개념과 일치한다면 공통 어휘를 사용하는 것이 좋습니다.
"JS 개발자의 유용성"과 "프레임워크에 대한 모든 후크 제공" 사이의 긴장
우수한 성능으로 구현 가능하고 사용 가능해야 합니다. 표면 API는 너무 많은 오버헤드를 발생시키지 않습니다.
프레임워크가 비공개 필드를 포함하여 자체 메서드와 필드를 추가할 수 있도록 하위 클래스화를 활성화합니다. 이는 프레임워크 수준에서 추가 할당이 필요하지 않도록 하는 데 중요합니다. 아래의 "메모리 관리"를 참조하세요.
가능한 경우: 계산된 신호는 활성 상태를 유지하는 더 넓은 그래프에 연결되어 있더라도(예: 활성 상태로 남아 있는 상태를 읽음으로써) 향후 읽기를 위해 참조하는 활성 항목이 없는 경우 가비지 수집 가능해야 합니다.
오늘날 대부분의 프레임워크는 살아 있는 다른 신호 그래프에 대한 참조가 있거나 다른 신호 그래프에서 참조가 있는 경우 계산된 신호를 명시적으로 폐기해야 합니다.
수명이 UI 구성 요소의 수명과 연결되어 있고 효과를 어쨌든 삭제해야 하는 경우에는 결과적으로 그렇게 나쁘지 않습니다.
이러한 의미 체계를 실행하는 데 비용이 너무 많이 든다면 현재 부족한 아래 API에 계산된 신호의 명시적 삭제(또는 "링크 해제")를 추가해야 합니다.
별도의 관련 목표: 할당 수를 최소화합니다. 예:
쓰기 가능한 신호를 만들기 위해(두 개의 별도 클로저 + 배열 방지)
효과 구현(모든 단일 반응에 대해 종료 방지)
신호 변경 사항을 관찰하기 위한 API에서는 추가 임시 데이터 구조를 생성하지 마세요.
솔루션: 하위 클래스에 정의된 메서드와 필드를 재사용할 수 있는 클래스 기반 API
Signal API의 초기 아이디어는 다음과 같습니다. 이는 초기 초안에 불과하며 시간이 지남에 따라 변경될 것으로 예상됩니다. 전체 .d.ts
부터 시작하여 전체적인 모양에 대한 아이디어를 얻은 다음 이것이 무엇을 의미하는지 세부적으로 논의하겠습니다.
인터페이스 Signal<T> {// signalget()의 값을 가져옵니다: T;}namespace Signal {// 읽기-쓰기 Signalclass State<T>는 Signal<T>를 구현합니다. {// 값으로 시작하는 상태 신호 생성 tconstructor(t: T, options?: SignalOptions<T>);// signalget()의 값을 가져옵니다: T;// 상태 Signal 값을 tset(t: T): void;}// A Signal 이것은 다른 Signalsclass를 기반으로 하는 공식 Computed<T = 알 수 없음>은 Signal<T>를 구현합니다. {// 콜백에서 반환된 값으로 평가되는 신호를 생성합니다.// 콜백은 이 신호를 this 값으로 사용하여 호출됩니다.constructor(cb: ( this: Computed<T>) => T, options?: SignalOptions<T>);// signalget()의 값 가져오기: T;}// 이 네임스페이스에는 떠나는 것이 더 나은 "고급" 기능이 포함되어 있습니다// 프레임워크용 애플리케이션 개발자가 아닌 작성자.// `crypto.subtle`네임스페이스 미묘함과 유사 {// 모든 추적 비활성화 함수를 사용하여 콜백 실행 untrack<T>(cb: () => T): T;// 현재 계산된 신호 가져오기 모든 신호 읽기를 추적하는 함수입니다. currentCompulated(): Computed | null;// 마지막으로 평가된 동안 이 신호가 참조한 모든 신호의 순서가 지정된 목록을 반환합니다.// 감시자의 경우 감시 중인 신호 세트를 나열합니다.function introspectSources(s: Computed | Watcher): (상태 | 계산됨)[];// 이 신호가 포함된 감시자와 // 마지막으로 평가되었을 때 이 신호를 읽은 계산된 신호를 반환합니다.// 계산된 신호가 (재귀적으로) 감시된 경우.function introspectSinks(s: State | Computed): (Compulated | Watcher)[];// 이 신호가 Watcher에 의해 감시된다는 점에서 "live"인 경우 참이거나// 또는 계산된 신호에 의해 읽혀집니다( 재귀적으로) live.function hasSinks(s: State | Computed): boolean;// 이 요소가 "반응적"이면 참입니다. 즉, // 다른 신호에 의존한다는 점입니다. hasSources가 false인 Computed는// 항상 동일한 상수를 반환합니다.function hasSources(s: Computed | Watcher): boolean;class Watcher {// Watcher의 (재귀) 소스가 기록되면 이 콜백을 호출합니다.// 마지막 `watch` 호출 이후 아직 호출되지 않은 경우// inform.constructor(notify: (this: Watcher) => void) 동안 신호를 읽거나 쓸 수 없습니다.// 이 신호를 추가하세요 감시자의 집합에 추가하고, 집합의 신호(또는 해당 종속성 중 하나)가 변경될 때 // 다음에 콜백을 알리도록 감시자를 설정합니다.// "알림" 상태를 재설정하기 위해 인수 없이 호출할 수 있으므로 // 알림 콜백이 다시 호출됩니다.watch(...s: Signal[]): void;// 감시 세트에서 이러한 신호를 제거합니다(예: 삭제된 효과의 경우)unwatch(...s : 신호[]): 무효;// 아직 더티이거나 계산된 신호인 Watcher 세트의 소스 세트를 반환합니다. // 더티하거나 보류 중이며 아직 재평가되지 않은 소스가 있습니다.getPending(): Signal[];}// 후크 감시 중이거나 더 이상 감시되지 않는 것을 관찰합니다var Watched: Symbol;var unwatched: Symbol;}interface SignalOptions<T> {// 이전 값과 새 값 사이의 사용자 정의 비교 기능입니다. 기본값: Object.is.// 신호는 context.equals?에 대한 this 값으로 전달됩니다.: (this: Signal<T>, t: T, t2: T) => boolean;// isWatched가 다음과 같을 때 콜백이 호출됩니다. true, 이전에 false였던 경우[Signal.subtle.watched]?: (this: Signal<T>) => void;// 이전에 false였던 경우 isWatched가 false가 될 때마다 호출되는 콜백 true[Signal.subtle.unwatched]?: (this: Signal<T>) => void;}}
신호는 시간이 지남에 따라 변경될 수 있는 데이터 셀을 나타냅니다. 신호는 "상태"(수동으로 설정한 값) 또는 "계산"(다른 신호를 기반으로 한 공식)일 수 있습니다.
계산된 신호는 평가 중에 읽혀지는 다른 신호를 자동으로 추적하여 작동합니다. 계산된 항목을 읽으면 이전에 기록된 종속성이 변경되었는지 확인하고 변경된 경우 자체를 재평가합니다. 여러 개의 계산된 신호가 중첩되면 추적의 모든 속성이 가장 안쪽 신호로 이동합니다.
계산된 신호는 게으른, 즉 풀 기반입니다. 종속성 중 하나가 이전에 변경된 경우에도 액세스할 때만 재평가됩니다.
계산된 신호에 전달된 콜백은 일반적으로 액세스하는 다른 신호의 결정론적, 부작용 없는 함수라는 의미에서 "순수"해야 합니다. 동시에 콜백이 호출되는 타이밍은 결정적이므로 부작용을 주의해서 사용할 수 있습니다.
신호에는 뛰어난 캐싱/메모 기능이 있습니다. 상태 신호와 계산된 신호 모두 현재 값을 기억하고 실제로 변경되는 경우 이를 참조하는 계산된 신호의 재계산만 트리거합니다. 이전 값과 새 값을 반복적으로 비교하는 것도 필요하지 않습니다. 비교는 소스 신호가 재설정/재평가될 때 한 번 이루어지며 신호 메커니즘은 해당 신호를 참조하는 항목이 새 값을 기반으로 업데이트되지 않았는지 추적합니다. 아직 가치가 없습니다. 내부적으로 이는 일반적으로 (Milo의 블로그 게시물)에 설명된 대로 "그래프 색상 지정"을 통해 표현됩니다.
계산된 신호는 종속성을 동적으로 추적합니다. 실행될 때마다 결국 다른 것에 따라 달라질 수 있으며 정확한 종속성 세트는 신호 그래프에서 최신 상태로 유지됩니다. 즉, 하나의 분기에만 필요한 종속성이 있고 이전 계산이 다른 분기를 사용한 경우 일시적으로 사용되지 않은 값을 변경해도 계산된 신호가 당겨져도 다시 계산되지 않습니다.
JavaScript Promise와 달리 Signals의 모든 것은 동기식으로 실행됩니다.
신호를 새 값으로 설정하는 것은 동기식이며 나중에 이에 의존하는 계산된 신호를 읽을 때 즉시 반영됩니다. 이 돌연변이에 대한 기본 제공 일괄 처리는 없습니다.
계산된 신호를 읽는 것은 동기식이므로 해당 값은 항상 사용할 수 있습니다.
아래 설명된 대로 Watchers의 notify
콜백은 이를 트리거한 .set()
호출 중에(그러나 그래프 색상 지정이 완료된 후) 동기식으로 실행됩니다.
약속과 마찬가지로 신호는 오류 상태를 나타낼 수 있습니다. 계산된 신호의 콜백이 발생하면 해당 오류는 다른 값처럼 캐시되고 신호를 읽을 때마다 다시 발생합니다.
Signal
인스턴스는 시간이 지남에 따라 업데이트가 추적되는 동적으로 변경되는 값을 읽는 기능을 나타냅니다. 또한 다른 계산된 신호에서 추적된 액세스를 통해 암시적으로 신호를 구독하는 기능도 암시적으로 포함됩니다.
여기의 API는 "signal", "computed" 및 "state"와 같은 이름을 사용하여 Signal 라이브러리의 상당 부분 사이에서 매우 대략적인 생태계 합의를 일치시키도록 설계되었습니다. 그러나 계산된 신호 및 상태 신호에 대한 액세스는 .get()
메서드를 통해 이루어지며, 이는 .value
스타일 접근자 또는 signal()
호출 구문을 사용하는 모든 인기 있는 신호 API와 동의하지 않습니다.
API는 기존 프레임워크 맞춤형 신호와 동일하거나 더 나은 성능을 달성하면서 신호를 JavaScript 프레임워크에 삽입하기에 적합하도록 할당 수를 줄이기 위해 설계되었습니다. 이는 다음을 의미합니다.
상태 신호는 동일한 참조에서 액세스하고 설정할 수 있는 쓰기 가능한 단일 객체입니다. (아래 "능력 분리" 섹션의 의미를 참조하세요.)
상태 및 계산된 신호는 모두 하위 클래스화 가능하도록 설계되어 공개 및 비공개 클래스 필드(및 해당 상태를 사용하는 방법)를 통해 추가 속성을 추가하는 프레임워크의 기능을 용이하게 합니다.
다양한 콜백(예: equals
, 계산된 콜백)은 관련 신호를 컨텍스트에 대한 this
값으로 사용하여 호출되므로 신호마다 새 클로저가 필요하지 않습니다. 대신 신호 자체의 추가 속성에 컨텍스트를 저장할 수 있습니다.
이 API에 의해 시행되는 일부 오류 조건은 다음과 같습니다.
계산된 값을 재귀적으로 읽는 것은 오류입니다.
Watcher의 notify
콜백은 신호를 읽거나 쓸 수 없습니다.
계산된 신호의 콜백이 발생하면 종속성 중 하나가 변경되어 다시 계산될 때까지 신호에 대한 후속 액세스에서 캐시된 오류가 다시 발생합니다.
시행되지 않는 일부 조건:
계산된 신호는 콜백 내에서 동기적으로 다른 신호에 쓸 수 있습니다.
Watcher의 notify
콜백에 의해 대기열에 있는 작업은 신호를 읽거나 쓸 수 있으므로 신호 측면에서 고전적인 React 안티패턴을 복제할 수 있습니다!
위에 정의된 Watcher
인터페이스는 효과를 위한 일반적인 JS API, 즉 순전히 부작용을 위해 다른 신호가 변경될 때 다시 실행되는 콜백을 구현하기 위한 기초를 제공합니다. 위의 초기 예제에서 사용된 effect
함수는 다음과 같이 정의할 수 있습니다.
// 이 함수는 일반적으로 애플리케이션 코드가 아닌 라이브러리/프레임워크에 있습니다. // 참고: 이 스케줄링 논리는 너무 기본적이어서 유용하지 않습니다. 복사/붙여넣기하지 마세요.pending = false;let w = new Signal.subtle.Watcher(() => {if (!pending) {pending = true;queueMicrotask(() => {pending = false;for (let s of w.getPending()) s.get();w.watch();});}});// cb로 평가되는 효과 효과 신호로 // 자체 읽기를 예약합니다. 종속성 중 하나가 변경될 때마다 마이크로태스크 대기열export function effect(cb) {let destructor;let c = new Signal.Compulated(() => { destructor?.(); destructor = cb(); });w.watch( c);c.get();return () => { 소멸자?.(); w.unwatch(c) };}
Signal API에는 effect
와 같은 내장 함수가 포함되어 있지 않습니다. 이는 효과 스케줄링이 미묘하고 종종 JS가 액세스할 수 없는 프레임워크 렌더링 주기 및 기타 상위 프레임워크 관련 상태 또는 전략과 연결되기 때문입니다.
여기에 사용된 다양한 작업 살펴보기: Watcher
생성자에 전달된 notify
콜백은 신호가 "깨끗한" 상태(캐시가 초기화되고 유효하다는 것을 알고 있음)에서 "확인됨" 또는 "더러운" 상태로 바뀔 때 호출되는 함수입니다. " 상태(재귀적으로 의존하는 상태 중 하나 이상이 변경되었기 때문에 캐시가 유효할 수도 있고 유효하지 않을 수도 있음).
notify
호출은 궁극적으로 일부 상태 Signal에서 .set()
호출에 의해 트리거됩니다. 이 호출은 동기식입니다. .set
반환되기 전에 발생합니다. 하지만 이 콜백이 절반 처리된 상태에서 신호 그래프를 관찰하는 것에 대해 걱정할 필요가 없습니다. 왜냐하면 notify
콜백 중에는 untrack
호출에서도 신호를 읽거나 쓸 수 없기 때문입니다. .set()
중에 notify
호출되기 때문에 완료되지 않았을 수 있는 다른 로직 스레드를 중단합니다. notify
에서 신호를 읽거나 쓰려면 나중에 액세스할 수 있도록 목록에 신호를 기록하거나 위와 같이 queueMicrotask
사용하여 작업을 나중에 실행하도록 예약하십시오.
Glimmer처럼 계산된 신호의 폴링을 예약하면 Symbol.subtle.Watcher
없이 신호를 효과적으로 사용하는 것이 완벽하게 가능하다는 점에 유의하세요. 그러나 많은 프레임워크에서는 이 스케줄링 논리를 동기식으로 실행하는 것이 매우 유용하다는 것을 알았으므로 Signals API에 이를 포함합니다.
계산된 신호와 상태 신호 모두 JS 값처럼 가비지 수집됩니다. 그러나 Watcher에는 사물을 유지하는 특별한 방법이 있습니다. Watcher가 감시하는 모든 신호는 기본 상태에 도달할 수 있는 한 유지됩니다. 이는 향후 notify
호출(및 향후 .get()
). 이러한 이유로 효과를 정리하려면 Watcher.prototype.unwatch
호출하는 것을 잊지 마세요.
Signal.subtle.untrack
은 읽기를 추적 하지 않고 신호를 읽을 수 있는 탈출구입니다. 이 기능은 다른 신호에 따라 값이 달라지지만 해당 신호가 변경될 때 업데이트되지 않는 계산된 신호를 생성할 수 있기 때문에 안전하지 않습니다. 추적되지 않은 액세스가 계산 결과를 변경하지 않을 때 사용해야 합니다.
이러한 기능은 나중에 추가될 수 있지만 현재 초안에는 포함되어 있지 않습니다. 이러한 누락은 프레임워크 간의 디자인 공간에서 확립된 합의가 부족하고 이 문서에 설명된 신호 개념 위에 메커니즘을 사용하여 부재를 해결할 수 있는 능력이 입증되었기 때문입니다. 그러나 불행하게도 이 생략으로 인해 프레임워크 간의 상호 운용성이 제한됩니다. 이 문서에 설명된 신호의 프로토타입이 생성됨에 따라 이러한 누락이 적절한 결정인지 재검토하려는 노력이 있을 것입니다.
Async : 이 모델에서는 신호를 항상 동기식으로 평가할 수 있습니다. 그러나 신호가 설정되도록 하는 특정 비동기 프로세스를 갖고 신호가 여전히 "로딩"되는 시기를 이해하는 것이 유용한 경우가 많습니다. 로딩 상태를 모델링하는 간단한 방법 중 하나는 예외를 포함하는 것이며, 계산된 신호의 예외 캐싱 동작은 이 기술을 통해 어느 정도 합리적으로 구성됩니다. 향상된 기술은 Issue #30에서 논의됩니다.
트랜잭션 :보기 간의 전환의 경우, 종종 "From"및 "To"상태 모두에 대한 살아있는 상태를 유지하는 것이 유용합니다. "To"State는 백그라운드에서 렌더링되어 (거래를 저지 릅니다) "From"상태는 대화식으로 남아 있습니다. 두 상태를 동시에 유지하려면 신호 그래프의 상태를 "포킹"해야하며, 한 번에 여러 계류중인 전환을 지원하는 것이 유용 할 수도 있습니다. 문제 #73에서 토론.
가능한 편의 방법도 생략됩니다.
이 제안은 2024 년 4 월 1 단계에 대한 TC39 의제에 있습니다. 현재 "단계 0"으로 생각할 수 있습니다.
이 제안을위한 다색이 몇 가지 기본 테스트를 통해 사용할 수 있습니다. 일부 프레임 워크 저자는이 신호 구현을 대체하여 실험을 시작했지만이 사용법은 초기 단계에 있습니다.
신호 제안에 관한 협력자들은 우리 가이 제안을 발전시키는 방법에 특히 보수적 이기를 원합니다. 그래서 우리는 우리가 무언가를 배송하는 함정에 착륙하지 않고 실제로 사용하지 않고 사용하지 않습니다. 우리의 계획은 TC39 프로세스에서 필요하지 않은 다음 추가 작업을 수행 하여이 제안이 다음과 같은지 확인하는 것입니다.
2 단계를 제안하기 전에 다음을 계획합니다.
견고하고 잘 테스트 한 여러 생산 급 PolyFill 구현 (예 : 다양한 프레임 워크의 테스트와 Test262 스타일 테스트의 테스트 전달) 및 성능 측면에서 경쟁 (철저한 신호/프레임 워크 벤치 마크 세트로 검증)을 개발하십시오.
제안 된 신호 API를 우리가 다소 대표적인 것으로 간주하는 많은 JS 프레임 워크에 통합하고 일부 대규모 응용 프로그램 이이 기반으로 작동합니다. 이러한 맥락에서 효율적이고 정확하게 작동하는지 테스트하십시오.
API에 대한 확장 가능한 공간에 대한 확실한 이해를 가지고 있으며이 제안에 추가되어야하는 (있는 경우) 결론을 내 렸습니다.
이 섹션에서는 JavaScript에 노출 된 각 API가 구현하는 알고리즘에 대해 설명합니다. 이것은 프로토 특성화로 생각할 수 있으며,이 초기 시점에서 가능한 한 의미의 한 세트를 못 박 으면서 변화에 매우 개방적입니다.
알고리즘의 일부 측면 :
컴퓨팅 된 신호의 읽기 순서는 중요하며 특정 콜백 ( Watcher
가 호출 된 첫 번째 매개 equals
, new Signal.Computed
및 watched
/ unwatched
Callbacks)이 실행되도록 관찰 할 수 있습니다. 이는 계산 된 신호의 소스를 주문해야 함을 의미합니다.
이 4 개의 콜백은 모두 예외를 던질 수 있으며 이러한 예외는 호출 JS 코드에 예측 가능한 방식으로 전파됩니다. 예외는이 알고리즘의 실행을 중단하거나 그래프를 반 처리 상태로 남겨 두지 않습니다 . 감시자의 notify
콜백에 오류가 발생하는 경우, 그 예외는 .set()
호출로 전송되어 여러 예외가 발생한 경우 집계에 응집기를 사용하여 트리거되었습니다. 다른 것 ( watched
/ unwatched
포함? 포함)은 신호의 값에 저장되어 읽히면 다시 제작되며, 그러한 재가 부는 신호는 정상적인 값을 가진 다른 사람과 마찬가지로 ~clean~
표시 할 수 있습니다.
신호 그래프의 다른 부분과 독립적으로 수집 할 수 있도록 "시청"하지 않는 계산 신호의 경우 원형을 피하기 위해주의를 기울입니다. 내부적으로 이것은 항상 수집되는 생성 번호 시스템으로 구현 될 수 있습니다. 최적화 된 구현에는 로컬 노드 당 생성 번호가 포함되거나 시계 신호에서 일부 번호를 추적하지 않을 수도 있습니다.
신호 알고리즘은 특정 글로벌 상태를 참조해야합니다. 이 상태는 전체 스레드 또는 "에이전트"에 대해 전 세계적입니다.
computing
: .get
또는 .run
호출 또는 null
로 인해 현재 가장 안쪽 컴퓨팅 또는 효과 신호가 재평가 중입니다. 처음에는 null
.
frozen
: 부울은 현재 실행중인 콜백이 있는지 여부를 나타내며 그래프를 수정하지 않아야합니다. 처음에는 false
.
generation
: 0에서 시작하는 증분 정수는 순환을 피하면서 값이 전류의 방식을 추적하는 데 사용됩니다.
Signal
네임 스페이스 Signal
는 신호 관련 클래스 및 함수의 네임 스페이스 역할을하는 일반적인 객체입니다.
Signal.subtle
은 비슷한 내부 네임 스페이스 객체입니다.
Signal.State
클래스Signal.State
내부 슬롯 value
: 상태 신호의 현재 값
equals
: 값을 변경할 때 사용되는 비교 함수
watched
: 효과에 의해 신호가 관찰 될 때 호출되는 콜백
unwatched
: 효과에 의해 더 이상 신호가 관찰되지 않을 때 호출 할 콜백이 호출됩니다.
sinks
: 이것에 의존하는 시계 신호 세트
Signal.State(initialValue, options)
이 신호의 value
initialValue
으로 설정하십시오.
이 신호를 옵션으로 설정 equals
? .equals
이 신호를 옵션으로 watched
합니까?. [Signal.Subtle.Watched]
이 신호를 옵션으로 설정 unwatched
?. [Signal.Subtle.Unwatched]
이 신호의 sinks
빈 세트로 설정하십시오
Signal.State.prototype.get()
frozen
이 사실이라면 예외를 던지십시오.
computing
undefined
않은 경우 computing
sources
에이 신호를 추가하십시오.
참고 : 우리는이 신호의 sinks
감시자가 감시 될 때까지 computing
추가하지 않습니다.
이 신호의 value
반환하십시오.
Signal.State.prototype.set(newValue)
현재 실행 컨텍스트가 frozen
되면 예외를 던지십시오.
이 신호와 값의 첫 번째 매개 변수를 사용하여 "신호 값 설정"알고리즘을 실행하십시오.
해당 알고리즘이 ~clean~
반환하면 정의되지 않은 반환.
이 신호의 모든 sinks
state
를 (계산 된 신호 인 경우) ~dirty~
이전에 깨끗한 경우, 또는 (감시자 인 경우) ~pending~
이전에 ~watching~
.
모든 싱크의 계산 된 신호 종속성의 state
(재귀 적으로)를 ~checked~
~clean~
(즉, 더러운 표시를 그대로 두는 경우), 또는 감시자에게 ~pending~
~watching~
.
이전에 ~watching~
Watcher가 재귀적인 검색에서 발생한 다음 깊이 우선 순서로 만났습니다.
frozen
참으로 설정하십시오.
notify
콜백을 호출합니다 (예외를 제외하고 제외하고 notify
의 반환 값을 무시 함).
frozen
복원.
감시자의 state
~waiting~
로 설정하십시오.
notify
콜백에서 예외가 발생하면 notify
콜백이 실행 된 후 발신자에게 전파하십시오. 여러 예외가 있으면 집계 레터로 함께 포장하여 던지십시오.
정의되지 않은 반환.
Signal.Computed
클래스Signal.Computed
Puted State Machine 계산 된 신호의 state
다음 중 하나 일 수 있습니다.
~clean~
: 신호의 가치가 존재하고 부실하지 않은 것으로 알려져 있습니다.
~checked~
:이 신호의 (간접) 소스가 변경되었습니다. 이 신호에는 값이 있지만 부실 수 있습니다. 모든 즉각적인 출처가 평가 된 경우에만 부실한 지 여부는 알려지지 않습니다.
~computing~
:이 신호의 콜백은 현재 .get()
호출의 부작용으로 실행되고 있습니다.
~dirty~
:이 신호는 부실한 것으로 알려진 값이 있거나 평가 된 적이 없습니다.
전환 그래프는 다음과 같습니다.
상태 다이어그램-v2
[*] -> 더러운
더러운 -> 컴퓨팅 : [4]
컴퓨팅 -> 청소 : [5]
청소 -> 더러운 : [2]
청소 -> 확인 : [3]
확인 -> 청소 : [6]
확인 -> 더러운 : [1]
로드 중전환은 다음과 같습니다.
숫자 | 에서 | 에게 | 상태 | 연산 |
---|---|---|---|---|
1 | ~checked~ | ~dirty~ | 계산 된 신호 인이 신호의 즉각적인 소스가 평가되었으며 그 값이 변경되었습니다. | 알고리즘 : 더러운 계산 신호를 다시 계산합니다 |
2 | ~clean~ | ~dirty~ | 상태 인이 신호의 즉각적인 소스가 이전 값과 같지 않은 값으로 설정되었습니다. | 방법 : Signal.State.prototype.set(newValue) |
3 | ~clean~ | ~checked~ | 이 신호의 재귀 적이지만 즉각적인 소스는 상태 인 상태이며 이전 값과 같지 않은 값을 갖습니다. | 방법 : Signal.State.prototype.set(newValue) |
4 | ~dirty~ | ~computing~ | 우리는 callback 실행하려고합니다. | 알고리즘 : 더러운 계산 신호를 다시 계산합니다 |
5 | ~computing~ | ~clean~ | callback 평가를 마치고 값을 반환하거나 예외를 던졌습니다. | 알고리즘 : 더러운 계산 신호를 다시 계산합니다 |
6 | ~checked~ | ~clean~ | 이 신호의 모든 즉각적인 출처는 평가되었으며 모두 변하지 않은 것으로 밝혀 졌으므로 이제는 부실하지 않은 것으로 알려져 있습니다. | 알고리즘 : 더러운 계산 신호를 다시 계산합니다 |
Signal.Computed
내부 슬롯 value
: 신호의 이전 캐시 된 값 또는 ~uninitialized~
값. 값은 값을 읽을 때 다시 제외되는 예외 일 수 있습니다. 효과 신호에 대해 항상 undefined
.
state
: ~clean~
, ~checked~
, ~computing~
또는 ~dirty~
일 수 있습니다.
sources
:이 신호가 의존하는 순서대로 신호 세트.
sinks
:이 신호에 의존하는 순서대로 신호 세트.
equals
: 옵션에 제공된 동등한 방법.
callback
: 계산 된 신호의 값을 얻기 위해 호출되는 콜백. 생성자로 전달 된 첫 번째 매개 변수로 설정하십시오.
Signal.Computed
생성자생성자 세트
첫 번째 매개 변수로의 callback
옵션을 기준으로 equals
Object.is
에 대한 불이행
~ 더러운 state
~dirty~
~uninitialized~
에 대한 value
AsyncContext를 사용하면 콜백이 new Signal.Computed
으로 전달되어 생성자가 호출 된 시점부터 스냅 샷을 통해 닫히고 실행 중에이 스냅 샷을 복원합니다.
Signal.Computed.prototype.get
현재 실행 컨텍스트가 frozen
되거나이 신호에 상태 ~computing~
있거나이 신호가 효과가 있고 계산 된 신호를 computing
경우 예외를 던지십시오.
computing
null
이 아닌 경우 computing
sources
세트 에이 신호를 추가하십시오.
참고 :이 신호의 sinks
에 computing
추가하지 않아도됩니다.
이 신호의 상태가 ~dirty~
또는 ~checked~
:이 신호가 ~clean~
:
sources
통해 재발하여 가장 깊고 왼쪽으로 왼쪽 ~dirty~
가장 초기 관찰 된) 재귀 소스를 찾으십시오 ~clean~
검색하려면).
해당 신호에서 "더러운 계산 신호 재 계산"알고리즘을 수행하십시오.
이 시점 에서이 신호의 상태는 ~clean~
하고 재귀 소스는 ~dirty~
거나 ~checked~
는 없습니다. 신호의 value
반환하십시오. 값이 예외이라면 해당 예외를 재확인하십시오.
Signal.subtle.Watcher
클래스Signal.subtle.Watcher
상태 기계 감시자의 state
다음 중 하나 일 수 있습니다.
~waiting~
: notify
콜백이 실행되었거나 감시자가 새롭지 만 신호를 적극적으로보고 있지는 않습니다.
~watching~
: 감시자는 적극적으로 신호를보고 있지만 아직 변경 사항이 발생하지 않았으므로 notify
콜백이 필요합니다.
~pending~
: 감시자의 종속성이 변경되었지만 notify
콜백은 아직 실행되지 않았습니다.
전환 그래프는 다음과 같습니다.
상태 다이어그램-v2
[*] -> 기다리고 있습니다
대기 -> 시청 : [1]
시청 -> 대기 : [2]
시청 -> 보류 중 : [3]
보류 중 -> 대기 : [4]
로드 중전환은 다음과 같습니다.
숫자 | 에서 | 에게 | 상태 | 연산 |
---|---|---|---|---|
1 | ~waiting~ | ~watching~ | 감시자의 watch 방법이 호출되었습니다. | 방법 : Signal.subtle.Watcher.prototype.watch(...signals) |
2 | ~watching~ | ~waiting~ | 감시자의 unwatch 방법이 호출되었고 마지막 시계 신호가 제거되었습니다. | 방법 : Signal.subtle.Watcher.prototype.unwatch(...signals) |
3 | ~watching~ | ~pending~ | 시계 신호는 값이 변경되었을 수 있습니다. | 방법 : Signal.State.prototype.set(newValue) |
4 | ~pending~ | ~waiting~ | notify 콜백이 실행되었습니다. | 방법 : Signal.State.prototype.set(newValue) |
Signal.subtle.Watcher
내부 슬롯 state
: ~watching~
, ~pending~
또는 ~waiting~
signals
:이 감시자가보고있는 주문 된 신호 세트
notifyCallback
: 무언가가 변경 될 때 호출되는 콜백. 생성자로 전달 된 첫 번째 매개 변수로 설정하십시오.
new Signal.subtle.Watcher(callback)
state
~waiting~
로 설정됩니다.
빈 세트로 signals
초기화합니다.
notifyCallback
콜백 매개 변수로 설정됩니다.
AsyncContext를 사용하면 콜백이 new Signal.subtle.Watcher
생성자가 호출 된 시점부터 스냅 샷에 닫히지 않으므로 쓰기 주변의 문맥 정보가 표시됩니다.
Signal.subtle.Watcher.prototype.watch(...signals)
frozen
이 사실이라면 예외를 던지십시오.
주장 중 하나가 신호가 아닌 경우 예외를 던지십시오.
이 객체의 signals
끝에 모든 인수를 추가하십시오.
왼쪽에서 오른쪽 순서대로 새로 시합 된 각 신호에 대해
이 감시자를 해당 신호에 sink
로 추가하십시오.
이것이 첫 번째 싱크 인 경우 소스로 되돌아 가서 해당 신호를 싱크로 추가하십시오.
frozen
참으로 설정하십시오.
watched
콜백이 존재하는 경우 전화하십시오.
frozen
복원.
신호 state
가 ~waiting~
인 경우 ~watching~
으로 설정하십시오.
Signal.subtle.Watcher.prototype.unwatch(...signals)
frozen
이 사실이라면 예외를 던지십시오.
주장 중 하나가 신호가 아니 거나이 감시자가 감시되지 않는 경우 예외를 던지십시오.
인수의 각 신호에 대해 왼쪽에서 오른쪽으로 순서대로
이 감시자의 signals
세트에서 해당 신호를 제거하십시오.
해당 신호의 sink
세트 에서이 감시자를 제거하십시오.
해당 신호의 sink
세트가 비어 있으면 해당 신호를 각 소스에서 싱크대로 제거하십시오.
frozen
참으로 설정하십시오.
unwatched
콜백이 존재하는 경우 전화하십시오.
frozen
복원.
감시자에게 signals
없고 state
가 ~watching~
있다면 ~waiting~
로 설정하십시오.
Signal.subtle.Watcher.prototype.getPending()
~dirty~
에서 계산 된 signals
의 서브 세트가 포함 된 배열을 반환하십시오 ~pending~
Signal.subtle.untrack(cb)
c
실행 컨텍스트의 현재 computing
상태로하자.
computing
NULL로 설정하십시오.
cb
에 전화하십시오.
computing
c
로 복원하십시오 ( cb
예외를 던져도).
cb
의 반환 값을 반환합니다 (예외 재조정).
참고 : Untrack은 frozen
상태에서 벗어나게되지 않으며, 이는 엄격하게 유지됩니다.
Signal.subtle.currentComputed()
현재 computing
값을 반환하십시오.
이 신호의 sources
를 정리하고 해당 소스의 sinks
세트에서 제거하십시오.
이전 computing
값을 저장하고 computing
이 신호로 설정하십시오.
이 신호의 상태를 ~computing~
로 설정하십시오.
이 신호를이 값으로 사용 하여이 계산 된 신호의 콜백을 실행하십시오. 반환 값을 저장하고 콜백이 예외를 던지면 재가 젓기를 위해 저장하십시오.
이전 computing
값을 복원하십시오.
"신호 값 설정"알고리즘을 콜백의 리턴 값에 적용하십시오.
이 신호의 상태를 ~clean~
로 설정하십시오.
이 알고리즘이 ~dirty~
:이 신호의 모든 싱크를 ~dirty~
로 표시하면 (이전에 싱크는 점검과 더러운 것의 혼합 일 수 있음). (또는 이것이 해치되지 않은 경우 더러움이나 그와 비슷한 것을 나타내는 새로운 세대 번호를 채택하십시오.)
그렇지 않으면, 그 알고리즘이 ~clean~
:이 경우,이 경우,이 신호의 모든 ~checked~
싱크에 대해, 모든 신호의 소스가 깨끗한 경우, 그 신호를 ~clean~
로 표시하십시오. 이 정리 단계를 적용하여 싱크대를 확인한 새로 깨끗한 신호에 추가 싱크를 재귀 적으로 적용하십시오. (또는 이것이 해치지 않은 경우, 어떻게 든 동일하게 표시되므로 정리가 게으름하게 진행될 수 있습니다.)
이 알고리즘이 값이 전달 된 경우 (재고에 대한 예외와 반대로, 재 계산 더러운 계산 신호 알고리즘에서) :
이 신호의 equals
를 호출하여 현재 value
, 새 값 및이 신호를 매개 변수로 전달하십시오. 예외가 발생하면 해당 예외를 신호의 값으로 저장하고 마치 콜백이 False를 반환 한 것처럼 계속하십시오.
그 함수가 true가 반환되면 ~clean~
반환하십시오.
이 신호의 value
매개 변수로 설정하십시오.
반환 ~dirty~
Q : 2022 년에 방금 뜨거운 새로운 것이되기 시작했을 때 신호와 관련된 것을 표준화하는 것이 곧 조금이라? 우리는 그들에게 진화하고 안정화 할 시간을 더 많이주지 않아야합니까?
A : 웹 프레임 워크의 현재 신호 상태는 10 년 이상 지속적인 개발의 결과입니다. 최근 몇 년 동안 투자가 중단되면서 거의 모든 웹 프레임 워크가 매우 유사한 핵심 신호 모델에 접근하고 있습니다. 이 제안은 웹 프레임 워크에서 많은 현재 리더들 사이의 공유 설계 운동의 결과이며, 다양한 맥락에서 해당 도메인 전문가 그룹의 검증 없이는 표준화로 전달되지 않을 것입니다.
Q : 렌더링 및 소유권과의 긴밀한 통합을 고려할 때 내장 신호를 프레임 워크에서도 사용할 수 있습니까?
A : 프레임 워크 별 부품은 효과, 일정 및 소유권/처분 영역에있는 경향이 있으며,이 제안은 해결을 시도하지 않습니다. 프로토 타이핑 표준 트랙 신호의 첫 번째 우선 순위는 기존 프레임 워크를 호환적이고 우수한 성능으로 "아래에"앉을 수 있는지 확인하는 것입니다.
Q : 신호 API가 응용 프로그램 개발자가 직접 사용하거나 프레임 워크에 의해 포장되어야합니까?
A :이 API는 응용 프로그램 개발자 (적어도 Signal.subtle
네임 스페이스 내에 있지 않은 부분)가 직접 사용할 수 있지만 특히 인체 공학적으로 설계되지 않았습니다. 대신 라이브러리/프레임 워크 저자의 요구가 우선 순위입니다. 대부분의 프레임 워크는 기본 Signal.State
조차도 래핑 할 것으로 예상됩니다. Signal.Computed
API는 인체 공학적 경사를 표현하는 것으로 예상됩니다. 실제로, 프레임 워크를 통해 신호를 사용하는 것이 가장 좋습니다. 프레임 워크 (예 : 감시자, untrack
), 소유권 및 처분 관리 (예 : 신호를 추가하고 감시자로부터 제거 해야하는시기), 및 제거), 및 폐기를 관리), 및 폐기 관리 (예 : 신호를 파악) DOM에 대한 렌더링 일정-이 제안은 이러한 문제를 해결하려고 시도하지 않습니다.
Q : 위젯이 파괴 될 때 위젯과 관련된 신호를 찢어 야합니까? 그것의 API는 무엇입니까?
A : 여기서 관련 눈물 다운 작업은 Signal.subtle.Watcher.prototype.unwatch
입니다. 시청 한 신호 만 정리하지 않아도됩니다 (해치지 않음). 해치되지 않은 신호는 자동으로 쓰레기를 수집 할 수 있습니다.
Q : 신호는 vdom에서 또는 기본 HTML DOM과 직접 작동합니까?
A : 네! 신호는 렌더링 기술과 무관합니다. 신호와 같은 구조물을 사용하는 기존 JavaScript 프레임 워크는 vdom (예 : preact), 기본 dom (예 : 고체) 및 조합 (예 : VUE)과 통합됩니다. 내장 신호에서도 마찬가지입니다.
Q : Angular 및 Lit과 같은 클래스 기반 프레임 워크의 맥락에서 신호를 사용하는 것은 인체 공학적일까요? Svelte와 같은 컴파일러 기반 프레임 워크는 어떻습니까?
A : 클래스 필드는 신호 폴리 필드 레드 미에 표시된 것처럼 간단한 액세서 데코레이터로 신호 기반으로 만들 수 있습니다. 신호는 Svelte 5의 룬과 매우 밀접하게 정렬되어 있습니다. 컴파일러가 룬을 여기에 정의한 신호 API로 변환하는 것이 간단하며 실제로 Svelte 5가 내부적으로 수행하는 것입니다 (그러나 자체 신호 라이브러리).
Q : 신호가 SSR과 작동합니까? 수화? 재개 가능성?
A : 그렇습니다. QWIK은 이러한 특성 모두에서 신호를 사용하여 신호를 사용하며 다른 프레임 워크에는 다른 트레이드 오프가있는 신호를 가진 수화에 대한 다른 잘 개발 된 접근 방식이 있습니다. 우리는 상태와 계산 된 신호를 함께 사용하여 QWIK의 재개 가능한 신호를 모델링 할 수 있으며 코드에서이를 입증 할 계획입니다.
Q : 신호는 React와 마찬가지로 일방 방향 데이터 흐름으로 작동합니까?
A : 그렇습니다. 신호는 일방화 데이터 흐름의 메커니즘입니다. 신호 기반 UI 프레임 워크를 사용하면 모델의 함수 (모델에 신호를 통합)로보기를 표현할 수 있습니다. 상태 및 계산 된 신호의 그래프는 구조에 의해 비율적입니다. 또한 신호 내에서 반응 방 반포를 재현 할 수 있습니다 (!), 예를 들어, useEffect
내부의 setState
와 동등한 신호는 감시자를 사용하여 상태 신호에 쓰기를 예약하는 것입니다.
Q : 신호는 Redux와 같은 상태 관리 시스템과 어떤 관련이 있습니까? 신호는 구조화되지 않은 상태를 장려합니까?
A : 신호는 매장과 유사한 상태 관리 추상화에 효율적인 기초를 형성 할 수 있습니다. 다중 프레임 워크에서 발견되는 일반적인 패턴은 신호, 예를 들어, vue reactive()
또는 솔리드 스토어를 사용하여 속성을 내부적으로 나타내는 프록시를 기반으로하는 객체입니다. 이 시스템은 특정 응용 프로그램에 대한 올바른 추상화 수준에서 유연한 상태 그룹화를 가능하게합니다.
Q : Proxy
현재 처리하지 않는 신호는 무엇입니까?
A : 프록시와 신호는 보완적이고 잘 어울립니다. 프록시를 사용하면 얕은 객체 작업을 가로 채고 신호는 종속성 그래프 (셀)를 조정할 수 있습니다. 신호로 프록시를 뒷받침하는 것은 훌륭한 인체 공학으로 중첩 된 반응성 구조를 만드는 좋은 방법입니다.
이 예에서는 프록시를 사용하여 신호에 get
and set
메소드를 사용하는 대신 getter 및 setter 속성을 갖도록 할 수 있습니다.
const a = new Signal.state (0); const B = 새로운 프록시 (a, { get (target, property, receiver) {if (property === 'value') {return target.get () :} } set (대상, 속성, 값, 수신기) {if (property === 'value') {target.set (value)!} }}); // 가상의 반응 컨텍스트에서의 사용 : <template> {B.Value} <버튼 onclick = {() => {b.Value ++; }}> 변경 </button> </템플릿>
세분화 된 반응성에 최적화 된 렌더러를 사용하면 버튼을 클릭하면 b.value
셀이 업데이트됩니다.
보다:
신호와 프록시로 생성 된 중첩 반응성 구조의 예 : 신호-유틸
반응성 데이터 ATD 프록시 간의 관계를 보여주는 사전 구현 예 : 추적 된 구축
논의.
Q : 신호는 푸시 기반 또는 풀 기반입니까?
A : 계산 된 신호의 평가는 풀 기반입니다. 계산 된 신호는 기본 상태가 훨씬 일찍 변경 되더라도 .get()
가 호출 될 때만 평가됩니다. 동시에 상태 신호를 변경하면 알림을 "밀기"로 즉시 감시자의 콜백이 트리거 될 수 있습니다. 따라서 신호는 "푸시 풀"구조로 생각 될 수 있습니다.
Q : 신호가 비 정체주의를 JavaScript 실행에 소개합니까?
A : 아니요. 하나의 경우, 모든 신호 작업에는 잘 정의 된 의미와 순서가 있으며, 적합한 구현마다 다르지 않습니다. 더 높은 수준에서 신호는 특정 불변의 세트를 따릅니다. 계산 된 신호는 항상 일관된 상태에서 신호 그래프를 관찰하며, 다른 신호 중단 코드에 의해 실행이 중단되지 않습니다 (자체라고 부르는 것 제외). 위의 설명을 참조하십시오.
Q : 상태 신호에 글을 쓸 때 계산 된 신호에 대한 업데이트는 언제 예약됩니까?
A : 예약되지 않았습니다! 계산 된 신호는 다음에 누군가가 읽을 때 스스로를 다시 계산합니다. 동시에, 감시자의 notify
콜백을 호출 할 수있어 프레임 워크가 적절하다고 생각되는 시점에 읽기를 예약 할 수 있습니다.
Q : State Signals에 대한 글은 언제 발효됩니까? 즉시, 아니면 배치 되었습니까?
A : 상태 신호에 대한 쓰기는 즉시 반영됩니다. 다음에 상태 신호에 따라 계산 된 계산 신호가 읽히면 다음 코드 라인에 있더라도 필요한 경우 자체적으로 재 계산합니다. 그러나이 메커니즘에 내재 된 게으름 (계산 된 신호가 읽을 때만 계산됨)은 실제로 계산이 배치 된 방식으로 발생할 수 있음을 의미합니다.
Q : 신호가 "글리치 프리"실행을 가능하게하는 것은 무엇을 의미합니까?
A : 반응성에 대한 이전의 푸시 기반 모델은 중복 계산 문제에 직면 해 있습니다. 상태 신호에 대한 업데이트로 인해 계산 된 신호가 열심히 실행되면 궁극적으로 UI로 업데이트를 넓힐 수 있습니다. 그러나 다음 프레임 이전에 원래 상태 신호에 또 다른 변화가있을 경우 UI에 대한이 글은 조기에있을 수 있습니다. 때로는 부정확 한 중간 값이 그러한 결함으로 인해 최종 사용자에게도 나타났습니다. 신호는 푸시 기반보다는 풀 기반이 아닌 이러한 역 동성을 피합니다. 프레임 워크가 UI의 렌더링을 일정으로 일정에 따라 적절한 업데이트를 시작하여 계산에서 낭비되는 작업을 피하고 DOM에 서면으로 작성합니다.
Q : 신호가 "손실"이라는 것은 무엇을 의미합니까?
A : 이것은 결함없는 실행의 플립 사이드입니다. 신호는 시간이 지남에 따라 데이터 스트림이 아닌 즉각적인 전류 값 (변경 될 수 있음)을 나타냅니다. 따라서 다른 일을하지 않고 상태 신호에 두 번 연속으로 글을 쓰면 첫 번째 글은 "손실"이며 계산 된 신호 나 효과로는 보지 못했습니다. 이것은 버그가 아닌 기능으로 이해됩니다. 기타 구성 (예 : 비동기 반복, 관찰 가능)은 스트림에 더 적합합니다.
Q : 기존 신호가 기존 JS 신호 구현보다 빠릅니까?
A : 우리는 (작은 상수 요인으로) 그렇게 희망하지만, 이것은 코드에서 입증되어야합니다. JS 엔진은 마법이 아니며 궁극적으로 JS 신호 구현과 동일한 종류의 알고리즘을 구현해야합니다. 성능에 대한 위의 섹션을 참조하십시오.
Q :이 제안에 실질적인 신호 사용에 효과가 필요한 경우 effect()
기능이 포함되지 않는 이유는 무엇입니까?
A : 효과는 본질적으로 프레임 워크 와이 제안의 범위를 벗어난 스케줄링 및 폐기와 관련이 있습니다. 대신,이 제안에는보다 낮은 수준의 Signal.subtle.Watcher
를 통해 효과를 구현하기위한 기초가 포함되어 있습니다 .Subtle.Watcher API.
Q : 구독이 수동 인터페이스를 제공하는 것이 아니라 자동으로 인한 이유는 무엇입니까?
A : 경험에 따르면 반응성을위한 수동 구독 인터페이스가 비 통증적이고 오류가 발생하기 쉽습니다. 자동 추적은 더욱 합리적이며 신호의 핵심 기능입니다.
Q : Watcher
의 콜백이 마이크로스크에서 예약되지 않고 동기식으로 실행되는 이유는 무엇입니까?
A : 콜백은 신호를 읽거나 쓸 수 없기 때문에 동기식으로 부르면 사운드를 불러 일으키지 않습니다. 일반적인 콜백은 나중에 읽을 배열에 신호를 추가하거나 어딘가에 조금 표시합니다. 이러한 모든 종류의 행동에 대해 별도의 마이크로 마스크를 만드는 것은 불필요하고 실용적으로 비싸다.
Q :이 API는 내가 좋아하는 프레임 워크가 제공하는 좋은 것들이 누락되어 신호로 쉽게 프로그래밍 할 수 있습니다. 표준에도 추가 할 수 있습니까?
A : 아마도. 다양한 확장이 여전히 고려 중입니다. 중요하다고 생각되는 누락 된 기능에 대한 토론을 제기하려면 문제를 제기하십시오.
Q :이 API를 크기 나 복잡성으로 줄일 수 있습니까?
A :이 API를 최소화하는 것은 확실히 목표이며, 우리는 위에 제시된 내용으로 그렇게하려고 노력했습니다. 제거 할 수있는 더 많은 것들에 대한 아이디어가 있으면 논의 할 문제를 제기하십시오.
Q : 관찰 가능성과 같은보다 원시적 인 개념 으로이 분야에서 표준화 작업을 시작해서는 안됩니까?
A : 관찰 가능성은 어떤 것들에게는 좋은 아이디어 일 수 있지만 신호가 해결하려는 문제를 해결하지는 않습니다. 위에서 설명한 바와 같이, 관찰 정보 또는 기타 게시/구독 메커니즘은 개발자를위한 오류가 발생하기 쉬운 구성 작업과 게으름 부족으로 인해 작업을 낭비하기 때문에 많은 유형의 UI 프로그래밍에 대한 완전한 솔루션이 아닙니다.
Q : 대부분의 응용 프로그램이 웹 기반이라는 점을 감안할 때 DOM이 아닌 TC39에서 신호가 제안되는 이유는 무엇입니까?
A :이 제안의 일부 공동 저자는 비 WEB UI 환경에 목표로 관심이 있지만 요즘 웹 API가 웹 밖에서 더 자주 구현되면서 어느 장소에서도 그 장소에 적합 할 수 있습니다. 궁극적으로 신호는 DOM API에 의존 할 필요가 없으므로 어느 쪽이든 작동합니다. 누군가이 그룹이 전환 할 강력한 이유가 있다면 문제에 대해 알려주십시오. 현재 모든 기고자들은 TC39 지적 재산 계약에 서명했으며 계획은이를 TC39에 제시하는 것입니다.
Q : 표준 신호를 사용할 때까지 얼마나 걸립니까?
A : 폴리 필은 이미 사용할 수 있지만 검토 프로세스 중에이 API가 진화하기 때문에 안정성에 의존하지 않는 것이 가장 좋습니다. 몇 개월 또는 1 년 안에 고품질의 고품질 고성능 안정적인 폴리 필이 사용할 수 있어야하지만 이는 여전히위원회 개정의 대상이되고 아직 표준이 아닙니다. TC39 제안의 전형적인 궤적에 따라, 폴리 플릴이 필요하지 않도록 몇 버전을 거슬러 올라가는 모든 브라우저에서 신호를 기본적으로 사용할 수 있도록 최소 2-3 년이 걸릴 것으로 예상됩니다.
Q : {{js/web feature}}}와 마찬가지로 잘못된 종류의 신호를 너무 빨리 표준화하는 것을 어떻게 방지 할 것인가?
A :이 제안서의 저자는 TC39에서 스테이지 발전을 요청하기 전에 프로토 타이핑을 통해 추가 마일을 갈 계획입니다. 위의 "상태 및 개발 계획"을 참조하십시오. 이 계획의 격차 나 개선 기회가 표시되면 설명을 제출하십시오.