반응성의 가변 파생

Linda Hamilton
풀어 주다: 2024-10-24 08:22:02
원래의
814명이 탐색했습니다.

스케줄링과 비동기에 대한 이 모든 탐구를 통해 우리가 여전히 반응성에 대해 얼마나 이해하지 못하고 있는지 깨닫게 되었습니다. 많은 연구는 회로 및 기타 실시간 시스템 모델링에서 시작되었습니다. 함수형 프로그래밍 패러다임에 대해서도 상당한 양의 탐구가 있었습니다. 저는 이것이 반응성에 대한 현대적인 관점을 크게 형성했다고 생각합니다.

Svelte 3을 처음 봤고 나중에 React 컴파일러를 봤을 때 사람들은 이러한 프레임워크가 렌더링에서 세밀하게 구성되어 있다는 점에 의문을 제기했습니다. 그리고 솔직히 말해서 그들은 동일한 특성을 많이 공유합니다. 지금까지 본 신호 및 파생 프리미티브로 이야기를 마무리한다면 이러한 시스템이 UI 구성 요소 외부에서 반응성을 허용하지 않는다는 점을 제외하면 동등하다고 주장할 수 있습니다.

그러나 Solid가 이를 달성하기 위해 컴파일러가 필요하지 않은 이유가 있습니다. 그리고 오늘날까지도 이것이 여전히 더 최적인 이유는 무엇입니까? 구현 세부 사항이 아닙니다. 건축학적입니다. UI 구성요소로부터의 반응적 독립성과 관련이 있지만 그 이상입니다.


변경 가능 vs 불변

정의에서는 변화하는 능력과 변화하지 않는 능력을 말합니다. 그러나 그것은 우리가 의미하는 것이 아닙니다. 아무것도 바뀌지 않았다면 우리는 꽤 지루한 소프트웨어를 갖게 될 것입니다. 프로그래밍에서는 값이 변경될 수 있는지 여부가 결정됩니다. 값을 변경할 수 없는 경우 변수 값을 변경하는 유일한 방법은 해당 값을 다시 할당하는 것입니다.

그런 관점에서 신호는 설계상 불변입니다. 무언가 변경되었는지 알 수 있는 유일한 방법은 새 값이 할당될 때 가로채는 것입니다. 누군가가 자신의 값을 독립적으로 변경한다면 아무 반응도 일어나지 않을 것입니다.

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

우리의 반응형 시스템은 불변 노드의 연결된 그래프입니다. 데이터를 파생할 때 다음 값을 반환합니다. 이전 값을 사용하여 계산할 수도 있습니다.

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

그러나 신호를 신호에, 효과를 효과에 넣으면 흥미로운 일이 발생합니다.

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

이제 사용자 변경뿐만 아니라 사용자 이름도 변경할 수 있습니다. 더 중요한 것은 이름만 변경되면 불필요한 작업을 건너뛸 수 있다는 것입니다. 우리는 외부 Effect를 다시 실행하지 않습니다. 이 동작은 상태가 선언된 위치가 아니라 사용된 위치에 따른 결과입니다.

이것은 엄청나게 강력하지만 우리 시스템이 불변이라고 말하기는 어렵습니다. 그렇습니다. 개별 원자는 그렇습니다. 그러나 그것들을 중첩함으로써 우리는 돌연변이에 최적화된 구조를 만들었습니다. 무엇이든 변경하면 실행해야 하는 코드의 정확한 부분만 실행됩니다.

중첩 없이 동일한 결과를 얻을 수 있지만 이를 위해서는 diff에 추가 코드를 실행해야 합니다.

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

여기에는 상당히 명확한 절충안이 있습니다. 우리의 중첩 버전은 중첩된 신호를 생성하기 위해 들어오는 데이터에 대해 매핑한 다음 기본적으로 두 번째로 매핑하여 데이터가 독립적인 효과에서 액세스되는 방식을 분리해야 했습니다. 우리의 diff 버전은 일반 데이터를 사용할 수 있지만 변경 사항이 있을 때마다 모든 코드를 다시 실행하고 모든 값을 비교하여 변경된 내용을 확인해야 합니다.

diffing은 성능이 상당히 뛰어나고 특히 깊게 중첩된 데이터에 대한 매핑은 번거롭다는 점을 고려하여 일반적으로 사람들은 후자를 선택했습니다. React는 기본적으로 이것이다. 그러나 비교 비용은 데이터와 해당 데이터와 관련된 작업이 증가함에 따라 더욱 커질 것입니다.

한 번 비교를 포기하면 불가피합니다. 우리는 정보를 잃습니다. 위의 예에서 확인할 수 있습니다. 첫 번째 예에서 이름을 "Janet"으로 설정하면 프로그램이 user().setName("Janet") 이름을 업데이트하도록 지시합니다. 두 번째 업데이트에서는 완전히 새로운 사용자를 설정하고 프로그램은 사용자가 소비되는 모든 곳에서 무엇이 변경되었는지 파악해야 합니다.

중첩은 더 번거롭지만 불필요한 코드를 실행하지 않습니다. 내가 Solid를 만들도록 영감을 준 것은 중첩된 반응성 매핑의 가장 큰 문제가 프록시를 사용하여 해결될 수 있다는 사실을 깨닫는 것이었습니다. 그리고 반응형 스토어가 탄생했습니다:

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

훨씬 나아졌습니다.

이 방법이 작동하는 이유는 setUser(user => user.name = "Janet") 시 이름이 업데이트된다는 것을 여전히 알고 있기 때문입니다. name 속성의 설정자가 적중되었습니다. 데이터를 매핑하거나 비교하지 않고도 이러한 세부적인 업데이트를 달성할 수 있습니다.

이것이 왜 중요합니까? 대신 사용자 목록이 있는 경우를 생각해 보세요. 불변의 변화를 고려해보세요:

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

업데이트된 이름을 가진 새 개체를 제외한 모든 기존 사용자 개체가 포함된 새로운 배열을 얻습니다. 이 시점에서 프레임워크가 아는 모든 것은 목록이 변경되었다는 것입니다. 이동, 추가 또는 제거해야 하는 행이 있는지 또는 변경된 행이 있는지 확인하려면 전체 목록을 반복해야 합니다. 변경된 경우 맵 기능을 다시 실행하고 현재 DOM에 있는 것과 대체/비교되는 출력을 생성합니다.

변경 가능한 변경을 고려하세요.

const [user, setUser] = createSignal({ id: 1, name: "John" });

let prev;
createEffect(() => {
  const u = user();
  // diff values
  if (u.id !== prev?.id) console.log("User", u.id);
  if (u.name !== prev?.name) console.log("Name", u.name);

  // set previous
  prev = u;
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser({ id: 2, name: "Jack" }); 

// effect logs "Name Janet"
setUser({ id: 2, name: "Janet" });
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

우리는 아무것도 반환하지 않습니다. 대신, 해당 사용자의 이름인 하나의 신호는 우리가 이름을 표시하는 장소를 업데이트하는 특정 효과를 업데이트하고 실행합니다. 목록 재창조가 없습니다. 목록 차이가 없습니다. 행 레크리에이션이 없습니다. DOM 차이가 없습니다.

변경 가능한 반응성을 최우선으로 다루면 변경 불가능한 상태와 유사하지만 가장 똑똑한 컴파일러도 달성할 수 없는 기능을 갖춘 작성 경험을 얻을 수 있습니다. 하지만 오늘 우리는 반응형 저장소에 대해 정확히 이야기하려고 여기에 온 것이 아닙니다. 이것이 파생과 어떤 관련이 있나요?


파생 재검토

우리가 알고 있는 파생 값은 변경할 수 없습니다. 실행될 때마다 다음 상태를 반환하는 함수가 있습니다.

상태 = fn(상태)

입력이 변경되면 다시 실행되고 다음 값을 얻습니다. 이는 반응형 그래프에서 몇 가지 중요한 역할을 합니다.

첫째, 메모 포인트 역할을 합니다. 입력이 변경되지 않았다는 것을 인식하면 비용이 많이 드는 계산이나 비동기 계산에 대한 작업을 줄일 수 있습니다. 값을 다시 계산하지 않고도 여러 번 사용할 수 있습니다.

둘째, 융합 노드 역할을 합니다. 이는 그래프의 "조인"입니다. 그들은 서로 다른 여러 소스를 함께 묶어 관계를 정의합니다. 이것이 함께 업데이트하는 핵심이지만, 유한한 수의 소스와 그 사이의 의존성이 점점 커지면 결국 모든 것이 얽히게 될 것이라는 이유도 됩니다.

정말 의미가 있습니다. 파생된 불변 데이터 구조에는 "포크"가 아닌 "조인"만 있습니다. 복잡성이 증가하면 병합할 운명이 됩니다. 흥미롭게도 반응성이 뛰어난 "상점"에는 이 속성이 없습니다. 개별 부품은 독립적으로 업데이트됩니다. 그렇다면 이러한 생각을 파생에 어떻게 적용할 수 있을까요?

모양을 따라가기

Mutable Derivations in Reactivity

Andre Staltz는 몇 년 전에 모든 유형의 반응형/반복 가능 기본 요소를 하나의 연속체로 연결한 놀라운 기사를 발표했습니다. 밀고 당기는 것이 하나의 모델로 통일되어 있습니다.

저는 Andre가 이 기사에 적용한 체계적인 사고에서 오랫동안 영감을 받았습니다. 그리고 저는 오랫동안 이 시리즈에서 다루었던 주제들과 씨름해 왔습니다. 때로는 디자인 공간이 존재한다는 것을 이해하는 것만으로도 올바른 탐색을 시작하기에 충분합니다. 때로는 처음에는 솔루션의 모양만 이해하면 됩니다.


예를 들어, 특정 상태 업데이트에 대한 동기화를 피하려면 쓰기 가능한 상태를 파생시키는 방법이 필요하다는 것을 오래 전에 깨달았습니다. 그것은 몇 년 동안 내 마음 한구석에 머물렀지만 마침내 쓰기 가능한 파생물을 제안했습니다.

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사


항상 소스에서 재설정할 수 있지만 다음에 소스가 변경될 때까지 단기 업데이트를 적용할 수 있다는 아이디어입니다. Effect 대신 이것을 사용하는 이유는 무엇입니까?

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사


이 시리즈의 1부에서 자세히 다루었듯 여기의 Signal은 props.field에 의존한다는 사실을 결코 알 수 없었습니다. 종속성을 다시 추적할 수 없기 때문에 그래프의 일관성이 깨집니다. 직관적으로 나는 동일한 기본 요소 안에 읽기를 넣으면 해당 기능이 잠금 해제된다는 것을 알았습니다. 실제로 createWritable은 현재 사용자 영역에서 완전히 구현 가능합니다.

<script> // Detect dark theme var iframe = document.getElementById('tweet-1252839841630314497-983'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1252839841630314497&theme=dark" } </script>
const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

그냥 고차 신호일 뿐입니다. A Signal of Signals 또는 Andre는 이를 "Getter-getter" 및 "Getter-setter" 조합이라고 불렀습니다. 전달된 fn이 외부 파생(createMemo)을 실행하면 이를 추적하고 신호를 생성합니다. 이러한 종속성이 변경될 때마다 새로운 신호가 생성됩니다. 그러나 교체될 때까지 해당 Signal은 활성 상태이며 반환된 getter 함수를 수신하는 모든 항목은 종속성 체인을 유지하는 파생 및 Signal을 모두 구독합니다.

우리는 솔루션의 모양을 따랐기 때문에 여기까지 왔습니다. 그리고 시간이 지나면서 그 모양을 따라가며 이제 마지막 기사 끝에 표시된 것처럼 이 변경 가능한 파생 기본 요소는 쓰기 가능한 파생이 아니라 파생 신호라고 믿습니다.

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

하지만 우리는 여전히 불변의 기본 요소를 보고 있습니다. 예, 이것은 쓰기 가능한 파생이지만 값 변경 및 알림은 여전히 ​​전체적으로 발생합니다.


차이의 문제

Mutable Derivations in Reactivity

직관적으로 공백이 있음을 알 수 있습니다. 해결하고 싶은 일의 예를 찾을 수 있었지만 해당 공간을 처리하기 위해 단일 기본 요소를 사용할 수 없었습니다. 문제의 일부가 모양이라는 것을 깨달았습니다.

한편으로는 파생된 값을 Stores에 넣을 수 있습니다.

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

하지만 어떻게 모양을 동적으로 변경할 수 있습니까? 즉, Store에 쓰지 않고 다른 getter를 생성할 수 있습니까?

반면에 Stores에서 동적 모양을 파생할 수 있지만 해당 출력은 Store가 아닙니다.

const [user, setUser] = createSignal({ id: 1, name: "John" });

let prev;
createEffect(() => {
  const u = user();
  // diff values
  if (u.id !== prev?.id) console.log("User", u.id);
  if (u.name !== prev?.name) console.log("Name", u.name);

  // set previous
  prev = u;
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser({ id: 2, name: "Jack" }); 

// effect logs "Name Janet"
setUser({ id: 2, name: "Janet" });
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

파생된 모든 값이 다음 값을 반환하는 래퍼 함수를 ​​전달하여 만들어지면 어떻게 변경 사항을 격리할 수 있습니까? 기껏해야 새 결과를 이전 결과와 비교하고 세부적인 업데이트를 외부에 적용할 수 있었습니다. 하지만 그것은 우리가 항상 차이점을 원한다고 가정했습니다.

저는 Solid의 For 구성 요소와 같은 것을 일반적인 방식으로 구현하는 증분 계산에 대한 Signia 팀의 기사를 읽었습니다. 그러나 논리가 더 이상 단순하지 않다는 사실을 발견했습니다.

  • 불변의 단일 신호입니다. 중첩된 변경 사항은 독립적으로 실행될 수 없습니다.

  • 체인의 모든 노드가 참여해야 합니다. 각각은 업데이트된 값을 실현하기 위해 소스 diff를 적용해야 하며, 끝 노드를 제외하고 전달할 diff를 생성해야 합니다.

불변 데이터를 다룰 때. 참조가 손실됩니다. Diff는 이 정보를 다시 얻는 데 도움이 되지만 전체 체인에 대한 비용을 지불하게 됩니다. 그리고 서버의 새로운 데이터와 같은 경우에는 안정적인 참조가 없습니다. 모델에 "키"를 지정해야 하는 것이 있는데 예제에 사용된 Immer에는 없습니다. React에는 이런 능력이 있습니다.

그때 이 라이브러리가 React용으로 구축되었다는 생각이 들었습니다. 더 많은 차이가 있을 것이라는 가정은 이미 구워졌습니다. 일단 diffing을 포기하고 나면 diffing은 더 많은 diffing을 낳게 됩니다. 그것은 피할 수 없는 진실이다. 그들은 전체 시스템에 걸쳐 비용을 증가시켜 과도한 업무 부담을 피하는 시스템을 만들었습니다.

Mutable Derivations in Reactivity

내가 너무 영리하려고 노력하고 있다는 느낌이 들었습니다. 지속 불가능하지만 "나쁜" 접근 방식이 더 성능이 좋다는 것은 부인할 수 없습니다.


(세밀한) 반응성의 대통일 이론

Mutable Derivations in Reactivity

불변하게 모델링하는 데는 아무런 문제가 없습니다. 하지만 공백이 있습니다.

그럼 모양을 따라가 보겠습니다.

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

파생된 기능이 신호 설정 기능과 동일한 모양이라는 것이 분명해졌습니다. 두 경우 모두 이전 값을 전달하고 새 값을 반환합니다.

스토어와 함께 하면 어떨까요?

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

파생 소스를 가져올 수도 있습니다.

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

여기에는 대칭이 있습니다. 불변 변경은 항상 다음 상태를 구성하고, 변경 가능 변경은 현재 상태를 다음 상태로 변경합니다. 비교도 하지 않습니다. 불변 파생(메모)에서 우선순위가 변경되면 전체 참조가 교체되고 모든 부작용이 실행됩니다. 가변 파생(Projection)에서 우선순위가 변경되면 우선순위를 구체적으로 듣는 것만 업데이트됩니다.


투영 탐색

불변 변경은 어떤 변경 사항에 관계없이 다음 상태만 구축하면 되므로 작업이 일관됩니다. Mutable은 변경 내용에 따라 다른 작업을 수행할 수 있습니다. 불변 변경에는 항상 수정되지 않은 이전 상태가 적용되지만 변경 가능 변경에는 적용되지 않습니다. 이는 기대에 영향을 미칩니다.

예제에서 조정을 사용해야 한다는 점을 이전 섹션에서 확인할 수 있습니다. 완전히 새로운 개체가 투영과 함께 전달되면 모든 것을 바꾸는 데 만족하지 않습니다. 변경 사항을 점진적으로 적용해야 합니다. 어떤 업데이트에 따라 다른 방식으로 변경해야 할 수도 있습니다. 매번 모든 변경 사항을 적용하고 스토어 내부 동등성 검사를 활용할 수 있습니다.

const [user, setUser] = createSignal({ id: 1, name: "John" });

let prev;
createEffect(() => {
  const u = user();
  // diff values
  if (u.id !== prev?.id) console.log("User", u.id);
  if (u.name !== prev?.name) console.log("Name", u.name);

  // set previous
  prev = u;
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser({ id: 2, name: "Jack" }); 

// effect logs "Name Janet"
setUser({ id: 2, name: "Janet" });
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

그러나 이것은 얕게만 작동하기 때문에 엄청나게 빨라집니다. 조정(차이점)은 항상 선택 사항입니다. 그러나 종종 우리가 하고 싶은 것은 필요한 것만 적용하는 것입니다. 이로 인해 코드가 더 복잡해지지만 훨씬 더 효율적일 수 있습니다.

Solid의 Trello 복제본에서 발췌한 내용을 수정하면 프로젝션을 사용하여 각 낙관적 업데이트를 개별적으로 적용하거나 서버의 최신 업데이트에 보드를 조정할 수 있습니다.

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

이 기능은 UI에서 참조를 유지하여 세부적인 업데이트만 발생할 뿐 아니라 복제 및 비교 없이 점진적으로 변형(낙관적 업데이트)을 적용하므로 강력합니다. 따라서 구성 요소를 다시 실행할 필요가 없을 뿐만 아니라, 변경할 때마다 보드의 전체 상태를 반복적으로 재구성하여 변경된 사항이 거의 없음을 다시 한 번 인식할 필요가 없습니다. 그리고 마지막으로, 차이가 필요할 때 서버가 최종적으로 새로운 데이터를 반환하면 업데이트된 예측과 비교합니다. 참조는 유지되며 다시 렌더링할 필요가 없습니다.

저는 이 접근 방식이 미래의 실시간 및 로컬 우선 시스템에 큰 승리가 될 것이라고 믿지만, 오늘날 우리는 아마도 이를 깨닫지도 못한 채 이미 Projections를 사용하고 있습니다. 인덱스에 대한 신호를 포함하는 반응형 맵 기능을 고려해보세요.

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

색인은 반응형 속성으로 색인을 포함하지 않는 행 목록에 투영됩니다. 이제 이 프리미티브는 매우 기본이므로 createProjection을 사용하여 구현하지는 않을 것입니다. 그러나 이것이 절대적으로 하나라는 점을 이해하는 것이 중요합니다.

또 다른 예는 Solid의 모호한 createSelector API입니다. 선택한 항목을 변경해도 모든 행이 업데이트되지 않도록 성능이 뛰어난 방식으로 선택 상태를 행 목록에 투영할 수 있습니다. 형식화된 Projection 프리미티브 덕분에 더 이상 특별한 프리미티브가 필요하지 않습니다.

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

이렇게 하면 ID로 조회하는 맵이 생성되지만 선택한 행만 존재합니다. 이는 구독 기간 동안의 프록시이므로 존재하지 않는 속성을 추적하고 업데이트될 때 알림을 보낼 수 있습니다. SelectionId를 변경하면 최대 2개의 행(이미 선택된 행과 선택 중인 새 행)이 업데이트됩니다. O(n) 연산을 O(2)로 바꿉니다.

이 기본 요소를 더 많이 사용하면서 직접 변경 가능한 파생을 수행할 뿐만 아니라 반응성을 동적으로 전달하는 데 사용할 수 있다는 것을 깨달았습니다.

const [user, setUser] = createSignal({ id: 1, name: "John" });

let prev;
createEffect(() => {
  const u = user();
  // diff values
  if (u.id !== prev?.id) console.log("User", u.id);
  if (u.name !== prev?.name) console.log("Name", u.name);

  // set previous
  prev = u;
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser({ id: 2, name: "Jack" }); 

// effect logs "Name Janet"
setUser({ id: 2, name: "Janet" });
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

이 프로젝션은 사용자의 ID와 이름만 노출하지만 privateValue에는 액세스할 수 없습니다. 이름에 getter를 적용한다는 점에서 흥미로운 일을 합니다. 따라서 전체 사용자를 교체할 때 프로젝션이 다시 실행되는 동안 프로젝션을 다시 실행하지 않고 사용자 이름만 업데이트하면 효과를 실행할 수 있습니다.

이러한 사용 사례는 극히 일부에 불과하며 이해하는 데 시간이 조금 더 걸린다는 점을 인정합니다. 하지만 프로젝션은 시그널 스토리의 누락된 연결고리라고 생각합니다.


결론

Mutable Derivations in Reactivity

지난 몇 년 동안 반응성 파생에 대한 탐구를 통해 저는 많은 것을 배웠습니다. 반응성에 대한 나의 모든 관점이 바뀌었습니다. 필요악으로 간주되는 대신 가변성이 나에게 성장하여 반응성의 뚜렷한 기둥이 되었습니다. 체계적인 접근 방식이나 컴파일러로 에뮬레이션되지 않는 것입니다.

이것은 불변 반응성과 가변 반응성의 근본적인 차이를 제시하는 강력한 진술입니다. Signal과 Store는 같은 것이 아닙니다. 메모와 프로젝션도 마찬가지입니다. API를 통합할 수도 있지만 그렇게 해서는 안 될 수도 있습니다.

저는 Solid의 API 형태를 따라 이러한 깨달음을 얻었지만 다른 솔루션은 신호에 대해 다른 API를 사용합니다. 그래서 그들이 같은 결론을 내릴지 궁금합니다. 공평하게 말하면 투영을 구현하는 데 어려움이 있으며 여기서 이야기는 끝나지 않았습니다. 그러나 나는 이것이 당분간 우리의 사고 탐구를 끝내는 것이라고 생각합니다. 앞으로 할 일이 많아요.

이 여정에 함께해주셔서 감사합니다.

위 내용은 반응성의 가변 파생의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!